qub-protocolspecificatie

qub is een protocol voor cryptografische temporele verplichtingen: een systeem om woorden voor een toekomstige datum te verzegelen en later precies te verifiëren wat er verzegeld was, waarbij drand de vrijgave ervan per ronde regelde, en—wanneer een opslagtransactie of transparantielogbewijs beschikbaar is—een onafhankelijk tijdgestempelde bovengrens voor wanneer de ciphertext werd vastgelegd.

Drie primitieven laten het werken. drand is een gedecentraliseerde randomness beacon—de onthullingsdatum wordt cryptografisch afgedwongen in plaats van door de welwillendheid van qub. Duurzame opslag bestendigt erkende verzegelde bytes terwijl de huidige publicatiepaden afzonderlijke permanente-opslagtransacties plannen; succesvolle algemene upload-logboekbijvoegsels kunnen bovendien deelnemen aan gebatchte, verankerde toezeggingen. ML-DSA-65 is een post-quantum digitale handtekening—wanneer auteurschap is ingeschakeld, is de qub gekoppeld aan een sleutelpaar waarvan het geheim nooit het apparaat van de auteur verlaat.

Samen maken deze primitieve elementen een verklaring die tijd-gebonden en moeilijk te manipuleren is, optioneel toewijsbaar, en onafhankelijk te tijdstempelen—a kwitantie waarvan de waarde groeit naarmate het vermogen van de wereld om het verleden na te bootsen verbetert.

Het resterende deel van dit document is de normatieve specificatie die vereist is voor interoperabele implementaties.


qub-protocolspecificatie

Veld Waarde
Document vrijgave 1.0.0 (protocol-v1.0.0)
Draadprotocol 0x01
Buitenste verpakking 0x01
Ingangsdatum 23-09-2026
Status Huidig
Beoordeeld door 23-09-2026

Dit document is de normatieve protocolspecificatie voor het qub-systeem voor temporele toezeggingen. Het definieert datastructuren, serialisatieregels, afleidingsformules en verificatieprocedures die nodig zijn voor interoperabele implementaties.

Reikwijdte: de protocollaag is opzettelijk taalneutraal — de qub-body is ondoorzichtige platte tekst / markdown / pact-bytes, en locale-afhankelijke rendering is de verantwoordelijkheid van de lezer (qub.social-webapp, <qub-embed>-iframe, MCP-clients, enz.).


1. Notatie en conventies

Notatie Betekenis
u8, u64, i64 Niet-getekende / getekende gehele getallen van de aangegeven bitbreedte
[u8; N] Byte-array met vaste lengte van N bytes
Vec<u8> Byte-array met variabele lengte
Option<T> Waarde van type T, of afwezig
String UTF-8-tekenreeks, NFC-genormaliseerd
`
SHA3-256(x) NIST SHA3-256-hash van bytereeks x (FIPS 202)
ceil(x) Afrondingsfunctie naar boven: kleinste geheel getal ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Meest significante byte eerst

Alle gehele getallen in preimage-constructies worden gecodeerd als big-endian-byte-arrays met vaste breedte (i64 → 8 bytes, u8 → 1 byte), tenzij anders aangegeven.

Alle tijdstempels zijn Unix-seconden in UTC.


2. Datastructuren

2.1 ComposeQub (in-memory-status van de maker)

Niet geserialiseerd naar CBOR. Niet naar permanente opslag geschreven. Lokaal in de maker-app.

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

2.2 QubEnvelope (ontsleutelde payload)

Geserialiseerd met canonieke CBOR (§3). Versleuteld binnen de SealedQub. Dit is de structuur die de integriteit van de inhoud bewijst na ontsleuteling.

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

Basislijn (niet-ondertekende tekst qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; handtekening- en medeondertekeningsvelden ontbreken. Andere optionele metadata-velden kunnen aanwezig zijn.

Andere v1-configuraties: content_type = 0x03 (pact-body, zie §6.1); sig_alg = 0x01 (ML-DSA-65) met author_signature en author_pubkey aanwezig (zie §9.3); cosigner_pubkey en cosigner_signature samen aanwezig voor medeondertekende pacts (zie §9.7); reply_to ingesteld op het qub_id van de bovenliggende qub voor qubs in antwoordketens (zie §9.3 voor de implicaties voor het handtekeningbereik).

2.3 SealedQub (canoniek wire-formaat)

Geserialiseerd met behulp van canonieke CBOR (§3). Dit is het interne draadartefact: openbare levering slaat deze bytes naakt op, terwijl privélevering ze inwikkelt in OuterWrapper voor opslag (§13).

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

2.4 RevealedQub (toepassingsstatus van de lezer)

Niet geserialiseerd naar CBOR. Lokaal in de lezer-app. Geconstrueerd na geslaagde ontsleuteling en verificatie.

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

3. Canoniek CBOR-profiel

Alle SealedQub- en QubEnvelope-serialisatie MOET voldoen aan dit profiel. Twee implementaties moeten, gegeven dezelfde logische structuur, identieke bytes produceren.

3.1 Coderingsregels

Regel Specificatie
Standaard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Volgorde van mapsleutels Gesorteerd op gecodeerde bytelengte eerst (korter vóór langer), daarna lexicografisch (byte-voor-byte voor coderingen van dezelfde lengte)
Gehele-getalcodering Kortste vorm: 0–23 in initiële byte; 24–255 in 2 bytes; 256–65535 in 3 bytes; enz.
Lengtecodering Alleen bepaalde lengten. Geen arrays, maps, byte-strings of tekst-strings met onbepaalde lengte (additional info = 31 is verboden).
Tags Geen CBOR-tags (major type 6 is verboden).
Drijvende komma Geen floats (major type 7 waarden 0xF9–0xFB zijn verboden).
Tekst-strings UTF-8-gecodeerd, NFC-genormaliseerd (Unicode Normalization Form C).
Byte-strings Ruwe bytes. Geen base64-codering op de CBOR-laag.
Dubbele sleutels Weigeren met fout. Parsers MOGEN GEEN dubbele mapsleutels stilzwijgend accepteren.
Onbekende sleutels Weigeren met fout. Parsers MOGEN GEEN mapsleutels buiten de canonieke sleutelset van het type tolereren — twee verschillende canonieke byte-strings mogen nooit naar dezelfde waarde decoderen (encode(decode(x)) == x), en voor ondertekende payloads zou een extra sleutel verborgen inhoud zijn waaraan beide handtekeningen zich vastleggen. Schema-evolutie verloopt via version, nooit via extra sleutels.
Eenvoudige waarden Alleen true (0xF5), false (0xF4) en null (0xF6) zijn toegestaan.
Optionele velden Afwezige optionele velden worden volledig uit de CBOR-map weggelaten (niet gecodeerd als null). Aanwezige optionele velden worden opgenomen in gesorteerde sleutelvolgorde.

3.2 Geverifieerde canonieke sleutelvolgordes

Deze sleutelvolgordes zijn normatief. Implementaties MOETEN sleutels in exact deze volgorde uitvoeren. Debug-asserties ZOUDEN de volgorde MOETEN verifiëren in niet-release-builds.

QubEnvelope (versie 0x01, niet ondertekend, alle optionele velden afwezig):

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

Afleiding van de QubEnvelope-sleutelvolgorde: elke sleutel is een CBOR-tekst-string. Gecodeerde lengte = 1 byte header + stringlengte (voor strings korter dan 24 bytes). Sorteer eerst op totale gecodeerde lengte, daarna lexicografisch voor sleutels van dezelfde lengte.

SealedQub (versie 0x01, openbaar, 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 (verdict mechanic)
"visibility"        (11 encoded bytes)
"drand_round"       (12 encoded bytes)
"drand_chain_id"    (15 encoded bytes)
"recipient_pubkey"  (17 encoded bytes)  ← only if present
"tlock_ciphertext"  (17 encoded bytes)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)

PactTerms (pact-body, 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 (rij van de terms-array):

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

PartyIdentifier (party_a / party_b-map):

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

3.3 Bytecoderingsreferentie

Type CBOR-codering Voorbeeld
SHA3-256-hash (32 bytes) 0x58 0x20 + 32 bytes body_hash, qub_id
Tijdstempels (i64) Major type 0 (positief) of 1 (negatief), kortste codering Unix-seconden
Versie (u8, waarde 1) 0x01 (enkele byte)
Inhoudstype (u8, waarde 1) 0x01 (enkele byte)
sig_alg (u8, waarde 0) 0x00 (enkele byte)
ML-DSA-65-handtekening (3.309 bytes) 0x59 0x0C 0xED + 3.309 bytes author_signature, cosigner_signature
ML-DSA-65-publieke sleutel (1.952 bytes) 0x59 0x07 0xA0 + 1.952 bytes author_pubkey, cosigner_pubkey

4. Normatieve afleidingen

4.1 qub_id

De qub_id identificeert een qub op unieke wijze en koppelt de QubEnvelope aan de SealedQub. Hij wordt deterministisch afgeleid uit de envelope-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

Codering van de domeinseparator: de string "QUB_ID_V2" bestaat uit 9 ASCII-bytes. Eén 0x00-padding-byte wordt toegevoegd om 10 bytes te bereiken voor uitlijning. Implementaties MOETEN exact deze 10 bytes gebruiken: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

outcome_at codering: Een pre-release implementatie revisie verlengde de preimage van 92 naar 100 bytes om de optionele te vouwen outcome_at veld in de binding. Afwezig outcome_at is gecodeerd als 8 nullen bytes; de protocolvalidators wijzen af outcome_at <= 0 overal zodat deze sentry niet kan botsen met een legitieme waarde. Zie §3.2 (draadformaat) en de in-tree tasks/verdict-uplift-plan.md voor de vonnismechaniek die dit veld motiveert.

drand_round codering: Een latere pre-release implementatierevisie verlengde de preimage van 100 naar 108 bytes om te vouwen drand_round (de doel-drandronde, §4.3) in de binding, en verhoogde de domeinscheiding tot QUB_ID_V2. Dit koppelt de timelock-ronde aan de qub-identiteit: een gateway kan de ciphertext niet aan een andere (bijv. reeds verstreken) ronde binden dan die weergegeven unlock_at impliceert. De ontgrendelingsprocedure (§8) controleert bovendien of de ronde die in de tlock-ciphertextstanza is verwerkt overeenkomt unlock_round(unlock_at), dus de weergegeven ontgrendeltijd is aantoonbaar de ronde die decryptie mogelijk maakt.

Eigenschappen:

4.2 body_hash

body_hash = SHA3-256(body)

Waarbij body de ruwe Vec<u8>-inhoudspayload is. Voor tekst-qubs is dit de UTF-8-gecodeerde qub-body.

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

Waarbij title de optionele platte-tekst-titel is die wordt getoond op de aftelling van de lezer vóór onthulling (zie §3.2). NFC-normalisatie wordt op hashtijd uitgevoerd zodat de digest stabiel is voor visueel gelijkwaardige code-puntreeksen. De all-zeros-sentinel is gereserveerd voor het afwezige geval; een lege string wordt geweigerd op de canonieke CBOR-grens als een niet-canonieke codering van "afwezig" (de canonieke codering laat het veld volledig weg).

4.3 Ontgrendel-Ronde Mapping

drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
Parameter Bron Voorbeeld
unlock_at Door gebruiker gekozen Unix-seconden UTC 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time drand chain info (genesis_time) 1595431050
chain_period_seconds drand chain info (period) 30

Dit is de referentie-tlockmapping (drand's CurrentRound). drand publiceert ronde N bij chain_genesis_time + (N - 1) * chain_period_seconds, dus de formule selecteert de ronde stroom bij unlock_at — de ronde waarvan het handtekening de eerste is die een kijker ziet bij aankomst unlock_at kan gebruiken.

Uitlijningsattribuut (het geval dat in de praktijk van belang is): wanneer (unlock_at - chain_genesis_time) is precies deelbaar door chain_period_seconds, de handtekening van de geselecteerde ronde wordt gepubliceerd precies om unlock_at, nog nooit eerder. Dit geldt altijd voor de referentie-implementatie: de genesis-tijd van quicknet (1692803367) is deelbaar door zijn 3-secondenperiode, en de referentie-apps vergrendelen ontgrendeltijden op hele minuten. Voor een niet-uitgelijnde unlock_at, de handtekening van de geselecteerde ronde wordt strikt minder dan één periode ervoor gepubliceerd unlock_at — de temporele precisie van de verplichting is één beacon-periode.

Legacy pre-release mapping en unlock-zijde tolerantie: de oorspronkelijke mapping was ceil((unlock_at - chain_genesis_time) / chain_period_seconds), die—voor het hierboven genoemde periodelijk afgestemde geval—de volledig afgeronde gepubliceerde periode selecteerde voor unlock_at, waardoor de cijfertekst precies één periode eerder te ontsleutelen is. De twee toewijzingen verschillen precies +1 wanneer de delta de periode deelt, en anders akkoord gaan. Omdat drand_round is gevouwen in het onveranderlijke qub_id preimage (§4.1), onder de legacy-mapping verzegelde artefacten kunnen niet opnieuw worden afgeleid; verifiers die de §8 stap 6a ronde cross-check uitvoeren, MOETEN daarom een opgeslagen accepteren drand_round gelijk aan of de afgeleide ronde of het afgeleide ronde min één (en MOET vereisen dat de tlock-stanza-ronde exact gelijk is aan de opgeslagen ronde). De tolerantie vergroot de vroegste gating-handtekening met hoogstens één periode. De pact staging-service past dezelfde tolerantie toe wanneer het een geplande pact opnieuw afleidt qub_id (bij podium en bij co-ondertekening): als de huidige mappingronde de gecommitteerde waarde niet reproduceert qub_id en de delta deelt de periode, het probeert het opnieuw met de ronde min één, en het verzegelt het afgeronde pact tot welke ronde dan ook de qub_id bindt eigenlijk—nooit blindelings aan de opnieuw berekende ronde, wat het artefact permanent onafleidbaar zou maken.

Validatie: unlock_at MOET in de toekomst zijn bij zeehondentijd. unlock_at MAG niet meer dan 10 jaar zijn vanaf created_at (om het risico van lange-termijn afhankelijkheid van drand te beperken; de UI MOET waarschuwen voor ontgrendelingsdata die meer dan 2 jaar in de toekomst liggen).


5. Wire-formaat-newtypes

Wire-formaat-newtypes bieden veiligheid tijdens compilatie tegen het verwarren van CBOR-bytes met JSON, ruwe platte tekst of andere bytecoderingen.

Type Bevat Geproduceerd door Verzwolgen door
SealedQubCbor Canonieke CBOR van SealedQub serialize_sealed_qub() Binnenstaadsdraad artefact; onbedekt opgeslagen voor openbare levering of verpakt voor privélevering, daarna door de kijker hersteld
QubEnvelopeCbor Canonieke CBOR van QubEnvelope serialize_qub_envelope() tlock versleutel invoer, tlock ontsleutel uitvoer

5.1 Constructieregels

// 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 Validatie bij constructie

from_encoded() ZOU MOETEN valideren dat de invoer begint met een geldige CBOR-map-header. Volledige structurele validatie gebeurt op parse-tijd, niet op constructietijd, om dubbel parseren te vermijden.


6. Inhoudstype-register

Waarde Type Max body-grootte Opmerkingen
0x00 Gereserveerd (ongeldig) — MAG NIET worden gebruikt
0x01 Platte tekst (UTF-8, beperkte Markdown) 50 KB betaald / 10 KB gratis Zie §10 voor renderregels. De splitsing tussen gratis en betaald wordt afgedwongen door de uploadservice; het harde plafond op de protocollaag is 50 KB.
0x02 Gereserveerd (toekomstig) — Toegewezen aan een toekomstig inhoudstype; niet geldig in v1. Lezers MOETEN weigeren volgens de regel hieronder.
0x03 Pact (bilaterale overeenkomst, CBOR-body) 100 KB Body is canonieke CBOR PactTerms (§6.1). Medeondertekenaarsondertekening volgens §9.7.
0x04 Verdict (zelfbeoordeling door de maker, CBOR-body) 8 KB Body is canonieke CBOR VerdictBody (§6.2). Wordt alleen uitgezonden door de systeemzijdige verdict-intentie. De relatie met de bovenliggende qub staat op de Arweave-tag Parent-Tx-Id, niet in de body. Zie verdict-uplift-plan §3.4.

Lezers MOETEN onbekende inhoudstypen weigeren met een duidelijke voor de gebruiker zichtbare fout. Lezers MOGEN GEEN poging doen onbekende typen als tekst te renderen.

6.1 Pact-body (content_type = 0x03)

Een pact-body is de canonieke CBOR-codering van een 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 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }

Canonieke CBOR-sleutelvolgordes voor alle drie de maps staan in §3.2. De totale geserialiseerde pact-CBOR MAG NIET groter zijn dan 100 KB (komt overeen met §6).

Schema-discriminator. De eerste rij in terms voor een structured/v1-pact MOET zijn: { key: "pact_schema", value: "structured/v1" }. Rijen zonder deze marker zijn "custom"-pacts en ontvangen geen gestructureerde validatie of schemabewuste rendering.

Bevroren bevestigingsslots. structured/v1-pacts bevatten precies vier bevestigingsrijen onder deze sleutels:

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

De value voor elk is een van acht bevroren Engelse strings, gekozen op basis van het paar (role, kind), waarbij role ∈ { seller, buyer, provider, client } en kind ∈ { standard, capacity }. De strings zelf zijn normatieve protocoldata — de ML-DSA-65-handtekeningen van beide partijen leggen zich vast op de exacte bytes via body_hash. Ze worden NIET gelokaliseerd; de ondertekende body is taalneutraal. Iedere wijziging van de bewoording vereist een nieuwe schemaversie (structured/v2).

De acht strings, hun opzoeking (acknowledgement_for(role, kind)) en de rationale voor elk zijn vastgepind door de referentie-implementatie. Conforme implementaties MOETEN byte-identieke bevestigingswaarden produceren; golden-fixture-SHA3-256-body-hashtests die alle vier de rolcombinaties dekken, vangen elke drift op.

Weergavevolgorde voor lezers. De bevestigingsstrings bevatten zinsneden zoals "described above", die veronderstellen dat de beschrijvings- / scope-rijen vóór de bevestigingen worden gerenderd. Lezers MOETEN de terms-array in CBOR-volgorde renderen; herordening breekt de prozasemantiek.

Contact van de tegenpartij. Wanneer de contact van Partij B een geldig e-mailadres is, verzendt de qub-uploadservice automatisch een review- / medeondertekeningsuitnodigingsmail op stage-tijd en bindt de uiteindelijke medeondertekening aan verificatie van datzelfde adres (§9.7). Pacts waarbij het contact van Partij B afwezig is, kunnen nog steeds worden medeondertekend, maar alleen via een out-of-band-kanaal — de service weigert medeondertekeningsverzoeken die geen overeenkomstige 15-minuten-e-mailverificatiemarker kunnen produceren.

6.2 Verdict-body (content_type = 0x04)

Een verdict-body is de canonieke CBOR-codering van een 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
}

Canonieke CBOR-sleutelvolgorde:

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

De totale geserialiseerde verdict-CBOR MAG NIET groter zijn dan 8 KB (komt overeen met de registerrij hierboven).

Outcome-enum. De wire-byte is intentieneutraal; de vier categorieën Right / Partial / Wrong / Unfalsifiable dekken de uitkomstruimte van elke verdict-dragende intentie. Per-intentielabels ("Goed voorspeld" / "Gehouden" / "Geleverd" / "Bevestigd" voor Right, enz.) zijn een renderzorg aan de lezerskant die wordt opgelost aan de hand van de intentie van de bovenliggende qub — de wire blijft taal- en intentieneutraal. Waarden buiten 1..=4 MOETEN bij decoderen worden geweigerd.

Koppeling met bovenliggende qub. Een verdict-qub draagt de verwijzing naar de bovenliggende qub NIET in zijn body. Het Arweave-transactie-id van de bovenliggende qub wordt bij upload-tijd uitgezonden als de opslagtag Parent-Tx-Id (§7 opslagtaglaag). Dit houdt de body een op zichzelf staande ondertekende verklaring van zelfbeoordeling; de audit-keten ("waarover gelijk?") wordt vastgesteld via de opzoeking op Arweave-tag.

Veiligheid van de bewijslink (normatief). Wanneer evidence_url aanwezig is, MOETEN validators (compose-zijde, wire-zijde, Worker-edge) afdwingen:

  1. Alleen HTTPS. De string MOET beginnen met de bytesequentie https://. Elk ander schema — http, ftp, javascript, data, file, enz. — wordt geweigerd.
  2. Lengtelimiet. ≤ 2.048 bytes (praktische browser-URL-limiet).
  3. NFC + controle op vijandige codepunten. Zelfde regel als title en reflection — bidi-override- / nulbreedte- / tag-blok- / BOM- / C0- / C1-codepunten worden geweigerd. De definitie komt overeen met de Rust crate::handle::contains_hostile_text_codepoint en de TS workers/api/src/utils/unicode.ts::isHostileCodepoint (houd ze gelijklopend).
  4. Geen witruimte, geen ASCII-stuurtekens. Witruimte / DEL / bytes onder 0x20 waar dan ook in de URL worden geweigerd — sluit de \n/\t-injectievector die de bidi-regel niet dekt.
  5. Niet-leeg host-segment. Alles tussen https:// en de eerste /, ? of # MOET niet leeg zijn.

Geen server-side fetch. De Worker MAG de URL NIET proxyen, ophalen of een preview tonen. Het protocol slaat een string op; rendering gebeurt aan de lezerszijde met rel="nofollow noopener noreferrer" target="_blank" en een zichtbare host die naast de linktekst wordt getoond.

Reflectie. Optionele door de maker geschreven reflectietekst ("wat is er veranderd, wat heb je geleerd"). Zelfde NFC + controle op vijandige codepunten als title. Lege invoer / invoer met alleen witruimte klapt bij constructie samen tot afwezig.

Schemaversie. v1 ondersteunt alleen verdict_version = 0x01. Toekomstige schema-revisies verhogen deze byte en landen samen met een nieuwe protocolversie volgens §12.


7. Verzegelingsprotocol

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

Opslagtaglaag (out-of-band). De qub-uploadservice voegt een opzettelijk kleine set opslagtransactietags toe naast de geselecteerde uploadinhoud. Content-Type=application/octet-stream is normatief vereist. De referentiedienst voegt daarnaast drie optionele tags toe wanneer de maker ervoor kiest deze weer te geven: Intent (toegestane lijst gevalideerde compose-intentie—announcement, thesis, prediction, letter, secret, commitment, proof, of door het systeem uitgegeven verdict), Author (handtekening van de maker §9.3 als 64-karakter kleine letters hex), en Parent-Tx-Id (opslagtransactie-ID van ouder qub voor antwoordketens, 43-teken base64url).

De Author label is aanmelden per qub: de referentie-creator-app voegt het alleen toe wanneer de gebruiker expliciet publieke toeschrijving inschakelt op het moment van verzegeling. Wanneer de schakelaar uit staat — de standaard — geen Author de tag is geschreven en de qub is niet toegeschreven op de chain: niets in de permanente opslag koppelt de upload aan de gebruikersnaam, e-mail of andere qubs van een maker. Wanneer de schakelaar aan staat, de Author vingerafdruk correspondeert met de door de maker gekozen @handle via de §9.5 attestatieketen. Reactieketenrelaties en Intent zijn niet-identificerend. Voor privébezorging versleutelt de buitenste verpakking (§13) de herkenbare binnenkant SealedQub artefact dus het oogsten van opgeslagen wrappers en het verkrijgen van openbare drand-handtekeningen is nog steeds onvoldoende om het lichaam zonder K te herstellen; opslagtags blijven opzettelijk openbare metadata.

De referentie-service voegt opzettelijk GEEN App-Name-, App-Version- of Type-tags toe: zo'n filter met één waarde zou het hele qub-corpus teruggeven bij een GraphQL-query, wat onverenigbaar is met de body-only-vertrouwelijkheidsreikwijdte van de wrapper.

Een conforme verifier MAG NIET afhankelijk zijn van een opslagtag voor §11 derde-partijverificatie; de body-hash / qub_id / handtekening leggen zich alleen vast op de inner CBOR, nooit op de tagset.


8. Ontgrendelingsprotocol

De volledige ontgrendelingssequentie. Elke stap is normatief.

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

9. Auteurschapsondertekening

9.1 Rationale

qubs worden in permanente opslag bewaard. Auteurschapshandtekeningen moeten onvervalsbaar blijven voor onbepaalde tijd, en daarom gebruikt v1.0 het post-quantum-schema ML-DSA-65 (FIPS 204) in plaats van een klassiek schema waarvan de beveiliging binnen de permanente levensduur van de qub kan afnemen.

9.2 Algoritmeregister

sig_alg Schema Sleutellengte Handtekeninggrootte Status
0x00 Geen handtekening (ongetekend) — — Actief
0x01 ML-DSA-65 (FIPS 204) 1.952 bytes 3.309 bytes Actief
0x02 Ed25519 32 bytes 64 bytes Gereserveerde constante; wordt niet ondersteund in protocol v1

Protocol-v1 kijkers MOETEN elke waarde buiten afwijzen {0x00, 0x01}, inclusief de gereserveerde 0x02 waarde. Reservering voorkomt per ongeluk hergebruik; het is niet activering. Het activeren ervan vereist de geregeerde verandering zoals beschreven in §15.

9.3 Constructie van het ondertekende preimage

Er hebben twee preimage-versies bestaan. Alle handtekeningen MOETEN V2 gebruiken, en verifiers MOETEN alleen V2 accepteren. Het verouderde V1-preimage (hieronder gedocumenteerd ter historische referentie) werd tijdens de V2-migratie geaccepteerd als alleen-verificatie-fallback; die fallback is uitgefaseerd en een handtekening die alleen tegen V1 verifieert wordt nu geweigerd.

V2 (huidig — geproduceerd door alle nieuwe auteursondertekening, en door beide handtekeningen van de pact-staging-/medeondertekeningsflow):

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

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

signature = Sign(author_secret_key, sig_input)

sender_label_hash volgt dezelfde afwezigheids-sentinelconventie als title_hash (§4.2.1): 32 nulbytes zijn geen geldige SHA3-256-uitvoer, dus 'afwezig' kan nooit botsen met een aanwezig label. Alle velden hebben een vaste breedte, dus het preimage is ondubbelzinnig zonder lengteprefixen.

V1 (verouderd — UITGEFASEERD; wordt niet meer geproduceerd en niet meer geaccepteerd bij verificatie):

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

Het V1-preimage liet sender_label en reply_to weg. Het werd tijdens de migratie naar V2 geaccepteerd als alleen-verificatie-fallback; die fallback is sindsdien uitgefaseerd — verifiers MOETEN alleen het V2-preimage accepteren. De definitie wordt hier bewaard ter historische referentie en om de domeinseparator hieronder toe te lichten. Een handtekening die alleen tegen V1 verifieert MOET worden behandeld als een verificatiefout.

Domeinseparatoren: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" bestaan elk uit 17 ASCII-bytes ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Geen padding. De verschillende separator scheidt de twee constructies domeinmatig, zodat een handtekening over het ene preimage nooit als het andere kan verifiëren.

org_id_present-byte: de byte na unlock_at MOET 0x00 zijn. De referentie-implementatie stelt deze beschikbaar als de constante ORG_ID_PRESENT_INDIVIDUAL = 0x00 in crates/qub-core/src/signing.rs; lezers die sig_input reconstrueren voor verificatie MOETEN dezelfde byte uitzenden.

Handtekeningreikwijdte — wat wel en niet wordt gedekt. Het V2-sig_input legt zich direct vast op version, qub_id, body_hash, unlock_at, sender_label en reply_to (plus de vaste domeinseparator en org_id_present-byte). qub_id wordt zelf afgeleid uit version, content_type, created_at, unlock_at, outcome_at, drand_round en body_hash via het §4.1-preimage, dus elke wijziging van die velden produceert een andere qub_id en maakt de handtekening transitief ongeldig. Het geauthenticeerde oppervlak is daarom:

Veld Geverifieerd met handtekening Hoe
version ✓ Directe invoer naar sig_input
qub_id ✓ Directe invoer
body_hash ✓ Directe invoer
unlock_at ✓ Directe invoer
sender_label ✓ Directe invoer via sender_label_hash (V2 preimage — de enige geaccepteerde vorm)
reply_to ✓ Directe invoer via reply_to_or_zero (V2 preimage — de enige geaccepteerde vorm)
content_type ✓ Transitief, via qub_id voorafbeelding
created_at ✓ Transitief, via qub_id voorafbeelding
outcome_at ✓ Transitief, via qub_id voorafbeelding
drand_round ✓ Transitief, via qub_id voorafbeelding
body ✓ Transitief, via body_hash = SHA3-256(body)
author_pubkey — (impliciet) Sleutel die de handtekening heeft geverifieerd is per definitie de auteur
cosigner_pubkey / cosigner_signature — Onafhankelijk overgedragen aan dezelfde sig_input (zie §9.7)
drand_chain_id, tlock_ciphertext, visibility — Buitenkant SealedQub velden, niet binnen de envelop — gedekt door hun eigen structurele invarianties (ronde / keten consistentie) maar niet door de auteurs handtekening. (drand_round is nu transitief gebonden via de qub_id preimage — zie hierboven.)

Waarom V2 het enige geaccepteerde preimage is.

Implementaties die sender_label of reply_to aan eindgebruikers tonen MOETEN de geauthenticeerde identiteit (pubkey-vingerafdruk, attestatie) als het primaire identiteitssignaal naar voren brengen, niet het label.

9.4 Verificatieprocedure

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

Handtekeningverificatie is de duurste bewerking (vooral ML-DSA-65). Hij ZOU MOETEN worden uitgevoerd nadat alle goedkopere checks (hash, qub_id, unlock_at) zijn geslaagd.

9.5 Identiteitsattestaties

Identiteitsattestaties — de afbeelding van author_pubkey op door mensen herkenbare identiteitsclaims zoals een qub-handle, e-mailadres, socialmedia-handle of passkey-credential — zijn een progressieve verbetering aan de kant van de lezer en zijn niet vereist voor handtekeningverificatie. Lezers die attestaties oplossen naar een weergegeven identiteit MOETEN de volgende voorrang toepassen:

handle > email > social > fingerprint

De vingerafdruk-fallback is de hex in kleine letters van SHA3-256(author_pubkey); deze is altijd beschikbaar voor elke ondertekende qub. Lezers MOGEN deze afkorten voor weergave — de referentielezer rendert qub: gevolgd door de eerste en laatste vier bytes (qub:<8 hex>…<8 hex>).

Een conforme verifier kan elke controle in §9.4 voltooien zonder contact op te nemen met de qub-API, zonder enig netwerk buiten de permanente opslag en drand, en zonder enige serverside-lookup. Attestatieoplossing is een aparte best-effort-stap die alleen wordt uitgevoerd nadat handtekeningverificatie is geslaagd.

9.6 Grootte-impact

Ed25519 ML-DSA-65
Handtekening 64 bytes 3.309 bytes
Publieke sleutel 32 bytes 1.952 bytes
Totaal per qub 96 bytes 5.261 bytes
Opslagkostenverschil (bij ~$5/MB) ~$0,0005 ~$0,026

Voor een tekst-qub van 500–2.000 bytes verdrievoudigt ML-DSA-65 ruwweg de opgeslagen grootte. De absolute kosten zijn verwaarloosbaar.

9.7 Medeondertekenaarsverificatie (pact-bilaterale overeenkomsten)

Voor bilaterale overeenkomsten (content_type = 0x03) bewijst een tweede handtekeninglaag dat beide partijen hebben ingestemd met dezelfde voorwaarden.

Envelope-velden:

Beide velden MOETEN samen aanwezig zijn of beide afwezig. Als er precies één aanwezig is, MOETEN lezers een integriteitsfout melden.

Verificatieprocedure:

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

Eigenschappen:

E-mailbindingspoort (operationeel). Wanneer een stage-pact een e-mailcontact van Partij B bevat (§6.1), MOET de qub-uploadservice het medeondertekeningsverzoek weigeren tenzij er een kortlevende e-mailverificatiemarker bestaat die overeenkomt met zowel het staging-id als de hash van het genormaliseerde e-mailadres van dat contact. De marker wordt geschreven door /api/v1/auth/verify wanneer het magische-link-token een staging_id draagt en het geverifieerde adres overeenkomt met SHA-256(normalise_email(party_b.contact)) — waarbij normalise_email(addr) de hoofdletter-gevoeligheid van het lokale deel behoudt en alleen het domeingedeelte naar kleine letters omzet (volgens RFC 5321 §2.3.11), en SHA-256 hier de NIST FIPS 180-4-hash is (onderscheiden van de SHA3-256 die wordt gebruikt in de §4-afleidingen) — en verloopt 900 seconden (15 minuten) na uitgifte. Dit is een operationele anti-impersonatiepoort, GEEN onderdeel van het on-chain qub-bewijs — een derde-partij-verifier die §11 herhaalt heeft alleen de permanente opslag en drand nodig, zonder enige serverside-lookup. De marker bestaat alleen aan de server-zijde en is nooit onderdeel van de ondertekende body.

Grootte-impact (ML-DSA-65 auteur + medeondertekenaar):

Component Grootte
Auteurshandtekening 3.309 bytes
Publieke sleutel auteur 1.952 bytes
Medeondertekenaarshandtekening 3.309 bytes
Publieke sleutel medeondertekenaar 1.952 bytes
Totale crypto-overhead 10.522 bytes
Opslagkostenverschil ~$0,05

10. Markdown-rendering en -sanering

Deze sectie is beveiligingskritiek. De lezer rendert tekst-qubs (content_type = 0x01) met een beperkte Markdown-subset.

10.1 Toegestane elementen

10.2 Verboden elementen

Element Behandeling
Ruwe HTML (<div>, <script>, enz.) Volledig verwijderd. Geen HTML komt erdoor.
Afbeeldingen (![alt](url)) Verwijderd. Afbeeldingssyntaxis wordt uit de uitvoer verwijderd.
Links ([text](url)) URL wordt gerenderd als zichtbare platte tekst. Niet automatisch gelinkt. Niet aanklikbaar zonder expliciete gebruikersactie.
Gevaarlijke URL-schema's javascript:, data:, vbscript:, file: — verwijderd.
Iframes, embeds, objects Verwijderd.
HTML-entiteiten Gedecodeerd naar weergavetekens alleen als veilig.

10.3 Implementatie

Implementaties MOETEN een strikte allowlist-parser gebruiken, geen blocklist. De aanbevolen aanpak:

  1. Parse Markdown met pulldown-cmark (of equivalent).
  2. Loop door de AST en verwijder elke node die niet in de allowlist staat (§10.1).
  3. Voor link-nodes: zend de URL uit als zichtbare tekst, niet als een aanklikbaar <a>-element.
  4. Converteer de gefilterde AST naar een getypeerde tussenrepresentatie (bijv. een MarkdownNode-enum met alleen veilige varianten). Ruwe HTML is structureel niet representeerbaar in deze IR.
  5. Render vanuit de getypeerde IR naar de doel-viewlaag (bijv. reactieve viewcomponenten, DOM-nodes). Geen HTML-string-concatenatie of innerHTML op enig moment.

Blocklist-aanpakken zijn broos omdat nieuwe Markdown-extensies of parserkwaliteiten ongefilterde elementen kunnen introduceren. De getypeerde-AST-aanpak maakt XSS structureel onmogelijk — er is geen variant die willekeurige HTML kan dragen.

10.4 Grootte- en structuurlimieten


11. Verificatie door derden

Elke derde partij die de opgeslagen bytes (en K voor een privé/omhuld qub) vasthoudt, kan verifieer het cryptografische artefact zonder samenwerking van qub. Onafhankelijk getimed bestaan vordering vereist bovendien ofwel geverifieerd per-qub permanent-opslag opname of een geverifieerd §16 transparantie-logboekbewijs.

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

Waar verificatie bewijs van levert:

Bewijs invoer Wat het vaststelt
Geldig pakket / verzegeld artefact + drand-handtekening Het teruggevonden lichaam komt overeen body_hash; de metadata ingebonden in qub_id is intact; de versleutelde tekst is gebonden aan de verklaarde drand-ronde; en die ronde is verstreken. Dit doet niet vaststellen wanneer de ciphertext is gemaakt.
Geldige V2 handtekening van auteur/medetekening De houder(s) van de overeenkomstige geheime sleutel(s) hebben het ondertekende oppervlak in §9.3 geauthenticeerd.
Onafhankelijk geverifieerde per-qub opslagtransactie De exacte opgeslagen versleutelde tekst bestond niet later dan de tijdstempel van zijn blok.
Geldige verankerde transparantie-logbewijs De blad-soort-specifieke claim in §16.11, inclusief een bovengrens voor de verbintertijd vanaf het ankerblok.

Wat verificatie NIET bewijst:

Niet-proof Waarom
Auteurschap De sender_label is decoratief. Zonder sig_alg ≥ 0x01, iedereen had deze inhoud kunnen verzegelen.
Intentie Het artefact bewijst bytes en cryptografische relaties, niet wat de maker subjectief bedoelde.
Bestaande verplichting van .qub alleen Een maker kan een geldig pakket samenstellen nadat de gebonden ronde is verstreken. De ingebedde drand-handtekening bewijst dat de ronde is verstreken, niet dat de ciphertext er vóór bestond.
Exacte zegelknoptijd Een opslag- of ankerbloktijdstip is een zelfstandig verifieerbare bovengrens en kan achterlopen bij de lokale actie van de gebruiker. sealed_at / received_at stellingen zijn niet-bewijsmatig.

Het geïmplementeerde transparantielogboek (§16) breidt de verificatie uit over qubs met knop-bewijs bestellen en een vertrouwensloos bovenste-limiet toewijdingstijd (de ankerbloktijd), gespecificeerd naar bladsoort (§16.11). Het voegt geen auteurschap toe of bedoeling; voor het standaard pad van byte-blinde upload bewijst het op zichzelf niet body_hash of drand_round, die blijven komen uit de artefactcontroles.


12. Versiebeheer en releasecontrole

Documentreleases, het interne draadprotocol en de externe verpakking zijn gescheiden versieruimtes. Een alleen-document verduidelijking doet daarom niet stilletjes wijzig bytes, en een toekomstige draadmigratie kan zich niet voordoen als een redactioneel stuk herziening.

12.1 Documentversie

Deze specificatie gebruikt semantische documentreleases (MAJOR.MINOR.PATCH) en een onveranderlijke Git-tag genaamd protocol-v<release>.

De releasedstatus is een van Concept (nog niet normatief), Huidig (de enigste aanbevolen implementatiedoel), of Vervangen (behouden voor historische verificatie). De ongeversioneerde /protocol route toont de huidige release; Het release-label behoudt zijn exacte bron en elke publiceerde locale erbij. Het wijzigen van de status of het versienummer vereist het bijwerken van deze tabel en de release geschiedenis in dezelfde beoordeelde wijziging.

Document vrijgave Ingangsdatum Status Draadprotocol Omhulsel Bron
1.0.0 23-09-2026 Huidig 0x01 0x01 protocol-v1.0.0

12.2 Protocolversie

De version veld (u8) in beide SealedQub en QubEnvelope identificeert de belangrijkste protocolversie.

12.3 Protocolversiegeschiedenis

Versie Waarde Beschrijving
v1 0x01 Privé/ingepakte en openbare/naakte levering; tekst (0x01), pact (0x03), en uitspraak (0x04) lichamen; ML-DSA-65 V2 auteur/medetekenaar ondertekenen; drand quicknet tlock; SHA3-256.

12.4 Voorwaartse compatibiliteit

Een v1-viewer die een QubEnvelope tegenkomt met onbekende CBOR-map-sleutels (sleutels niet in de canonieke volgorde van §3.2) MOET deze weigeren met een decodefout (§3.1). Voorwaartse compatibiliteit hangt af op de version veld, niet op sleutel tolerantie: toekomstige toevoegingen — zelfs kleine metadata — worden onder een nieuwe geleverd version waarde, die een v1-viewer afwijst met een duidelijke "nieuwer protocol"-fout in plaats van stilletjes inhoud te negeren waar de handtekeningen zich aan binden.

Een v1-kijker die tegenkomt sig_alg = 0x01 (ML-DSA-65) maar als de ML-DSA-65 verificatiesteun ontbreekt, MOET de qub-inhoud worden weergegeven met een melding 'handtekening aanwezig maar niet verifieerbaar', en mag de qub niet volledig worden afgewezen. De referentie-implementatie wijst momenteel elke sig_alg waarde anders dan 0x00 en 0x01 omdat het v1-register geen andere geldige algoritme bevat — strikte afwijzing en soft-fail zijn observationeel identiek totdat een derde algoritme wordt geregistreerd. Het soft-fail gedrag hierboven wordt dragend zodra §9.2 een nieuwe entry toelaat, en de referentiekijker zal op dat punt worden bijgewerkt naar soft-fail.

12,5 Buitenste Verpakking Versie

De OuterWrapper zoals beschreven in §13 draagt zijn eigen version byte, onafhankelijk of SealedQub.version en QubEnvelope.version. De twee versieruimtes evolueren afzonderlijk: een toekomstige post-quantum-veilige symmetrische vervanging verhoogt de wrapper-byte zonder de interne protocolversie aan te raken, en een toekomstige toevoeging op protocolniveau (bijv. een nieuw envelopveld) verhoogt de interne versie zonder de wrapper-byte aan te raken.

OUTER_WRAPPER_VERSION_* Waarde Algoritme Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM met 12-byte nonce, 16-byte authenticatietag, AAD gebonden aan qub_id Actief voor privélevering
— 0x02–0xFF Gereserveerd Toekomst

Kijkers MOETEN onbekende wrapper-versies afwijzen met een duidelijke foutmelding. Het protocol houdt de wrapper-versieruimte opzettelijk klein totdat een concrete migratie-driver verschijnt (bijv. NIST-richtlijnen die een andere AEAD bevoordelen); a 0x02 Er wordt een slot toegewezen in dezelfde revisie die het algoritme introduceert.


13. Buitenste versleutelingswrapper

13.1 Rationale

De protocollagen (QubEnvelope → tlock → SealedQub) maken een verzegelde qub tijdvergrendeld: de body is onleesbaar totdat unlock_at en de drand-rondehandtekening zijn gepubliceerd. Na ontgrendeling is de rondehandtekening echter openbaar en is de canonieke CBOR-vorm van SealedQub herkenbaar, zodat een verzamelaar die permanente-opslag-transacties heeft geïndexeerd het hele qub-corpus in bulk zou kunnen ontsleutelen.

Voor privélevering sluit de buitenste encryptielaag dat kanaal door een extra symmetrische AEAD-laag in te voegen tussen de canonieke SealedQubCbor en de opgeslagen bytes. In het browser-seal-pad, de 256-bits sleutel K levens alleen in het URL-fragment van de leverings-URL en op gebruikersapparaten; browsers sturen URL-fragmenten niet naar servers, dus qub.social, elke opslaggateway en elke CDN ervoor zijn observationeel blind voor K. De opgeslagen representatie van een privé qub is daarom een ondoorzichtige ciphertext waarvan de platte tekst niet te herstellen is zonder de URL die de maker ervoor heeft gekozen te delen. Openbare levering laat deze laag opzettelijk weg (§13.8).

Netto effect:

13.2 Lagen

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

Verzegelen en ontgrendelen op de protocollaag (§7, §8) zijn onveranderd onder de wrappergrens; de wrapper wordt aangebracht op de aanroepplaats van seal() en losgemaakt op de aanroepplaats van unlock().

13.3 OuterWrapper-datastructuur

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

Veldinvarianten.

CBOR-codering. Canonieke CBOR volgens §3, met dezelfde sleutelvolgorderegel (gesorteerd op gecodeerde bytelengte oplopend, daarna lexicografisch). De vier sleutels zijn:

Sleutel Gecodeerde bytes Volgorde
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

De eerste byte van de OuterWrapper-CBOR is daarom de map-header met bepaalde lengte voor een map met 4 elementen (0xA4).

13.4 AAD-binding aan qub_id

De wrapper bindt qub_id als AEAD-aanvullende geauthenticeerde data. Dit is de dragende structurele verdediging tegen drie klassen aanvallen:

Aanval Verdediging
Verplaats de ciphertext onder een andere qub_id veld in de wrapper AAD komt niet overeen → AEAD-authenticatie mislukt
Meng het URL-fragment van qub A met de opgeslagen bytes van qub B Verkeerde sleutel (en onafhankelijk gebonden AAD) → AEAD-authenticatie mislukt
Fraudeer met de qub_id veld van de wrapper na upload AAD mismatch → AEAD-authenticatie mislukt

Het meedragen van qub_id in de platte tekst van de wrapper verzwakt de enumeratie-immuniteit niet substantieel — qub_id is zelf een SHA3-256-hash van het §4.1-preimage zonder herstelbare preimage uit de digest, en een enumerator die de wrapper-bytes al heeft verzameld leert niets uit de zichtbare qub_id dat hij niet kon afleiden uit het bestaan van de upload zelf.

13.5 Wrap- en unwrap-algoritmen

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

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

Inklappen van faalmodi. Foute K, foute nonce, AAD-mismatch en gemanipuleerde ciphertext produceren allemaal dezelfde DECRYPT_FAILED-fout. Dit is een opzettelijke AEAD-eigenschap: het onderscheiden van de faalmodus zou een zijkanaal creëren dat een externe aanvaller kan onderzoeken door misvormde wrappers te sturen en de respons te timen. Referentie-implementaties MOETEN alle AEAD-fouten samenvouwen tot één enkele foutvorm.

13.6 Sleutelmateriaal en distributie

De wrapping-sleutel K is een 256-bit-uniforme willekeurige waarde die per qub wordt gegenereerd door een CSPRNG. De referentie-implementaties betrekken deze uit:

Distributie: K MOET worden gecodeerd als URL-veilige base64 (RFC 4648 §5, geen padding) en aan de bezorglink worden toegevoegd als het fragmentcomponent:

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

Het fragment wordt nooit verzonden naar enige server door een conforme browser. Herstelkanalen (serverside-geschiedenisindex, opt-in e-mail-automatisch-verzenden) die de volledige bezorglink — inclusief het fragment — bewaren buiten het apparaat van de gebruiker, vormen een expliciete afweging tegen de standaard crypto-shredding-positie en MOETEN worden afhankelijk gemaakt van expliciete toestemming van de gebruiker.

Fragmentverlies. Als een gebruiker het URL-fragment verliest en geen herstelkanaal heeft, is de qub onleesbaar. Dit is de dragende afweging van het ontwerp en MOET worden bekendgemaakt aan de gebruiker op het moment van verzegelen. De MVP versterkt de mededeling op verzegelingstijd met expliciete tekst "bewaar deze URL" en een geverifieerd-e-mail-herstelkanaal voor gebruikers die zich aanmelden.

13.7 Buiten de reikwijdte van deze sectie

13.8 Openbare qubs (weglaten van de wrapper)

De buitenste wikkel is optioneel op de afleverlaag. Een maker kan een qub verzegelen als openbaar, in welk geval de canonieke SealedQubCbor komt de opslagpijplijn binnen direct, zonder OuterWrapper laag en geen sleutel K:

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

Een openbaar café is tijd-vergrendeld maar niet link-gebonden: het blijft onleesbaar totdat de drand-rondte wordt gepubliceerd (de tlock-laag blijft ongewijzigd), maar na ontgrendeling kan iedereen die de opslagtransactie-id heeft het ontsleutelen — geen URL-fragment is vereist, omdat er geen is K. Dit is de opzettelijke uitwisseling voor oppervlakken die de server moet aansturen: reveal-notificatied-mails, fragment-loze oEmbed/auto-embed-links en rijkere post-reveal SEO hebben allemaal een link nodig die werkt zonder een geheim dat de server nooit bezit (§13.6). Een private qub kan nog steeds de expliciete <qub-embed src="full_delivery_url"> vorm wanneer de uitgever zijn volledige fragment-bevattende capaciteit levert.

Gevolgen waar een producent rekening mee MOET houden:

Privé (ingepakt) blijft de standaard; openbaar is een expliciete keuze van de maker per qub.


14. Testvectoren

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  = 4695446  (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, drand mainnet params §14.2)
  body         = "Hello, future."  (UTF-8, 14 bytes)
  title        = absent

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

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

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

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

Implementaties MOETEN identiek produceren body_hash en qub_id waarden voor deze invoer. Deze testvector MOET de eerste unit test zijn die wordt geschreven. De hierboven vermelde canonieke waarden zijn berekend door de referentie-implementatie en MOETEN bit-voor-bit overeenkomen. Historische pre-launch prototype-indelingen (geen live qubs waren afhankelijk van de eerste twee) gebruikten 92 bytes ervoor outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) en 100 bytes na toevoeging outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). De huidige 108-byte lay-out werd vervolgens toegevoegd drand_round en de QUB_ID_V2 domeinscheider. Een vroege 108-byte vector gebruikte de legacy ceil ronde kaartlegging (drand_round = 4695445) en geproduceerd 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—nog steeds geldig qub_id voor die ronde invoer, terwijl het bovenstaande voorbeeld de §4.3 actuele-ronde toewijzing volgt.

14.2 Ontgrendel-Ronde Mapping

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

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

drand_round = 4675286

Ronde 4675286 wordt gepubliceerd om 1595431050 + (4675286 - 1) * 30 = 1735689600—precies om unlock_at, nog nooit eerder. (De legacy pre-release ceil kaartgegeven 4675285, gepubliceerd op 1735689570—30 seconden te vroeg; verifiers accepteren die oude ronde volgens §4.3.)

14.3 Canonieke CBOR-rondreis

Implementaties MOETEN verifiëren dat serialize(parse(serialize(qub))) == serialize(qub) voor alle geldige invoeren. Dit is een eigenschapstest, geen enkele vector.

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)

De canonieke CBOR-bytes en SHA3-256 body_hash worden berekend door de referentie-implementatie. Implementaties MOETEN byte-identieke CBOR produceren voor deze invoer.

Implementaties MOETEN ook verifiëren dat serialize(parse(serialize(pact))) == serialize(pact) voor alle geldige PactTerms-invoeren (eigenschapstest).

14.5 Cross-language-vectoren voor de buitenste wrapper

De buitenste wrapper (§13) heeft een aparte canonieke fixture op crates/qub-core/tests/vectors/wrapper_v1.json. Elk geval legt een (key, nonce, qub_id, sealed_cbor)-tupel vast als ondoorzichtige hex-invoer en stelt een specifieke expected_wrapper_hex-uitvoer vast. Beide referentie-implementaties consumeren hetzelfde JSON-bestand:

De fixture koppelt momenteel drie laag-niveau wrapper-gevallen. Ze testen deterministisch OuterWrapper codering en AEAD-interoperabiliteit onafhankelijk van de §13.8 leveringsvorm-invariant; in het bijzonder, de historische naam basic-text-public en zijn binnenste visibility = 0x01 doen niet maak van de resulterende ingepakte bytes een conforme openbare levering. Een producent MOET nog steeds de openbare interne bytes onbedekt opslaan en alleen privé wikkelen (0x00) binnenste bytes.

Zaak Dekking
basic-text-public Historische laag-niveau fixture naam. Meest realistische kleinste SealedQub vorm, zonder optionele velden; test alleen wrapper-bytes en is geen conforme §13.8 opgeslagen levering.
with-recipient-pubkey SealedQub met recipient_pubkey instellen (gereserveerd toekomstpad). Oefent een andere interne CBOR-sleutelset uit; de verschillende fixture-inhoud levert onafhankelijk een andere op qub_id (recipient_pubkey hetzelf staat niet in het §4.1-voorafbeelding).
longer-body ~4 KiB-body — oefent met multi-byte CBOR-lengtevoorvoegsels zowel in de interne envelop als in de externe ciphertext.

Implementaties MOETEN byte-identieke expected_wrapper_hex produceren voor de vastgelegde invoeren. Het regenereren van de fixture vereist QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors en is voorbehouden aan opzettelijke formaatwijzigingen.


15. Governance van het cryptografieprofiel (toekomst)

Deze sectie is informatief voor v1 en wordt normatief zodra voor het eerst een tweede algoritme in een van de cryptografische primitieven van qub wordt opgenomen.

15.1 Huidige houding

Protocol v1 bindt precies één algoritme per primitief:

Verifiers coderen momenteel de sleutel- en handtekeninglengtes per actieve primitief hard. sig_alg en wrapper-versie-bytes zijn expliciete selectors, maar v1 voert geen in-band onderhandeling uit en staat alleen de bovenstaande actieve waarden toe.

15.2 Beoogde vorm

Wanneer een tweede algoritme in het protocol wordt opgenomen, wordt de verifier geconfigureerd voor een benoemd CryptoProfile (bijv. ExqubV1) dat de exacte set van toegestane waarden per primitief opsomt — sig_algs, drand-chains, wrapper-versies, inhoudstypen. Het profiel wordt vastgesteld op het verificatiemoment, nooit in-band onderhandeld. Elke waarde buiten het actieve profiel wordt geweigerd.

Dit garandeert dat het toevoegen van ML-DSA-87 of het activeren van Ed25519 bestaande verifier-configuraties niet met terugwerkende kracht kan verzwakken: een v1-verifier blijft een v1-verifier, zelfs nadat een v2-profiel is gepubliceerd.

15.3 Triggervoorwaarden

Promoveer §15 naar normatieve status zodra een van de volgende zaken wordt voorgesteld:

Tot dan is §15 een placeholder die de migratievorm vastlegt zodat toekomstige PR's tegen een bekend doel landen in plaats van het onderhandelingsoppervlak vanaf nul opnieuw te bediscussiëren.


16. Transparantielogboek en duurzaamheidsniveaus (Ontwerp — beoordeling afgerond)

Status. Deze sectie is geïmplementeerd (W5/UP-B1, Fasen 1–8), met de producent- en trust-root-omvang hier vermeld. De draadformaten, hashing en verifier-paden zijn live: de kern Merkle + canonieke CBOR-typen (qub-core), de TypeScript-spiegel + ANS-104 bundler (workers/api/src/crypto/), de enige schrijver LogDO + coördinaat-gekoppelde R2-knooppuntopslag, de /upload log-append poging, de dagelijkse anchor + bundler-drain crons, de GET /api/v1/qub/:tx_id/proof (inclusie) en GET /api/v1/log/consistency (RFC 9162) bewijs-eindpunten, het getypeerde inclusiebewijs dat wordt gedragen in de .qub bundle (§17.5), de ingebouwde ANS-104 ankerverificateur (tools/qub-verify), en de dubbele zelf-uitgegeven-koppen haak (§16.6). Een succesvolle /upload is altijd R2-duurzaam maar is alleen met hout bedekt wanneer LOG_DO is geconfigureerd en de inline-aanvulling slaagt; pas dan draagt zijn reactie log_seq, receipt, en anchor_status. Als RECEIPT_SK is afwezig of ongeldig, die ontvangst sig_b64url is leeg en levert geen niet-ontkenning. De huidige /seal en pact publicatiepaden plannen individuele Arweave-transacties maar voegen geen logblad toe. Geen enkele code voert momenteel de /upload de voorgestelde latere verzoening van commentaar na een append-fout. De W5-externe beoordeling is voltooid: §16.15 registreert ontwerpbeslissingen en lanceringsbeperkingen, maar die beperkingen breiden de eerder genoemde producentendekking niet uit. Drie vertrouwens-/implementatie-items blijven geblokkeerd: (a) de speciale anchor-portemonnee (ANCHOR_JWK; LogProfile.anchor_owner is nog steeds de [0xAB; 32] plaatsaanduiding); (b) de ontvangstondertekeningssleutel en overeenkomende public-key pin (RECEIPT_SK is optioneel en LogProfile.receipt_pubkey is momenteel leeg); en (c) de self-published-heads GitHub-repository + token (§16.6). Totdat de anchor/profile-pins zijn voorzien, rapporteert een zelfstandige verifier de bewijsstatus eerlijk in plaats van te beweren een volledig verankerde, gepinde verificatie te hebben. Het ontwerp is strikt additief en er is geen verandering aan de SealedQub / QubEnvelope draadformaat.

16.1 Rationale en duurzaamheidsniveaus

De huidige publicatiepaden scheiden erkenning van Arweave-bevestiging: ze halen een individuele transactie af en ondertekenen deze, slaan het artefact en de exacte indieningsstatus op in R2, en plaatsen het vervolgens asynchroon. Het transparantielogboek voegt een onafhankelijk verankerde ordeningslaag toe voor de subset van algemeen /upload verzoeken waarvan LogDO toevoegen is gelukt:

Dier Naam Garantie Wanneer
T1 R2-eerste synchrone bevestiging Duurzaamheidsvloer — verzegelde bytes en exacte publicatiestatus worden naar duurzaam opslagmedium geschreven voordat succes wordt geretourneerd. Geïmplementeerd over de huidige publicatiepaden.
T2 Verzamelde transparantie-log opname Alleen toevoegen, aantoonbaar onveranderbare toezegging + totale volgorde zodra opgenomen en verankerd. Huidige producent: succesvol LogDO voegt toe van /upload; reactie bevat de ontvangst-tuple. Niet universeel.
T3 Per-qub Arweave permanentie Een individuele Arweave-transactie voor de qub. Momenteel voorbereid voor elke geaccepteerde publicatie en asynchroon geplaatst; de exacte ondertekende transactie blijft in de uitwisbare outbox totdat deze wordt afgeleverd.

De niveaus beschrijven verschillende bewijs- en duurzaamheidseigenschappen, niet het huidige commerciële plan. De huidige code plant nog steeds een individuele Arweave-transactie voor elke geaccepteerde publicatie; het maakt T3 niet alleen beschikbaar als een betaalde upgrade. API-sleutel/accountquotumlimieten blijven aparte toepassingscontroles.

Duurzaamheid eerlijkheid. De T1-schrijfbewerking is synchroon, dus een succesvolle reactie zorgt voor duurzaamheid op applicatieniveau zonder te wachten op een Arweave-gateway. Het stelt op zichzelf geen onafhankelijke tijdstempel vast. Een bevestigde individuele transactie levert de bovengrens van de block-tijd. Voor een reactie die de volledige T2-ontvangst-tuple bevat, kan de volgende bevestigde anker de logbewijs leveren zoals hieronder beschreven. Als de tuple afwezig is, mag geen oppervlak impliceren dat deze qub al in het transparantielogboek staat. Anker- en publicatielatentie hebben geen numerieke SLA op protocolniveau.

16.2 LogLeaf-structuur (twee vastgelegde vormen)

Een logboekvermelding is een LogLeaf, gecodeerd als handgeschreven canoniek CBOR onder het §3.1-profiel (bepaalde lengte, geen tags, geen floats, kortste-vorm-integers, NFC-tekst, optionele velden weggelaten indien afwezig, sleutels geordend op gecodeerde-bytelengte oplopend, daarna bytewise). De §3.1-canonieke parse → her-coderen → vergelijken-bescherming wordt toegepast op het codeerpad vóór het hashen (niet alleen bij het decoderen), zodat twee implementaties het niet oneens kunnen zijn over de blad-bytes door een verschil in integer-breedte of sleutelvolgorde. Alle integers zijn u8 / u64 / i64; alle digests zijn 32-byte byte-strings (bstr[32]). Een opgeslagen Arweave-transactie-id is een ruwe 32-byte SHA-256-digest die als bstr[32] wordt gedragen, nooit een base64url-tekststring (komt overeen met §3.3).

Het blad heeft twee vormen geselecteerd door een kind byte, omdat het algemene uploadpad byte-blind is: POST /api/v1/upload behandelt opzettelijk beide geaccepteerde payloadvormen als ondoorzichtig en ontvangt qub_id en unlock_at alleen als onbetrouwbare cliëntverklaringen. Op het standaard privé-pad, body_hash, drand_round, created_at, en drand_chain_version zijn bovendien verborgen in de buitenste omslag §13, waarvan de sleutel de Werker nooit bezit. Het typesysteem definieert ook een bevestigd formaat voor een producent die afgeleid is body_hash / drand_round zichzelf. De huidige /seal route heeft die waarden maar roept niet aan LogDO, dus produceert productie momenteel alleen geasserteerde (0x02) verlaat van succesvolle algemene-upload toevoegingen. De splitsing houdt elke gecommitteerde waarde eerlijk zonder te doen alsof de bevestigde producent aangesloten is:

Sleutel Bijlage len Type Aanwezigheid Betekenis
seq vier u64 vereist Globaal op 0 gebaseerde bladindex; de positie waar het inclusiebewijs zich op vastlegt.
kind vijf u8 vereist 0x01 bewezen-capabel (gedefinieerd, momenteel niet uitgezonden) of 0x02 bevestigd (client-seal / byte-blinde upload).
ref vier bstr[32] vereist Bladverwijzing id. Getuigd → rauw qub_id. Bevestigd → de geblindeerd id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash zes bstr[32] vereist Inhoudsadres SHA3-256(stored_bytes) — de enige inhoudskoppeling die de Werknemer altijd eerlijk kan berekenen, op beide paden.
unlock_at tien i64 vereist Gekopieerd (bevestigd) of gesteld (aangegeven); gevalideerd > 0 voordat het het blad binnengaat.
received_at twaalf i64 vereist Werker wandklok bij R2-ack. Niet-evident (door de operator bevestigd; §16.6). Aanwezig voor zelfbeschrijving, nooit een bewijs. Geverifieerd > 0.
body_hash tien bstr[32] kind=0x01 alleen Overgeslagen op 0x02 — de Werknemer ontbreekt het onder §13.
drand_round twaalf u64 kind=0x01 alleen Overgeslagen op 0x02.

Een kind=0x02-blad legt opzettelijk noch body_hash noch drand_round vast: het attesteert de toezegging en ordening van een ondoorzichtige ciphertext op inhoudsadres chash, met de claim qub_id en unlock_at — niet de platte tekst of de ronde ervan. De platte-tekst-/ronde-poten voor een beweerde qub komen uit de bestaande §11-.qub-bundelverificatie, niet uit het logboek (§16.11). drand_chain_version zit niet in het blad (het zit binnen de wrapper op het standaardpad); de chain-granulariteit leeft op het anker (§16.7). Codeerdiscipline: weiger een geheel-nul ref of chash, en weiger een niet-positieve unlock_at / received_at, in lijn met de outcome_at > 0-sentinelbescherming in cbor.rs.

16.2.1 Blindering van privé-qubs

Het logboek mag niet de enumeratieorakel worden die de §13-buitenste wrapper bestaat te voorkomen (§13.1). Voor een privé- (ingepakte) qub legt het asserted-blad de geblindeerde identifier SHA3-256(qub_id ‖ log_blind_secret) vast, waarbij log_blind_secret een serverside-geheim is, en laat het body_hash weg. Een derde kan zo'n blad niet binden aan een specifieke qub_id; de houder van de qub, die de bezorglink heeft en daarom qub_id, kan de blindering herberekenen om zijn eigen inclusie te bevestigen. Een openbare qub (al opsombaar, die al de Visibility: public-Arweave-tag draagt per §13.8) legt de ruwe qub_id vast. Dit is de ene plek waar standalone-verifieerbaarheid opzettelijk wijkt voor een dragende privacy-invariant; de standalone-binding voor privé-qubs is chash (§16.9).

Bewaring van log_blind_secret (opgelost — §16.15 Q4). De blindering beschermt bladonkoppelbaarheid, niet de vertrouwelijkheid van de platte tekst (de §13-wrapper houdt dat onafhankelijk). Bij een compromittering van log_blind_secret herberekent de tegenstander voor elke qub_id die hij al bezit of kan reconstrueren (elke qub waarvan hij de bundel/URL heeft, plus elke laag-entropische of openbare qub_id) de blad-ref in één hash en koppelt deze — dit is directe koppeling van een bekende populatie, geen brute-force over een onbekende ruimte. Classificeer log_blind_secret als een correlatie-/Sybil-graad-geheim in dezelfde bewaringscategorie als andere serverside-geheimen, en roteer alleen voorwaarts (een rotatie blindeert toekomstige bladeren opnieuw; het kan al-verankerde bladeren niet met terugwerkende kracht ontkoppelen).

16.3 Blad- en knooppunt-hashing

RFC 6962 §2.1 domeingescheiden hashing met SHA-256 vervangen door SHA3-256:

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

De domeinprefix-bytes 0x02 (vermeldingsketen, §16.4) en 0x03 (STH-hash, §16.6) zijn gereserveerd en disjunct van deze. Het zijn enkele bytes en kunnen daarom niet botsen met de bestaande 10-byte ASCII-domeinscheiders (QUB_ID_V2, enz.). De boom is de RFC 6962 links-volle ongebalanceerde boom (elke interne splitsing bij de grootste macht van twee strikt kleiner dan het aantal subboom-bladeren), wat inclusie- en consistentiebewijzen één audit-padalgoritme laat delen. De referentiespecificatie draagt expliciete links/rechts-afleidingspseudocode en pint een niet-macht-van-twee (5-blad) testvector vast zodat het rechterrand-promotiegeval — dat een 4-blad-vector verbergt — wordt uitgeoefend.

16.4 Hash-ketening (intern)

De LogDO onderhoudt een interne vermeldingsketen alleen voor crash-consistentie. Deze wordt nooit gepubliceerd en is nooit verifier-gericht:

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

De gepubliceerde alleen-toevoegen-autoriteit is de cumulatieve Merkle-wortel + zijn anker (§16.5–16.6), nooit de ruwe volgorde waarin de operator toevallig bladeren serveert: de keten herberekent voor elke geserveerde volgorde, dus alleen de verankerde wortel pint de canonieke positie vast.

16.5 Cumulatieve Merkle-boom en batchen

Er is één steeds groeiende RFC 6962-boom over alle bladeren in seq-volgorde — niet geïsoleerde bomen per batch. (Een carry-blad-geketende constructie per batch werd afgewezen: het is geen echte prefixrelatie, dus de "consistentiebewijzen" ervan zijn onjuist.) De cumulatieve boom geeft echte RFC 9162-consistentiebewijzen en laat één enkel recent anker inclusie bewijzen voor elke oudere qub.

De LogDO Duurzaam object is de enkelvoudige schrijver (blockConcurrencyWhile, spiegelen QuotaDO / EntitlementDO) — toevoegen aan een gedeeld log is lezen-aanpassen-schrijven op gedeelde staat en moet dus via een DO gaan, nooit KV. Het cachet de rechterrand van de boom (O(log n) hashes) dus het sluiten van een batch is O(batch). A partij is de verzameling bladeren die aan elkaar verankerd zijn; de geïmplementeerde triggers zijn een tree_size vooruitgang van ten minste LOG_BATCH_MAX_LEAVES (standaard 4096), leeftijd bereikt de anker-cadans, of een expliciete administratieve/cron-force-close. root_i is de cumulatieve Merkle-boomhash over bladeren 0 .. tree_size_i.

16.6 Signed Tree Head via Arweave-anker

De Arweave-ankertransactie is de Signed Tree Head en vervangt een operatorhandtekening voor de boomhoofd zelf: het dagelijkse anker heeft geen qub-sleutel nodig omdat de Arweave-tx-owner de handtekening is. De moat-these houdt stand — het onveranderlijke substraat, niet een qub-gehouden geheim, is dragend voor de verankerde wortel.

Het logboekontwerp vereist één warme succesvolle-aanvulling ontvangstsleutel (§16.10), vastgemaakt in LogProfile en medeondertekend door anchor_owner. De huidige implementatie heeft die trust-root provisioning nog niet voltooid: RECEIPT_SK is optioneel, een ontbrekende/ongeldige sleutel levert op sig_b64url: "", en de gecompileerde LogProfile.receipt_pubkey is leeg. Zo'n ontvangstbewijs kan het bijgevoegde blad beschrijven maar is niet een niet-weerlegbaar ondertekend ontvangstbewijs. De sterkere ontwerpclaim geldt alleen nadat een verifier-vrijgave de overeenkomende publieke sleutel vastpint en de anker-eigenaar deze kruisondertekent. Een publicatie-reactie zonder de volledige receipt-tuple doet geen log-acceptatieclaim; een met een lege handtekening doet een append-position claim maar geen signature-verificatieclaim.

De SignedTreeHead is canoniek CBOR (sleutels op gecodeerde lengte): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (voorgaande sth_hash; genesis = 32 nul-bytes), log_id:bstr[32], first_seq:u64, anchored_at:i64. De hash ervan is sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

Gepinde vertrouwenswortel. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Een conforme verifier MOET anchor_tx.owner == LogProfile.anchor_owner vereisen, waarbij anchor_owner (en de publieke sleutel van de ontvangstsleutel) in qub_core is ingebakken als het LogProfile — naast de quicknet-constanten die al in DrandTimelockProvider::quicknet() zitten — en gedistribueerd met het verifier-binary. De verifier MOET ook de Arweave-tx data → tx_id-binding lokaal verifiëren in plaats van een gateway-/raw/-respons te vertrouwen. Dit sluit het schurken-wallet-equivocatiegat: "verankerd op Arweave" is betekenisloos totdat de verifier welke wallet vastpint.

Rotatie is een §15-governance-uitbreiding, geen hergebruik (opgelost — §16.15 Q3). Het profieloppervlak van §15.2 somt momenteel alleen sig_algs / drand-chains / wrapper-versies / inhoudstypen op, en de triggers van §15.3 noemen geen van deze — LogProfile / anchor_owner zit nog niet in het oppervlak van §15. Rotatiegovernance moet daarom worden gebouwd: §15.3 wordt uitgebreid (hieronder) om de LogProfile-trigger toe te voegen, en een rotatie is een ondertekende LogProfile-bump verzonden in een verifier-update. Een geplande rotatie draagt een uitgaande → inkomende kruishandtekening; een compromittering-gedreven rotatie kan dat niet (de uitgaande sleutel is dan precies niet-vertrouwd/onbeschikbaar) en valt terug op de §15-bestuurde bump, waarbij de prev-anker-fork-controle (hieronder) de schade in de tussentijd begrenst.

Dubbelzinnigheidsvenster (parameter voor eersteklas vertrouwen). Een blad is alleen bestand tegen dubbelzinnigheid zodra het bedekkingsanker Arweave- isbevestigd. Het raam is received_at → anchor confirmation (cadans + Arweave-finaliteit, zonder garantie van protocolvertraging). Voorafgaand aan het voorzien van een vertrouwensbasis, levert de huidige implementatie de operationele integriteit van qub plus de aanwezige niet-ondertekende toevoegingsmetadata; het levert de geplande garantie van non-repudiatie niet. Drie verantwoordingsstukken definiëren het voltooide ontwerp (het getuigenmodel is de resolutie van §16.15 Q2):

  1. Zegelontvangst (afhankelijk van bevoorrading) — het SCT-analoge signaal wordt teruggegeven wanneer het toevoegen van een log van een upload slaagt (§16.10). Het wordt pas niet-weerlegbaar wanneer sig_b64url is niet leeg en De overeenkomende publieke sleutel/anchor-eigenaarrelatie is vastgelegd in de verifier. De momenteel lege productieprofielpin kan dat oordeel niet ondersteunen. Deze controle is niet van toepassing op een weggelaten ontvangsttuple of een niet-ondertekende ontvangst.
  2. Gepubliceerde monitoringsmethodologie + vorige-ketenwandeling — de anker prev keten wordt bewandeld hoofd→genesis; een vork (twee ankers op één size met verschillend root, of een gebroken prev) is publiceerbaar bewijs van wangedrag. Detectie van dubbelzinnigheid is een expliciete operationele verplichting, geen stille aanname.
  3. Dubbele zelfgepubliceerde hoofden — elk nieuw hoofd {sth_hash, tree_size} is geplaatst op een speciaal qub-eigendom openbare, alleen-toevoegen GitHub-repository (het dragende, knip-waarneembare zelfpublicatiebeen), met een sociale post alleen als best-effort bevestiging. Een mislukte plaatsing MOET een pagina tonen (niet stil falen). Geïmplementeerd (Fase 8) als de publishHead haak aan het anker cron (workers/api/src/utils/heads-publish.ts): een PUT naar de inhouds-API zonder een sha is alleen-toevoegbaar (een 422 betekent dat de head al is gepubliceerd, nooit een overschrijving); opt-in / deploy-gated aan PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO} en inactief totdat de opslagplaats is voorzien. Een harde GitHub-foutpagina's via de health_alert kanaal en verhoogt een duurzame faalmetriek (m:tlog_publish_head_fail); de Arweave-anker zelf draait nooit terug bij een publicatiefout. "Not fail silent" wordt gegarandeerd door die duurzame maatstaf — waarop operators MOETEN dashboard-alerten — zelfs als de best-effort e-mailpagina niet kan worden geleverd. Twee eerlijke beperkingen volgen uit "anchor-on-advance" (de cron publiceert alleen wanneer de grootte toeneemt): een tijdelijke GitHub-storing laat een kloof in de gepubliceerde-koppenreeks voor die grootte — begrensd, niet stil (het wisselt van pagina), en omdat elke kop een superset-boom vastlegt, overbrugt een §16.9 consistentiebewijs de kloof; cruciaal is dat dat consistentiebewijs wordt berekend uit de gezaghebbende Arweave- verankerde boom, niet van het GitHub-oppervlak, dus een GitHub-gat verzwakt de verifieerbaarheid nooit. Een inhaalslag die de gaten bij gepubliceerde koppen opvult, is een uitgestelde verbetering.

Eerlijkheid gebonden (bindende beperking). Omdat qub beide geplande plaatsingsoppervlakken controleert, is dit zelfgepubliceerd, niet onafhankelijk waargenomen. Geen enkel product-, marketing- of juridisch oppervlak mag beweren dat het logboek "onafhankelijk waargenomen" is. Nadat de ontvangst/profiel/hoofdpoorten zijn voorzien, is de toegestane bewering dat dubbelzinnigheid is detecteerbaar en een succesvol ondertekende toevoeging laat een niet-afwijsbare ontvangstbewijs achter. Voordien is die claim niet beschikbaar. Een echte onafhankelijke derde-partij getuige wordt doorgeschoven naar een toekomstige §15 governance verhoging.

received_at is operator-beweerd en geen enkele claim mag erop leunen — het wordt nooit als bewijs of als geschilbevestiging op enig product-/juridisch/API-/bewijsweergave-oppervlak getoond. De Arweave-anker-bloktijd T is de enige vertrouwensloze tijdstempel (een bovengrens op "gelogd door"). Elke monitor-sanity-controle op received_at MOET vergelijken met T, niet met het operator-beheerde anchored_at-STH-veld; zo'n controle is alleen een bescherming tegen een klokfout van een eerlijke operator, niet een verantwoordingscontrole tegen een kwaadwillende operator (§16.15 Q5).

16.7 Ankertransactieformaat en -cadans

De AnchorBundle is het canonieke-CBOR-Arweave-transactielichaam, geschreven via de §16.8-bundler: ver:u8, sth:bstr (canonieke SignedTreeHead-bytes), prev_anchor:bstr (ruwe bytes van het voorgaande anker-tx-id; weggelaten bij genesis), chain_hash:tstr (de geldende drand-chain — quicknet), en de blad-CBOR-stroom van de batch in seq-volgorde zodat het anker zelfvoorzienend is: een monitor leidt root opnieuw af uit het lichaam met nul qub-afhankelijkheid. (Als de bladstroom groot wordt bij hoog volume, mag een toekomstige revisie alleen een bladbereik per referentie vastleggen; genoteerd, niet aangenomen in v1.)

Arweave-tags zijn opzettelijk opsombaar — het logboek is bedoeld om gevonden te worden, anders dan privé-qubs: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tags zijn niet-vertrouwde hints; het CBOR-lichaam is de enige autoriteit.

Cadans: dagelijks standaard, opnieuw bekeken met volume (de grootte-trigger verkort automatisch de effectieve cadans onder belasting). De huidige producent implementeert geen betaalde-zekering force-anchor-haak. De ankerportefeuille is toegewijd en van lage snelheid, gescheiden van de uploadportemonnee — het MOET de zijne zijn eigen JWK (een aparte sleutel, geen logische rol op de uploadportemonnee) zodat een compromis van de uploadportemonnee geen ankers kan vervalsen — met een streng dagbudget voor ankertransacties. De bewaarplicht wordt duidelijk vermeld: een smalle hotkey met een strakke stroomonderbreker en laag saldo, niet "cold" — een portemonnee die dagelijks automatisch ondertekent, kan niet cold zijn, en de specificatie doet alsof niet anders.

16.8 ANS-104-bundler

Een eigen ANS-104-DataItem-encoder en deep-hash-ondertekenaar, ongeveer 300 regels, alleen Web Crypto, nul npm-afhankelijkheden (beide Turbo-SDK's falen de npm ci --ignore-scripts-toeleveringsketenpoort). DataItem-byte-indeling:

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

Ondertekening is Arweave deepHash — een recursieve SHA-384-digest (Arweaves wire-vereiste, crypto.subtle.digest("SHA-384")) over ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — daarna RSA-PSS over de deep hash met de wallet-JWK via crypto.subtle; id = base64url(SHA-256(signature)). De SHA-384 hier wordt gequarantaineerd als een alleen-Arweave-wire-primitief, nooit een qub-vertrouwensprimitief (§15 legt de afscheiding vast; qub-vertrouwenshashing is overal SHA3-256).

Het ANS-104-codepad bedient de uitgestelde fallback/drain-machinerie en schrijft AnchorBundle DataItems. Het gewone publicatiepad maakt eerst een exacte ondertekende Arweave-transactie en bewaart de JSON ervan in een duurzaam uitvak; directe plaatsing is een latency-optimalisatie, en het drain-pad probeert dezelfde transactie opnieuw voordat de bundler-fallback wordt toegepast. Handtekeningregeling (opgelost — §16.15 V8): v1 ondertekent met RSA-PSS (handtekeningtype 1) het hergebruiken van het bestaande Arweave-portemonnee JWK-mechanisme (geen nieuwe langdurige sleutelbewaring, dienend aan de 'één geheim minder'-these); Ed25519 wordt uitgesteld naar het §15 PQ-migratiepad.

De handgerolde deep hash is de code met het hoogste risico en de laagste natuurlijke dekking in W5, dus de afscherming ervan is niet-onderhandelbaar (§16.15 Q8):

  1. De cross-language-fixture tlog_v1.json (Rust + TS, het §14.5-wrapper_v1.json-patroon) dekt deep-hash, DataItem-bytes + id, blad-hashes, een 5-blad-wortel + audit-pad, een STH-hash, een inclusiebewijs en een consistentiebewijs — in zowel de onderteken- als de verifieerrichting (de verifieerrichting is van belang omdat de lokale tx → tx_id-controle van §16.6 de deep hash in elke standalone-verifier trekt, niet alleen in de schrijver).
  2. Een eenmalige interop-rondreis door een referentie-ANS-104-bundler, geconsumeerd als alleen statische testdata — nooit een npm-runtime-afhankelijkheid (de alleen-Web-Crypto-/geen-install-scripts-houding blijft staan).
  3. Het deep-hash- + RSA-PSS-pad moet rondreizen door dezelfde crypto.subtle-primitieven die de productie gebruikt, zodat de eigen encoder byte-compatibel is.
  4. Een doorlopende post-bundle-acceptatiemonitor bevestigt dat elke anker-/terugval-DataItem daadwerkelijk Arweave-acceptatie bereikt, met een alarm + circuit breaker — omdat de deep hash ook de Arweave-onbeschikbaarheidsterugvalwachtrij bedient, dus een stille regressie zou die wachtrij vullen met netwerk-afgewezen items tijdens precies de storing die het bestaat te dekken.

16.9 Inclusie- en consistentiebewijzen

Beide zijn RFC 9162, SHA3-256, geserveerd als canoniek CBOR.

InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (het exacte blad-CBOR — de verifier herberekent leaf_hash zelf en vertrouwt nooit een aangeleverde hash), index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.

ConsistencyProof — GET /api/v1/log/consistency?first=<size_a>&second=<size_b>: ver:u8, first_size:u64, second_size:u64, first_root:bstr[32], second_root:bstr[32], nodes:[bstr[32]], first_anchor, second_anchor. Eén ondubbelzinnige sleutellijst, gepind door testvector.

Standalone-verificatie (geen qub-server, breidt §11 uit):

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

Bewijs-serverende opslag MOET coördinaat-gesleuteld zijn (opgelost — §16.15 Q7, blokkerende voorwaarde). Koud-blad-bewijsgeneratie is correctheid-neutraal alleen als het R2-auditmateriaal een persistente Merkle-knooppuntopslag is, gesleuteld op absolute boomcoördinaat (level, index) — geen knooppuntdelta's per batch. Met een coördinaat-gesleutelde opslag is elk (blad i, size N)-audit-pad een set van O(log N) directe R2-GETs met geen herberekening over batchgrenzen heen; met een batch-gesleutelde opslag is dat niet zo, wat het opslag-indelingsgat is dat deze oplossing sluit. De blad-lichamen zijn eveneens inhoudsadresseerbaar op seq. Een W5-testvector MOET een koud blad uit het genesis-tijdperk tegen een veel-latere wortel bewijzen met alleen R2 + Arweave terwijl de LogDO-opslag is gewist, zodat de reclamatie-veiligheidsclaim in §16.13 wordt ondersteund in plaats van beweerd. De O(log N) sequentiële R2-GETs horen alleen op het asynchrone bewijs-eindpunt — nooit op het verzegelings-hete-pad (§16.10) of een cron per tick.

16.10 R2-eerst-bevestigingsordening

De geïmplementeerde POST /api/v1/upload volgorde is:

  1. Poorten aan de voorkant (authenticatie, validatie, idempotentie sharding-sleutel) — ongewijzigd.
  2. Maak, tag en onderteken de exacte individuele Arweave-transactie. Dit leidt af tx_id Lokaal, hoewel het aanmaken van een transactie mogelijk beloning-/ankermetadata van een gateway ophaalt. Een voorbereidingsfout faalt nog steeds de aanvraag voordat er erkenning is.
  3. Synchroon schrijf het geselecteerde artefact op qub-cache/<tx_id> en bewaar de stabiele creatie-operatie/outbox-records. Dit zijn de duurzaamheid en retry-grondslag; fouten vóór afwikkeling geven 503 terug.
  4. Wanneer LOG_DO is geconfigureerd, synchronously proberen LogDO.append(leaf). De enkele schrijver wijst toe seq, breidt de ingangsketen uit en werkt de grens bij. De append-RPC doet alleen dat; batch close draait off-path op het alarm. Een append transport-/applicatiefout is momenteel faal-veilig: de reactie kan nog steeds slagen zonder log_seq, receipt, of anchor_status. Ondanks een implementatie-opmerking is er vandaag geen automatische latere logreconciliatie aangesloten.
  5. Verstuur de bevestiging. Inclusief { log_seq, anchor_status: "pending", receipt } alleen wanneer de append de volledige succesvolle tuple heeft geretourneerd. receipt.sig_b64url is leeg wanneer de ontvanger van het ontvangstbewijs niet beschikbaar is; cliënten MOETEN die waarde NIET als ondertekend of niet-weerlegbaar beschouwen. Afwezigheid van de tuple betekent alleen duurzame publicatie, niet acceptatie door het transparantielogboek.
  6. Gebruik één uitgestelde taak om de exact ondertekende transactie te plaatsen. Succes verwijdert de uitgaande berichtenbak; falen laat deze achter voor de gebonden drain cron en mag het reeds erkende niet wijzigen tx_id. Voorlopige metadata en andere best-effort bijlagen worden ook uitgesteld.

Latentiegrens. Het aanvraagpad omvat front-half autoriteit/quota werk, transactievoorbereiding/ondertekening, duurzame R2-schrijftaken, en (indien geconfigureerd) de LogDO poging. < 300 ms verschijnt in de ontwerpbeoordeling als een operationeel doel, niet als een protocolgarantie; de huidige stap van transactievoorbereiding kan een gateway-metadataopvraag uitvoeren. Vertragingalarmen en lanceerpoorten zijn operationele controles, geen bewijs dat beschikbaar is voor een verificateur.

16.11 Vertrouwensmodel — de precieze claim, gescoped op bladtype

Voor kind=0x01 (geattesteerd): "Deze inhoud — body die overeenkomt met body_hash, geïdentificeerd door qub_id — werd toegezegd aan qubs alleen-toevoegen-logboek op positie seq en bestond niet later dan Arweave-bloktijd T; deze was cryptografisch onleesbaar tot drand-ronde R = unlock_round(unlock_at)." Dit is het volledige {tlock-rondebinding + Merkle-inclusie + verankerde wortel}-drietal.

Voor kind=0x02 (beweerd, de standaard): "Een ondoorzichtige ciphertext met inhoudsadres chash, met de claim qub_id en unlock_at, werd toegezegd aan het alleen-toevoegen-logboek op positie seq en bestond niet later dan Arweave-bloktijd T." De ronde- en body-poten worden geleverd door de bestaande §11-.qub-bundelverificatie (qub_core::unlock), niet door het logboek; wat het logboek toevoegt boven een kale transactie per qub, is manipulatie-evidente ordening, een vertrouwensloze bovengrens-toezeggingstijd en equivocatiebestendigheid.

Beide claims sluiten uit, per §11: auteurschap zonder sig_alg ≥ 0x01, intentie, en timing onder anker-granulariteit. Geen van beide laat enige claim leunen op received_at.

Vorderingsplafond (bindende lanceringsbeperking — opgelost §16.15 V1). Voor een beweerde (kind=0x02) blad, de hierboven vermelde beperkte vordering is de plafond op wat elk product, marketing, voorwaarden of bewijs-weergave oppervlak kan beweren. Geen enkel oppervlak mag stellen of impliceren dat het logboek de inhoud of de unlock-ronde van een byte-blinde upload bewijst — het logboek bewijst ordening + een vertrouwensvrije bovenlimiet toezeggingstijd van een ondoorzichtige ciphertekst. Inhoud en rondebewijs komen uitsluitend van de bestaande §11 .qub-bundelverificatie, die log-onafhankelijk is. Een publicatie zonder succesvolle toevoeging/ontvangst heeft helemaal geen logclaim.

16.12 Versionering en W3-coördinatie

Er is nee SealedQub draadbult en daarom geen protocolversie-verhoging (§12.2): de log is een sidecar die zich commit aan bestaande velden en bytes, dus het gaat niet in de §12.3 protocol-versiegeschiedenis. W3's optioneel drand_chain_version is onaangetast en blijft de enige optie SealedQub veld. Het logboek introduceert in plaats daarvan zijn eigen onafhankelijke versieruimtes — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — die de §12.5 wrapper-versieonafhankelijkheid weerspiegelt (de wrapper draagt een versiebite die onafhankelijk is van de protocolversie, en de logversies volgen dezelfde scheiding).

Bewijslevering wordt standaard opgehaald, met een optionele meelift. Een bewijs kan op verzegelingstijd niet bestaan (het anker is nog niet geschreven), dus de .qub-bundel op verzegelingstijd blijft bewijsvrij. W7's verifier haalt GET …/proof één keer op, of reconstrueert in volledig-offline-modus het bewijs uit de openbare AnchorBundle via een Arweave-query op Log-Id. De .qub-bundel (W7) reserveert een optioneel inclusion_proof-lid — afwezig bij verzegeling, gevuld door een post-anker-herexport voor koude archivering — volgens hetzelfde "optioneel, standaard weggelaten, additief"-patroon als W3's drand_chain_version.

16.13 Bewaring

Bewaartermijnen voor de LogDO-open-staart, het R2-bewijs-serverende substraat, de anker-circuit-breaker-tellers en de bundler-terugvalwachtrij worden gespecificeerd in docs/DATA-RETENTION.md. Principe: de hete opslag per vermelding van het logboek (LogDO) is na verankering reclaimeerbaar; het auditmateriaal ervan — de coördinaat-gesleutelde (level, index)-Merkle-knooppuntopslag + de seq-geadresseerde blad-lichamen (§16.9) + de Arweave-ankers — is permanent. Het reclaimeren van een koud blad uit de DO maakt nooit een uitgegeven bewijs ongeldig, omdat een bewijs zich oplost tegen die permanente R2-knooppuntopslag en het Arweave-anker, niet de DO (en de §16.9-gewiste-DO-testvector bewijst het).

16.14 Testvectoren

W5 levert de cross-language-fixture tlog_v1.json (§16.8) plus uitgewerkte vectoren: een kind=0x01- en een kind=0x02-blad → leaf_hash; de 5-blad-cumulatieve wortel; één inclusiebewijs; één consistentiebewijs; één AnchorBundle; en één DataItem-id. Deze leven naast de §14.5-buitenste-wrapper-vectoren en worden uitgeoefend door zowel de Rust- (qub-core) als de TypeScript- (Worker) implementatie.

16.15 Beoordelingsbeslissingen (W5 — opgelost)

De W5-externe beoordeling (een tegenspellingsontwerpronde + goedkeuring door de eigenaar) is voltooid. Elke beslissing hieronder is genomen en weerspiegeld in de §16-tekst hierboven; de binding lanceerbeperkingen worden aan het einde herhaald. Implementatie kan onder hen doorgaan.

  1. Standaardpad (kind=0x02) blad eerlijkheid — OPGELOST. Verzend het tweebladige type gesplitst zoals gespecificeerd: kind=0x02 maakt zich nergens aan schuldig body_hash noch drand_round. Nee *_body_hash veld op het byte-blinde pad (het zou het meest leesbare valse "gecontroleerde" signaal voor integrators zijn en is een gemak dat §11 al biedt vanuit de bundel). Do niet vereis server-seal voor log-attested qubs (dat zou plaintext door de Worker dwingen en de crypto-shredding gracht vernietigen). Elke zelfbeschrijvende kortsluiting hoort in de .qub bundel / bewijs-envelop als een door de verifier-opnieuw berekend veld, nooit een bladveld. Door eigenaar bevestigd claimplafond: §16.11.
  2. Dubbelzinnigheid / weglating verantwoordelijkheid — ONTWERP OPGELOST, VOORZIENING ONVOLLEDIG. Het ontwerp vereist dat de verzegelingsontvangstsleutel wordt vastgezet LogProfile en medeondertekend door anchor_owner, plus monitormethodologie, prev-chain walk en dubbele zelfgepubliceerde koppen. Het samengestelde profiel en de implementatiehooks zijn nog steeds tijdelijke aanduidingen/optioneel zoals gespecificeerd in §16.6, dus de sterkere detecteerbare + bevestigd claim is momenteel niet geldig totdat die poorten sluiten. Het mag nooit worden gepromoot als onafhankelijk getuigd. Een echte derdepartijgetuige wordt doorgeschoven naar een §15 governance-verhoging.
  3. Vastgezette anchor-eigenaar vertrouwenswortel + rotatie — OPGELOST. Neem de LogProfile pin (§16.6); de verifier controleert anchor_tx.owner == anchor_owner en controleert de tx-gegevens → tx_id-koppeling lokaal. Rotatiebeheer is een §15 uitbreiding om te bouwen (§15.3-trigger toegevoegd), geen hergebruik; geplande rotaties kruisverwijzen, compromis-gedreven rotaties vallen terug op de §15-verhoging met de vorkcontrole die de schade beperkt.
  4. Privé-qub-blad verblinding — OPGELOST. Blijf verblinden voor privé qubs (ref = SHA3-256(qub_id ‖ log_blind_secret)), rauw qub_id voor openbare qubs (al §16.2.1), chash als de zelfstandige stropdas. log_blind_secret is een correlatie/Sybil-graad geheim, alleen vooruit roteren (§16.2.1).
  5. received_at — BESLOTEN. Houd het in het blad, toegewijd maar expliciet niet-als-bewijs; nooit als bewijs of ter ondersteuning van een geschil op enig oppervlak naar voren gebracht. Elke monitor sanity check vergelijkt met de Arweave blocktijd T, niet de door de operator gecontroleerde anchored_at (§16.6).
  6. Gelaagde bewijsbare timing — ONTWERPRESOLUTIE, GEEN HUIDIGE ROUTING. Het beoordeelde ontwerp wijst anker-blok timing toe aan het gebatchte niveau en exacte-uur bewijs aan betaalde T3, zonder numerieke SLA voor het eerstgenoemde. De huidige routes hebben dat commerciële onderscheid niet doorgevoerd: ze plannen een individuele transactie voor elke geaccepteerde publicatie, en de logdekking blijft voorwaardelijk zoals vermeld in §16.1/§16.10. Producttekst moet de implementatie beschrijven, niet deze toekomstige niveau-splitsing.
  7. Cumulatieve boom over Werknemers — OPGELOST. Enkele cumulatieve RFC 9162-boom + frontier-gecachete single-writer LogDO (comfortabele marge ten opzichte van het ~1k schrijfacties/sec DO-plafond; stel Merkle-van-schijf-wortels sharding uit tot vlakbij dit punt). De coördinaten-sleutel (level, index) R2 knoopopslag + gewiste-DO cold-leaf testvector zijn geïmplementeerd (§16.9). < 300 ms blijft een ontwerp-/operationeel doel, geen protocolbelofte (§16.10).
  8. ANS-104 handtekeningenschema + diepe-hash — OPGELOST. RSA-PSS (handtekeningtype 1, met hergebruik van de speciale anchor-wallet JWK); Ed25519 uitgesteld naar het §15 PQ-pad. De zelfgemaakte SHA-384 diepe hash is beperkt tot de cross-impl fixture in beide richtingen, een alleen-statisch reference-bundler interoperabiliteitscontrole, het gedeelde-crypto.subtle retourvlucht, en de post-bundel Arweave-acceptatemonitor (§16.8).

Binding lanceerbeperkingen (uitvoeren in implementatie + product/juridische beoordeling):


17. Draagbare verificatiebundel (.qub)

Status. Deze sectie is geïmplementeerd (W7 / UP-C2): qub_core::export produceert en analyseert het pakket, en tools/qub-verify is een openbare, zelfstandige CLI die er één offline verifieert. §11 en §16.9 verwijzen al naar "de" .qub bundel als de eenheid die een zelfstandige verifier verbruikt; deze sectie specificeert de bytes en de verificatiestap. Het is strikt aanvullend — de bundel verpakt bestaande §11-invoer en verandert geen on-chain draadformaat.

17.1 Doel

§11 stelt dat elke derde partij het cryptografische artefact van een qub kan verifiëren zonder medewerking van qub. De .qub bundel maakt die verificatie draagbaar en offline: het verpakt de verzegelde CBOR en de drand-rondehandtekening die deze ontgrendelt in één zelfvoorzienend artefact, zodat een ontvanger de inhoudsintegreteit, rondebinding en eventuele auteurschapshandtekeningen kan verifiëren met helemaal geen netwerkoproep (geen opslagopvraging, geen live drand-verzoek, geen qub-API). Een bundel alleen bewijst niet wanneer de gecodeerde tekst is gemaakt; een onafhankelijk geverifieerde opslagtransactie of verankerd logbewijs levert die aparte bestaans-tijdaanduiding (§11, §17.5).

17.2 Bundelindeling

A QubBundle is handgeschreven canonieke CBOR onder het §3.1-profiel (definite-length, geen tags, geen floats, kortste-vorm integers, NFC-tekst, optionele velden weggelaten wanneer afwezig, sleutels geordend op oplopende gecodeerde-bytes lengte en daarna byte-voor-byte). De drie 15-karakter sleutels volgorde d < i < s. Een rauwe .qub bestand is precies deze bytes; voor URL- of copy-paste-overdracht zijn dezelfde bytes base64url (zonder opvulling).

Sleutel Bijlage len Type Aanwezigheid Betekenis
version acht u8 vereist Bundelformaatversie (0x01).
sealed_at tien i64 optioneel Door de maker opgegeven zegeltijd (Unix-seconden); zelfbeschrijvend, niet-bewijzend.
drand_round twaalf u64 vereist De ronde waaraan de qub is vergrendeld. Een projectie van de ingebedde verzegelde qub.
arweave_tx_id veertien tstr vereist De transactie-id waaronder de verzegelde bytes zijn opgeslagen (provenance-pointer).
drand_chain_id vijftien tstr vereist De drand-keten (hex). Een projectie van de ingesloten verzegelde qub.
drand_signature zestien bstr vereist De drand-bakenhandtekening voor drand_round — de waarde die de ciphertext ontgrendelt.
inclusion_proof zestien bstr optioneel De §16 transparantie-log Merkle-inclusieproef, nadat een verankeringsbewijs beschikbaar is (§17.5).
sealed_qub_cbor zestien bstr vereist Het binnenste SealedQubCbor bytes (na §13-ontwinden), d.w.z. de §11-verificatie-invoer.

drand_round en drand_chain_id zijn gemakshalve projecties van sealed_qub_cbor, zodat tooling ze kan lezen zonder de interne CBOR te parseren. Ze worden afgeleid bij constructie en opnieuw gecontroleerd bij decode tegen de geparseerde verzegelde qub; een bundel waarvan het topniveauveld niet overeenkomt met de payload wordt afgewezen. De discipline van de encoder weerspiegelt de rest van het draadformaat: een lege afwijzen drand_signature of arweave_tx_id, en bond elk veld met variabele lengte.

17.3 Wat de ingebedde drand-handtekening bewijst

Het pakket draagt de drand-handtekening in plaats van te vereisen dat de verificateur deze ophaalt. Timelock-decryptie (tlock over de drand-keten, §8) kan alleen slagen met de oprecht bakenhandtekening voor de gebonden ronde—een waarde die de keten slechts eenmaal publiceert zodra die ronde verloopt, en die een geldige BLS-handtekening is onder de publieke sleutel van de keten. Een vervalste of verkeerde handtekening faalt bij BLS-verificatie of IBE/AEAD-decryptie. Een bundel die ontsleutelt, bewijst daarom: de ciphertext is gebonden aan ronde R, en ronde R is verstreken. De verificateur spelt de ketting vast (DrandTimelockProvider::quicknet()) en past de §11-rondgebondenheidscontrole toe, zodat een bundel geen ronde kan claimen waaraan zijn ciphertext niet is gebonden.

Dit is een vrijgave-conditie bewijs, geen aanmaakdatum. Na ronde R heeft verstreken, iedereen kan een nieuwe ciphertext voor R maken en het reeds openbare verpakken handtekening. Daarom MAG het bundel alleen NIET worden beschreven als bewijs dat de cijfertekst of inhoud bestond vóór R, ervoor unlock_at, of voor een gebeurtenis.

17.4 Offline verificatie-stappenplan

qub-verify <file.qub> voert de standaard §11-procedure volledig uit vanaf het dossier, en stuurt qub_core::unlock::unlock met een gespeld DrandTimelockProvider:

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

De CLI sluit af 0 (geverifieerd), 1 (verificatie mislukt — nog steeds vergrendeld, body-hash komt niet overeen, gebroken ronde/ketenbinding, of een handtekening die niet kan worden geverifieerd), of 2 (gebruik / verkeerd gevormde bundel). A --json rapport draagt hetzelfde oordeel voor automatisering. Omdat het pakket zelfvoorzienend is, de verifier-crate (qub-core) en de CLI (qub-verify) zijn de enige software die een derde partij nodig heeft; beide zijn openbaar en hergebruiken het bestaande verificatiepad van het protocol — geen speciale cryptografie.

17.5 Relatie tot het transparantielogboek

inclusion_proof is een optionele plaats voor het §16 Merkle-inclusiebewijs. Alleen bundelverificatie (§17.4) is compleet voor integriteit, ronde binding / ronde verstreken, en optionele auteurschap, maar heeft opzettelijk geen zelfstandig getimestampd bestaansbewijs. Een gevuld, volledig anker-geverifieerd inclusion_proof voegt de blad-soort-specifieke verplichting en bovengrens tijd van §16.11 toe zonder de bundel formaatversie te wijzigen. Een ontbrekend bewijs betekent alleen "geen bewijs inbegrepen"—niet "ongeldig" en niet noodzakelijk "niet verankerd".

In de referentie-implementatie is de slot nu getypt: qub_core::export::QubBundle::inclusion_proof_typed() geeft een Option<InclusionProof> het volledige §16.9-structuur dragend (blad, auditpad, verankerde wortel, en AnchorRef) via hetzelfde ondoorzichtige CBOR-veld — geen verhoging van de bundelversie. De zelfstandige qub-verify CLI gebruikt het via zijn --anchor leg, en — totdat de ankerportemonnee is voorzien (§16, Status) — rapporteert een ingevulde-maar-plaatsvervangende-eigenaarbewijs als alleen-inclusie in plaats van volledig anker-geverifieerd.