qub Protokolspecifikation
qub er en protokol til kryptografiske tidsmæssige forpligtelser: et system til at forsegle ord til en fremtidig dato og bevise, når den dato indtræffer, præcis hvad der blev sagt og hvornår.
Tre primitiver får det til at fungere. drand er et decentraliseret tilfældigheds-beacon — afsløringsdatoen håndhæves af fysikken, ikke af nogen parts velvilje. Permanent offentligt lager er et manipulationssikret offentligt lager — ingen part kan redigere eller slette en qub, når den først er forseglet. ML-DSA-65 er en post-kvante digital signatur — hver qub er bundet til et nøglepar, hvis hemmelighed aldrig forlader forfatterens enhed.
Tilsammen producerer disse primitiver en erklæring, der er tidslåst, tampersikker og tilskrivelig — en kvittering, hvis værdi vokser i takt med, at verdens evne til at fabrikere fortiden forbedres.
Resten af dette dokument er den normative specifikation, der kræves til interoperable implementeringer.
qub-protokolspecifikation
| Felt | Værdi |
|---|---|
| Version | 1.0 (protokolversion 0x01, ydre wrapper-version 0x01) |
| Dato | 2026-05-01 |
| Status | Udkast |
| Gennemgået til og med | 2026-05-01 |
Dette dokument er den normative protokolspecifikation for qub-systemet til tidsmæssige forpligtelser. Det definerer datastrukturer, serialiseringsregler, udledningsformler og verifikationsprocedurer, der kræves til interoperable implementeringer.
Anvendelsesområde: protokollaget er bevidst sprogneutralt — qub-bodyen er uigennemsigtig klartekst / markdown / pact-bytes, og lokalitetsbevidst rendering er læserens ansvar (qub.social-webappen, <qub-embed>-iframen, MCP-klienter osv.).
1. Notation og konventioner
| Notation | Betydning |
|---|---|
u8, u64, i64 |
Heltal uden/med fortegn af angivet bitbredde |
[u8; N] |
Byte-array med fast længde på N bytes |
Vec<u8> |
Byte-array med variabel længde |
Option<T> |
Værdi af typen T, eller fraværende |
String |
UTF-8-tekststreng, NFC-normaliseret |
| ` | |
SHA3-256(x) |
NIST SHA3-256-hash af bytestrengen x (FIPS 202) |
ceil(x) |
Loftfunktion: mindste heltal ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | Mest betydende byte først |
Alle heltal i preimage-konstruktioner kodes som big-endian-byte-arrays med fast bredde (i64 → 8 bytes, u8 → 1 byte), medmindre andet er angivet.
Alle tidsstempler er Unix-sekunder i UTC.
2. Datastrukturer
2.1 ComposeQub (skaberens in-memory-tilstand)
Ikke serialiseret til CBOR. Ikke skrevet til permanent lager. Lokalt i skaberens 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, // 0x01 = public (only value in MVP)
content_type: u8, // 0x01 = text (only value in MVP)
plaintext: Vec<u8>, // UTF-8 qub body
sender_label: Option<String>, // Decorative display name; not authenticated
status: DraftStatus, // Composing | Sealed | Uploaded | Failed
}
2.2 QubEnvelope (dekrypteret payload)
Serialiseret med kanonisk CBOR (§3). Krypteret inde i SealedQub. Dette er strukturen, der beviser indholdsintegritet efter dekryptering.
QubEnvelope {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Derived (see §4.1)
content_type: u8, // Content type registry (see §6)
created_at: i64, // Unix seconds UTC
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
sender_label: Option<String>, // Decorative; not authenticated in MVP
reply_to: Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
body: Vec<u8>, // Content payload (UTF-8 for text, CBOR for pact)
body_hash: [u8; 32], // SHA3-256(body) (see §4.2)
sig_alg: u8, // Signature algorithm (see §9.2)
author_signature: Option<Vec<u8>>, // Set when sig_alg != 0x00
author_pubkey: Option<Vec<u8>>, // Set when sig_alg != 0x00
cosigner_pubkey: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
cosigner_signature: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}
Baseline (usigneret tekst-qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, alle Option-felter fraværende.
Andre v1-konfigurationer: content_type = 0x03 (pact-body, se §6.1); sig_alg = 0x01 (ML-DSA-65) med author_signature og author_pubkey til stede (se §9.3); cosigner_pubkey og cosigner_signature til stede sammen for medunderskrevne pagter (se §9.7); reply_to sat til den overordnede qubs qub_id for svarkæde-qubs (se §9.3 for konsekvenserne af signaturomfanget).
2.3 SealedQub (kanonisk wire-format)
Serialiseret med kanonisk CBOR (§3). Skrevet til permanent lager. Dette er artefaktet on-chain.
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x01 = public; v1 viewers reject other values
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — surfaced on the verdict-watch CTA
// before reveal; mirrors QubEnvelope.outcome_at;
// bound to qub_id via the §4.1 preimage.
drand_chain_id: String, // drand chain hash (hex string)
drand_round: u64, // Target drand round number
tlock_ciphertext: Vec<u8>, // tlock-encrypted QubEnvelope CBOR bytes
recipient_pubkey: Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
// but not interpreted by the v1 reference viewer
title: Option<String>, // Plaintext title surfaced on the viewer
// countdown before reveal. Bound to qub_id
// via title_hash (§4.1). 1..=100 NFC code
// points, no control characters.
}
2.4 RevealedQub (læserens applikationstilstand)
Ikke serialiseret til CBOR. Lokalt i læserens app. Konstrueret efter succesfuld dekryptering og verifikation.
RevealedQub {
qub_id: [u8; 32],
arweave_tx_id: String,
visibility: u8,
content_type: u8,
created_at: i64,
unlock_at: i64,
outcome_at: Option<i64>, // V1.1 — videreført fra QubEnvelope.outcome_at / SealedQub.outcome_at; driver verdict-watch-blokken på afsløringssiden (verdict-uplift-plan §5.1)
drand_chain_id: String,
drand_round: u64,
sender_label: Option<String>,
title: Option<String>, // Carried forward from SealedQub.title
reply_to: Option<[u8; 32]>,
body: Vec<u8>,
body_hash: [u8; 32],
body_hash_verified: bool,
author_signature: Option<Vec<u8>>,
author_pubkey: Option<Vec<u8>>,
signature_verified: Option<bool>,
cosigner_pubkey: Option<Vec<u8>>,
cosigner_signature: Option<Vec<u8>>,
cosigner_verified: Option<bool>,
}
3. Kanonisk CBOR-profil
Al serialisering af SealedQub og QubEnvelope MUST overholde denne profil. To implementeringer, der får den samme logiske struktur, MUST producere identiske bytes.
3.1 Kodningsregler
| Regel | Specifikation |
|---|---|
| Standard | RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements) |
| Map-nøglerækkefølge | Sorteret efter kodet bytelængde først (kortere før længere), derefter leksikografisk (byte for byte for kodninger med samme længde) |
| Heltalskodning | Korteste form: 0–23 i startbyte; 24–255 i 2 bytes; 256–65535 i 3 bytes; osv. |
| Længdekodning | Kun definerede længder. Ingen arrays, maps, byte-strenge eller tekststrenge af ubestemt længde (additional info = 31 er forbudt). |
| Tags | Ingen CBOR-tags (major type 6 er forbudt). |
| Flydende komma | Ingen floats (major type 7-værdierne 0xF9–0xFB er forbudt). |
| Tekststrenge | UTF-8-kodede, NFC-normaliserede (Unicode Normalization Form C). |
| Byte-strenge | Rå bytes. Ingen base64-kodning på CBOR-laget. |
| Duplikerede nøgler | Afvis med fejl. Parsere MUST NOT stiltiende acceptere duplikerede map-nøgler. |
| Ukendte nøgler | Afvis med fejl. Parsere MUST NOT tolerere map-nøgler uden for typens kanoniske nøglesæt — to forskellige kanoniske byte-strenge må aldrig afkodes til samme værdi (encode(decode(x)) == x), og for signerede payloads ville en ekstra nøgle være skjult indhold, som begge signaturer forpligter sig til. Skemaudvikling sker gennem version, aldrig gennem ekstra nøgler. |
| Simple værdier | Kun true (0xF5), false (0xF4) og null (0xF6) er tilladt. |
| Valgfrie felter | Fraværende valgfrie felter udelades helt fra CBOR-mappet (kodes ikke som null). Tilstedeværende valgfrie felter inkluderes i sorteret nøglerækkefølge. |
3.2 Verificerede kanoniske nøglerækkefølger
Disse nøglerækkefølger er normative. Implementeringer MUST udsende nøgler i præcis denne rækkefølge. Debug-assertions SHOULD verificere rækkefølgen i ikke-release-builds.
QubEnvelope (version 0x01, usigneret, alle valgfrie felter fraværende):
"body" (5 encoded bytes)
"qub_id" (7 encoded bytes)
"sig_alg" (8 encoded bytes)
"version" (8 encoded bytes)
"reply_to" (9 encoded bytes) ← only if present (reply chains)
"body_hash" (10 encoded bytes)
"unlock_at" (10 encoded bytes)
"created_at" (11 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (V1.1 verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
Udledning af QubEnvelope-nøglerækkefølge: hver nøgle er en CBOR-tekststreng. Kodet længde = 1-byte-header + strenglængde (for strenge under 24 bytes). Sortér først efter samlet kodet længde, derefter leksikografisk for nøgler med samme længde.
SealedQub (version 0x01, offentlig, ingen modtager):
"title" (6 encoded bytes) ← only if present
"qub_id" (7 encoded bytes)
"version" (8 encoded bytes)
"unlock_at" (10 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (V1.1 verdict mechanic)
"visibility" (11 encoded bytes)
"drand_round" (12 encoded bytes)
"drand_chain_id" (15 encoded bytes)
"recipient_pubkey" (17 encoded bytes) ← only if present
"tlock_ciphertext" (17 encoded bytes)
PactTerms (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 (række i terms-arrayet):
"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 Byte-kodningsreference
| Type | CBOR-kodning | Eksempel |
|---|---|---|
| SHA3-256-hash (32 bytes) | 0x58 0x20 + 32 bytes |
body_hash, qub_id |
| Tidsstempler (i64) | Major type 0 (positiv) eller 1 (negativ), korteste kodning | Unix-sekunder |
| Version (u8, værdi 1) | 0x01 (enkelt byte) |
|
| Content type (u8, værdi 1) | 0x01 (enkelt byte) |
|
| sig_alg (u8, værdi 0) | 0x00 (enkelt byte) |
|
| ML-DSA-65-signatur (3.309 bytes) | 0x59 0x0C 0xED + 3.309 bytes |
author_signature, cosigner_signature |
| ML-DSA-65 offentlig nøgle (1.952 bytes) | 0x59 0x07 0xA0 + 1.952 bytes |
author_pubkey, cosigner_pubkey |
4. Normative udledninger
4.1 qub_id
qub_id identificerer entydigt en qub og binder QubEnvelope til SealedQub. Den udledes deterministisk fra envelope-indholdet.
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
Kodning af domæneseparator: Strengen "QUB_ID_V2" er 9 ASCII-bytes. En enkelt 0x00-padding-byte tilføjes for at nå 10 bytes til justering. Implementeringer MUST bruge præcis disse 10 bytes: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
Kodning af outcome_at: V1.1 udvidede preimage'et fra 92 til 100 bytes for at folde det valgfrie outcome_at-felt ind i bindingen. Fraværende outcome_at kodes som 8 nul-bytes; protokollens validatorer afviser outcome_at <= 0 overalt, så denne sentinel ikke kan kollidere med en legitim værdi. Se §3.2 (wire-format) og den in-tree tasks/verdict-uplift-plan.md for den verdict-mekanik, der motiverer dette felt.
Kodning af drand_round: V1.2 udvidede preimage'et fra 100 til 108 bytes for at folde drand_round (den mål-drand-runde, §4.3) ind i bindingen og hævede domæneseparatoren til QUB_ID_V2. Dette binder timelock-runden ind i qub-identiteten: en gateway kan ikke ombinde ciphertext'en til en anden (f.eks. allerede passeret) runde end den, som det viste unlock_at indebærer. Oplåsningsproceduren (§8) verificerer derudover, at den runde, der er indbagt i tlock-ciphertext-stanzaen, matcher unlock_round(unlock_at), så det viste oplåsningstidspunkt påviseligt er den runde, der gater dekrypteringen.
Egenskaber:
- Ændring af et hvilket som helst felt i QubEnvelope (body, tidsstempler, content type, version) producerer et andet qub_id.
- qub_id beregnes før kryptering. Både QubEnvelope og SealedQub bærer samme qub_id. Læseren verificerer, at de matcher efter dekryptering.
- qub_id afhænger ikke af
sender_label,author_signatureellerauthor_pubkey. Det betyder, at det samme indhold forseglet på samme tidspunkt producerer samme qub_id, uanset hvem der signerer det. - Ændring af SealedQub-
title(med alt andet fast) ændrerqub_idviatitle_hash. En gateway kan derfor ikke udskifte den klartekst-titel, der vises på nedtællingen, uden at ugyldiggøre qub-identiteten. - Ændring af SealedQub-
outcome_at(med alt andet fast) ændrerqub_idvia preimage'et. En gateway kan ikke udskifte den verdict-on-dato før afsløring, der vises på nedtællingen, uden at ugyldiggøre qub-identiteten. - Ændring af
drand_round(med alt andet fast) ændrerqub_idvia preimage'et. En gateway kan ikke ombinde timelock-ciphertext'en til en anden runde uden at ugyldiggøre qub-identiteten; kombineret med §8-kontrollen af stanza-runden ved oplåsning er det visteunlock_atden runde, der faktisk gater dekrypteringen.
4.2 body_hash
body_hash = SHA3-256(body)
Hvor body er den rå Vec<u8>-indholds-payload. For tekst-qubs er dette den UTF-8-kodede 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
Hvor title er den valgfrie klartekst-titel, der vises på læserens nedtælling før afsløring (se §3.2). NFC-normalisering køres på hash-tidspunktet, så digesten er stabil på tværs af visuelt ækvivalente kodepunktsekvenser. Sentinel-værdien med alle nuller er reserveret til det fraværende tilfælde; en tom streng afvises ved den kanoniske CBOR-grænse som en ikke-kanonisk kodning af "fraværende" (den kanoniske kodning udelader feltet helt).
4.3 Mapping fra oplåsning til runde
drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| Parameter | Kilde | Eksempel |
|---|---|---|
unlock_at |
Brugervalgte Unix-sekunder 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 |
ceil()-operationen vælger den første drand-runde, hvis afsløringstid er ≥ unlock_at. Dette sikrer, at qub'en ikke bliver dekrypterbar før det valgte oplåsningstidspunkt.
Grænsetilfælde: hvis (unlock_at - chain_genesis_time) er præcis deleligt med chain_period_seconds, er resultatet netop denne runde — qub'en låses op præcis ved den rundes afsløringstid.
Validering: unlock_at MUST være i fremtiden på forseglingstidspunktet. unlock_at MUST NOT være mere end 10 år fra created_at (for at begrænse risikoen ved langtidshorisont-drand-afhængighed; UI'et SHOULD advare for oplåsningsdatoer ud over 2 år).
5. Wire-format-newtypes
Wire-format-newtypes giver compile-time-sikkerhed mod at forveksle CBOR-bytes med JSON, rå klartekst eller andre byte-kodninger.
| Type | Indeholder | Produceret af | Forbrugt af |
|---|---|---|---|
SealedQubCbor |
Kanonisk CBOR af SealedQub | serialize_sealed_qub() |
Permanent-lager-upload, læser-fetch |
QubEnvelopeCbor |
Kanonisk CBOR af QubEnvelope | serialize_qub_envelope() |
tlock encrypt-input, tlock decrypt-output |
5.1 Konstruktionsregler
// 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 Validering ved konstruktion
from_encoded() SHOULD validere, at input begynder med en gyldig CBOR-map-header. Fuld strukturel validering sker ved parse-tid, ikke konstruktionstid, for at undgå dobbelt-parsing.
6. Content-Type-register
| Værdi | Type | Maks. body-størrelse | Noter |
|---|---|---|---|
0x00 |
Reserveret (ugyldig) | — | MUST NOT bruges |
0x01 |
Almindelig tekst (UTF-8, begrænset Markdown) | 50 KB betalt / 10 KB gratis | Se §10 for rendering-regler. Opdelingen gratis / betalt håndhæves af upload-tjenesten; det hårde protokol-loft er 50 KB. |
0x02 |
Reserveret (fremtidig) | — | Allokeret til en fremtidig content type; ikke gyldig i v1. Læsere MUST afvise pr. reglen nedenfor. |
0x03 |
Pagt (bilateral aftale, CBOR-body) | 100 KB | Body er kanonisk CBOR PactTerms (§6.1). Medunderskriver-signering pr. §9.7. |
0x04 |
Dom (skaberens selvbedømmelse, CBOR-body) | 8 KB | Body er kanonisk CBOR VerdictBody (§6.2). Udsendes kun af system-siden verdict-intent. Den overordnede relation ligger på Parent-Tx-Id-Arweave-tagget, ikke på body'en. Se verdict-uplift-plan §3.4. |
Læsere MUST afvise ukendte content types med en tydelig brugersynlig fejl. Læsere MUST NOT forsøge at rendere ukendte typer som tekst.
6.1 Pact-body (content_type = 0x03)
En pact-body er den kanoniske CBOR-kodning af en PactTerms-værdi:
PactTerms {
pact_version: u8, // 0x01 for structured/v1
title: String, // ≤ 200 bytes, NFC
terms: Vec<PactTerm>, // ≤ 20 rows
party_a: PartyIdentifier, // initiator
party_b: PartyIdentifier, // counter-signer
notes: Option<String>, // ≤ 5,000 bytes, NFC; absent key if none
}
PactTerm { key: String (≤ 100), value: String (≤ 2,000) } // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }
Kanoniske CBOR-nøglerækkefølger for alle tre maps findes i §3.2. Den samlede serialiserede pact-CBOR MUST NOT overstige 100 KB (matcher §6).
Skema-diskriminator. Den første række i terms for en structured/v1-pagt MUST være { key: "pact_schema", value: "structured/v1" }. Rækker uden denne markør er "custom"-pagter og modtager ingen struktureret validering eller skema-bevidst rendering.
Indefrosne anerkendelsespladser. structured/v1-pagter bærer præcis fire anerkendelsesrækker under disse nøgler:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value for hver er én af otte indefrosne engelske strenge, valgt af (role, kind)-parret, hvor role ∈ { seller, buyer, provider, client } og kind ∈ { standard, capacity }. Selve strengene er normative protokoldata — begge parters ML-DSA-65-signaturer forpligter sig til de præcise bytes via body_hash. De er IKKE lokaliseret; den signerede body er sprogneutral. Enhver ordlydsændring kræver en ny skemaversion (structured/v2).
De otte strenge, deres opslag (acknowledgement_for(role, kind)) og rationalet for hver er fastlåst af referenceimplementeringen. Konforme implementeringer MUST udsende byte-identiske anerkendelsesværdier; golden-fixture SHA3-256-body-hash-tests, der dækker alle fire role-kombinationer, fanger enhver drift.
Læsernes visningsrækkefølge. Anerkendelsesstrengene indeholder formuleringer som "described above", der forudsætter, at beskrivelses-/omfangsrækkerne rendres før anerkendelserne. Læsere MUST rendre terms-arrayet i CBOR-rækkefølge; omorganisering bryder prosa-semantikken.
Modpartens kontakt. Når Party B's contact er en gyldig e-mailadresse, sender qub-upload-tjenesten automatisk en gennemgang/medunderskrivnings-invitation pr. e-mail på stage-tidspunktet og binder den endelige medunderskrift til verifikation af samme adresse (§9.7). Pagter, hvis Party B-kontakt er fraværende, kan stadig medunderskrives, men kun gennem en out-of-band-kanal — tjenesten afviser medunderskrivningsanmodninger, der ikke kan producere en matchende 15-minutters e-mail-verifikationsmarkør.
6.2 Verdict-body (content_type = 0x04)
En verdict-body er den kanoniske CBOR-kodning af en VerdictBody-værdi:
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
}
Kanonisk CBOR-nøglerækkefølge:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
Den samlede serialiserede verdict-CBOR MUST NOT overstige 8 KB (matcher registret ovenfor).
Outcome-enum. Wire-byten er intent-neutral; de fire kategorier Right / Partial / Wrong / Unfalsifiable dækker resultatrummet for ethvert verdict-bærende intent. Per-intent-etiketter ("Ramte plet" / "Holdt det" / "Leveret" / "Bekræftet" for Right osv.) er en læser-side renderingssag, der opløses mod den overordnede qubs intent — wire-formatet forbliver sprog- og intent-neutralt. Værdier uden for 1..=4 MUST afvises ved dekodning.
Overordnet linkning. En verdict-qub bærer IKKE den overordnede reference i sin body. Den overordnede qubs Arweave-transaktions-id udsendes som Parent-Tx-Id-storage-tagget ved upload (§7 storage-tag-laget). Det holder body'en som en selvstændig signeret erklæring om selvbedømmelse; revisionskæden ("ret om hvad?") etableres via Arweave-tag-opslaget.
Sikkerhed for evidence-URL (normativ). Når evidence_url er til stede, MUST validatorer (compose-siden, wire-siden, Worker-edge) håndhæve:
- Kun HTTPS. Strengen MUST starte med byte-sekvensen
https://. Ethvert andet skema —http,ftp,javascript,data,fileosv. — afvises. - Længdeloft. ≤ 2.048 bytes (browserens praktiske URL-grænse).
- NFC + fjendtligt-kodepunkt-tjek. Samme regel som
titleogreflection— bidi-override- / nul-bredde- / tag-block- / BOM- / C0- / C1-kodepunkter afvises. Definitionen matcher Rustcrate::handle::contains_hostile_text_codepointog TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(hold dem synkroniseret). - Ingen mellemrum, ingen ASCII-kontroller. Mellemrum / DEL / sub-
0x20-bytes hvor som helst i URL'en afvises — lukker\n/\t-injektionsvektoren, som bidi-reglen ikke dækker. - Ikke-tomt hostsegment. Alt mellem
https://og første/,?eller#MUST være ikke-tomt.
Ingen server-side fetching. Workeren MUST NOT proxy, fetche eller forhåndsvise URL'en. Protokollen gemmer en streng; rendering sker læser-side med rel="nofollow noopener noreferrer" target="_blank" og en synlig vært vist sammen med linkteksten.
Reflektion. Valgfri skaber-skrevet refleksionstekst ("hvad ændrede sig, hvad lærte du"). Samme NFC + fjendtligt-kodepunkt-validering som title. Tomt / kun-mellemrum-input falder sammen til fraværende ved konstruktion.
Skemaversion. v1 understøtter kun verdict_version = 0x01. Fremtidige skemarevisioner bumper denne byte og lander sammen med en ny protokolversion pr. §12.
7. Forseglingsprotokol
Den komplette forseglingssekvens. Hvert trin er normativt.
1. User composes plaintext and metadata in ComposeQub.
2. Validate:
a. body is non-empty.
b. body size ≤ max for content_type and user tier (see §6).
c. unlock_at is in the future.
d. unlock_at ≤ created_at + 10 years.
e. content_type is a known, supported value.
3. Compute body_hash = SHA3-256(body).
4. Set created_at = current Unix seconds UTC.
5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
(Computed here, before qub_id, because drand_round is bound into the qub_id
preimage — §4.1, V1.2.)
6. Compute qub_id (see §4.1), folding in drand_round from step 5.
7. Construct QubEnvelope with all fields.
8. Serialise QubEnvelope using canonical CBOR → bytes B.
Assert: serialised output matches canonical profile (§3).
9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C, and matching qub_id, version,
unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
never the bare SealedQubCbor. K leaves the device only as the URL
fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
signs and uploads to permanent storage. The service is byte-blind to the inner
SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
when a short code is allocated). Browsers do not transmit URL fragments
to servers, so K is never observed by qub.social or any storage gateway.
Lager-tag-laget (out-of-band). qub-upload-tjenesten tilknytter et bevidst lille sæt lager-transaktionstags ved siden af den wrappede payload. Content-Type=application/octet-stream er normativt påkrævet. Referencetjenesten tilknytter desuden tre valgfrie tags, når skaberen vælger at vise dem: Intent (allowlist-valideret komposeringshensigt — f.eks. quote, reply, commitment), Author (skaberens §9.3-pubkey-fingeraftryk som 64-tegns lowercase hex) og Parent-Tx-Id (det overordnede qubs lager-transaktions-id for svarkæder, 43-tegns base64url).
Author-tagget er opt-in pr. qub: reference-skaber-appen tilknytter det kun, når brugeren eksplicit aktiverer offentlig tilskrivning på forseglingstidspunktet. Når kontakten er slået fra — standarden — skrives intet Author-tag, og qub'en er utilskrevet på kæden: intet i permanent lager forbinder uploaden til en skabers handle, e-mail eller andre qubs. Når kontakten er slået til, opløses Author-fingeraftrykket til skaberens valgte @handle via §9.5-attesteringskæden. Svarkæde-relationer og Intent er ikke-identificerende. Den ydre wrapper (§13) beskytter den indre body mod ciphertext-korrelation — og forhindrer en harvester i at genkende og masse-dekryptere qub-formede uploads, efter at deres drand-runde er udgivet.
Referencetjenesten tilknytter bevidst IKKE App-Name-, App-Version- eller Type-tags: ethvert sådant enkeltværdifilter ville returnere hele qub-korpusset til en GraphQL-forespørgsel, hvilket er uforeneligt med wrapperens body-only-fortrolighedsomfang.
En konform verifikator MUST NOT være afhængig af noget lager-tag til §11-tredjeparts-verifikation; body-hashet / qub_id / signatur forpligter kun til den indre CBOR, aldrig til tag-sættet.
8. Oplåsningsprotokol
Den komplette oplåsningssekvens. Hvert trin er normativt.
1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
K = base64url_decode(fragment) from the URL fragment. If the fragment
is absent or malformed → display "this URL is missing its decryption
key" and stop; the viewer MUST NOT contact the storage gateway
without K, since fetching wrapped bytes the viewer cannot decrypt
serves no purpose and only leaks the access attempt.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
`version` byte is `0x01`, and compute SealedQubCbor =
unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
swapped nonce) → display "this URL's decryption key does not match
the stored qub" and stop. Authentication failures are
indistinguishable to the viewer per §13.5.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check (V1.2). Recompute expected_round =
ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
Reject unless SealedQub.drand_round == expected_round AND the round baked
into the tlock ciphertext stanza (read via the age/tlock header, no signature
required) == expected_round. The stanza round is the one that actually gates
decryption; without this check a malicious creator could bind the ciphertext
to an already-past round while displaying a future countdown, so anyone
reading the stored bytes could decrypt before unlock_at. Implementations with
no chain identity (test mocks) skip this check.
7. Once current time ≥ SealedQub.unlock_at:
a. Fetch drand round signature for SealedQub.drand_round from drand network.
b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
8. Parse B → QubEnvelope.
9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
Fail → integrity error.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact). Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.
9. Forfatter-signering
9.1 Rationale
qubs opbevares i permanent lager. Forfatter-signaturer skal forblive uforfalskelige på ubestemt tid, hvilket er grunden til, at v1.0 bruger den post-kvante ML-DSA-65-ordning (FIPS 204) frem for en klassisk ordning, hvis sikkerhed kan degradere inden for qub'ens permanente levetid.
9.2 Algoritmeregister
sig_alg |
Ordning | Nøglestørrelse | Signaturstørrelse |
|---|---|---|---|
0x00 |
Ingen signatur (usigneret) | — | — |
0x01 |
ML-DSA-65 (FIPS 204) | 1.952 bytes | 3.309 bytes |
Læsere MUST afvise ukendte sig_alg-værdier.
9.3 Konstruktion af signeret preimage
Der har eksisteret to preimage-versioner. Alle signaturer MUST bruge V2, og verifikatorer MUST kun acceptere V2. Den ældre V1-preimage (dokumenteret nedenfor til historisk reference) blev accepteret som en fallback udelukkende til verifikation under V2-migreringen; den fallback er nu udfaset, og en signatur, der kun bruger V1, afvises nu.
V2 (aktuel — produceret af al ny forfatter-signering og af begge signaturer i pagtens staging- og medunderskrivningsflow):
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 følger samme fraværs-sentinel-konvention som title_hash (§4.2.1): 32 nul-bytes er ikke et gyldigt SHA3-256-output, så "fraværende" aldrig kan kollidere med en tilstedeværende etiket. Alle felter har fast bredde, så preimagen er entydig uden længdepræfikser.
V1 (ældre — UDFASET; produceres ikke længere og accepteres ikke længere ved verifikation):
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
V1-preimagen udelod sender_label og reply_to. Den blev accepteret som en fallback udelukkende til verifikation under migreringen til V2; den fallback er siden blevet udfaset — verifikatorer MUST kun acceptere V2-preimagen. Definitionen bevares her til historisk reference og for at forklare domæneseparatoren nedenfor. En signatur, der kun verificerer mod V1, MUST behandles som en verifikationsfejl.
Domæneseparatorer: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" er hver 17 ASCII-bytes ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Ingen padding. Den afvigende separator domæneadskiller de to konstruktioner, så en signatur over den ene preimage aldrig kan verificere som den anden.
org_id_present-byte: byten efter unlock_at MUST være 0x00. Referenceimplementeringen eksponerer denne som konstanten ORG_ID_PRESENT_INDIVIDUAL = 0x00 i crates/qub-core/src/signing.rs; læsere, der rekonstruerer sig_input til verifikation, MUST udsende samme byte.
Signaturomfang — hvad der er og ikke er dækket. V2-sig_input forpligter sig direkte til version, qub_id, body_hash, unlock_at, sender_label og reply_to (plus den faste domæneseparator og org_id_present-byten). qub_id er selv udledt af version, content_type, created_at, unlock_at, outcome_at, drand_round og body_hash via §4.1-preimagen, så enhver ændring til disse felter producerer et andet qub_id og ugyldiggør signaturen transitivt. Den autentificerede overflade er derfor:
| Felt | Autentificeret af signatur | Hvordan |
|---|---|---|
version |
✓ | Direkte input til sig_input |
qub_id |
✓ | Direkte input |
body_hash |
✓ | Direkte input |
unlock_at |
✓ | Direkte input |
sender_label |
✓ | Direkte input via sender_label_hash (V2-preimage — den eneste accepterede form) |
reply_to |
✓ | Direkte input via reply_to_or_zero (V2-preimage — den eneste accepterede form) |
content_type |
✓ | Transitivt, via qub_id-preimage |
created_at |
✓ | Transitivt, via qub_id-preimage |
outcome_at |
✓ | Transitivt, via qub_id-preimage |
drand_round |
✓ | Transitivt, via qub_id-preimage (V1.2) |
body |
✓ | Transitivt, via body_hash = SHA3-256(body) |
author_pubkey |
— (implicit) | Nøglen, der verificerede signaturen, er forfatteren pr. definition |
cosigner_pubkey / cosigner_signature |
— | Uafhængigt signeret over samme sig_input (se §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | Ydre SealedQub-felter, ikke inde i envelopen — dækket af deres egne strukturelle invarianter (runde-/kæde-konsistens), men ikke af forfattersignaturen. (drand_round er nu bundet transitivt via qub_id-preimage'et — se ovenfor.) |
Hvorfor V2 er den eneste accepterede preimage.
- Under den udfasede V1-preimage kunne en part med skriveadgang til de lagrede bytes udskifte
sender_label("Alice" → "Mallory") eller givereply_toen ny forælder — og genkryptere efter runden — uden at ugyldiggøre forfattersignaturen, fordi ingen af felterne var i den signerede preimage. V2 dækker begge, så enhver ændring til et af felterne vender verifikationen til "mislykket". Fordi verifikatorer nu kun accepterer V2, er denne udskiftning lukket for enhver signatur: en signatur, der ikke binder nogen af felterne (dvs. kun verificerer mod V1), afvises direkte i stedet for at blive accepteret via nedgradering. author_pubkeyinde i envelopen forbliver det sande identitetsanker — læsere MUST udlede vis-identiteten fraauthor_pubkey(via §9.5-attesteringslaget) i stedet for at stole påsender_label.
Implementeringer, der viser sender_label eller reply_to til slutbrugere, MUST fremhæve den autentificerede identitet (pubkey-fingeraftryk, attestering) som det primære identitetssignal, ikke etiketten.
9.4 Verifikationsprocedure
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."
Signaturverifikation er den dyreste operation (især ML-DSA-65). Den SHOULD udføres, efter alle billigere kontroller (hash, qub_id, unlock_at) er bestået.
9.5 Identitetsattesteringer
Identitetsattesteringer — mappingen af author_pubkey til menneskeligt genkendelige identitetspåstande som et qub-handle, en e-mailadresse, et socialt handle eller en passkey-legitimation — er en læser-side progressiv forbedring og er ikke påkrævet til signaturverifikation. Læsere, der opløser attesteringer til en vis-identitet, MUST anvende præcedensen:
handle > email > social > fingerprint
Fingeraftryks-fallbacken er lowercase-hex af SHA3-256(author_pubkey); den er altid tilgængelig for enhver signeret qub. Læsere MAY forkorte den til visning — reference-læseren renderer qub: efterfulgt af de første og sidste fire bytes (qub:<8 hex>…<8 hex>).
En konform verifikator kan gennemføre hver kontrol i §9.4 uden at kontakte qub-API'en, uden noget netværk ud over permanent lager og drand og uden noget server-side opslag. Attesteringsopløsning er et separat best-effort-trin, der kun udføres, efter signaturverifikation er lykkedes.
9.6 Størrelsesindvirkning
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| Signatur | 64 bytes | 3.309 bytes |
| Offentlig nøgle | 32 bytes | 1.952 bytes |
| I alt pr. qub | 96 bytes | 5.261 bytes |
| Lager-omkostningsdelta (ved ~$5/MB) | ~$0,0005 | ~$0,026 |
For en tekst-qub på 500–2.000 bytes tredobler ML-DSA-65 nogenlunde den lagrede størrelse. Den absolutte omkostning er ubetydelig.
9.7 Verifikation af medunderskriver (pact bilaterale aftaler)
For bilaterale aftaler (content_type = 0x03) beviser et andet signaturlag, at begge parter har samtykket til de samme vilkår.
Envelope-felter:
cosigner_pubkey: ML-DSA-65 offentlig nøgle for medunderskriveren (Party B).cosigner_signature: Signatur over sammesig_inputsom forfatteren (§9.3).
Begge felter MUST være til stede sammen eller begge fraværende. Hvis præcis ét er til stede, MUST læsere rapportere en integritetsfejl.
Verifikationsprocedure:
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."
Egenskaber:
- Medunderskriveren signerer det identiske
sig_inputsom forfatteren — begge parter forpligter sig til sammequb_id,body_hashogunlock_at(og, under V2, sammesender_label_hashogreply_to_or_zero). - For at lade medunderskriveren rekonstruere V2-preimagen uden adgang til de rå envelope-bytes håndhæver staging-tjenesten på stage-tidspunktet, at en pagt-envelopes
sender_labeler lig medpact_terms.party_a.label, og atreply_toer fraværende. Begge gælder for enhver reference-klient-pagt; envelopes, der overtræder dette, afvises ved staging. qub_id-udledning (§4.1) inkluderer IKKE medunderskriverfelter. At tilføje en medunderskriver til en eksisterende envelope ændrer ikkequb_id.- En pagt kan være kun forfattersigneret (énsidet forpligtelse), kun medunderskriver (usædvanligt) eller begge (fuldt bilateralt bevis).
E-mail-bindings-gate (operationel). Når en staget pagt bærer en Party B-e-mailkontakt (§6.1), MUST qub-upload-tjenesten afvise medunderskrivningsanmodningen, medmindre der eksisterer en kortvarig e-mail-verifikationsmarkør, der matcher både staging-id'et og den normaliserede e-mail-hash af denne kontakt. Markøren skrives af /api/v1/auth/verify, når magic-link-tokenet bærer et staging_id, og den verificerede adresse matcher SHA-256(normalise_email(party_b.contact)) — hvor normalise_email(addr) bevarer local-part-store/små-bogstaver og kun gør domænedelen lowercase (pr. RFC 5321 §2.3.11), og SHA-256 her er NIST FIPS 180-4-hashen (distinkt fra SHA3-256, der bruges i §4-udledninger) — og udløber 900 sekunder (15 minutter) efter udstedelse. Dette er en operationel anti-impersonation-gate, IKKE en del af det on-chain qub-bevis — en tredjeparts-verifikator, der genafspiller §11, behøver kun permanent lager og drand, uden noget server-side opslag. Markøren findes kun server-side og er aldrig en del af den signerede body.
Størrelsesindvirkning (ML-DSA-65 forfatter + medunderskriver):
| Komponent | Størrelse |
|---|---|
| Forfattersignatur | 3.309 bytes |
| Forfatterens offentlige nøgle | 1.952 bytes |
| Medunderskriversignatur | 3.309 bytes |
| Medunderskriverens offentlige nøgle | 1.952 bytes |
| Samlet krypto-overhead | 10.522 bytes |
| Lager-omkostningsdelta | ~$0,05 |
10. Markdown-rendering og sanering
Denne sektion er sikkerhedskritisk. Læseren renderer tekst-qubs (content_type = 0x01) ved hjælp af et begrænset Markdown-undersæt.
10.1 Tilladte elementer
- Overskrifter:
#til og med####(ingen#####eller######) - Fremhævning: fed (
**), kursiv (*), gennemstreget (~~) - Lister: ordnet (
1.) og uordnet (-,*) - Citatblokke (
>) - Kode: inline-spans (```) og fenced-blokke (`````)
- Vandrette streger (
---) - Linjeskift (to afsluttende mellemrum eller blank linje)
- Afsnit
10.2 Forbudte elementer
| Element | Håndtering |
|---|---|
Rå HTML (<div>, <script> osv.) |
Fjernes helt. Ingen HTML kommer igennem. |
Billeder () |
Fjernes. Billede-syntaks fjernes fra output. |
Links ([text](url)) |
URL'en gengives som synlig klartekst. Ikke auto-linket. Ikke klikbar uden eksplicit brugerhandling. |
| Farlige URL-skemaer | javascript:, data:, vbscript:, file: — fjernes. |
| Iframes, embeds, objects | Fjernes. |
| HTML-entiteter | Afkodes til vis-tegn, kun hvis de er sikre. |
10.3 Implementering
Implementeringer MUST bruge en streng allowlist-parser, ikke en blocklist. Den anbefalede tilgang:
- Parse Markdown med
pulldown-cmark(eller tilsvarende). - Gennemløb AST'et og kassér enhver knude, der ikke er i allowlisten (§10.1).
- For link-knuder: udsend URL'en som synlig tekst, ikke som et klikbart
<a>-element. - Konverter det filtrerede AST til en typed mellemrepræsentation (f.eks. en
MarkdownNode-enum med kun sikre varianter). Rå HTML er strukturelt ikke-repræsenterbar i denne IR. - Render fra den typed IR til mål-vislaget (f.eks. reaktive view-komponenter, DOM-knuder). Ingen HTML-strengsammenkædning eller
innerHTMLpå noget tidspunkt.
Blocklist-tilgange er skrøbelige, fordi nye Markdown-udvidelser eller parser-særheder kan introducere ufiltrerede elementer. Den typed-AST-tilgang gør XSS strukturelt umulig — der er ingen variant, der kan bære vilkårlig HTML.
10.4 Størrelses- og strukturgrænser
- Maksimal renderet overskriftsdybde:
####(H4).#####og dybere rendres som fed tekst. - Ingen grænse for antal afsnit (body-størrelsesgrænserne i §6 er begrænsningen).
- Fenced kodeblokke: ingen syntax-highlighting i MVP. Rendres som monospace-præformateret tekst.
11. Tredjeparts-verifikation
Enhver tredjepart kan verificere en offentlig qub uden qubs samarbejde. Verifikationsproceduren:
1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.
Hvad verifikation beviser:
| Bevis | Hvad det fastslår |
|---|---|
| Forpligtelse | Ciphertexten eksisterede senest ved lager-blokkens tidsstempel. |
| Integritet | Klartekst-bodyen matcher den forpligtede hash og er ikke blevet ændret. |
| Timing | Indholdet var ulæseligt indtil drand-runden, der svarer til det valgte oplåsningstidspunkt (under tlock- og drand-sikkerhedsantagelser). |
Hvad verifikation IKKE beviser:
| Ikke-bevis | Hvorfor |
|---|---|
| Forfatterskab | sender_label er dekorativt. Uden sig_alg ≥ 0x01 kunne hvem som helst have forseglet dette indhold. |
| Hensigt | qub'en beviser indhold og timing, ikke hvad skaberen subjektivt mente. |
| Pre-event-timing | Lager-blokinklusion kan lægge minutter efter den faktiske upload. Forpligtelsens tidsstempel er bloktiden, ikke det øjeblik, brugeren trykkede "forsegl". |
12. Versionering
12.1 Protokolversion
version-feltet (u8) i både SealedQub og QubEnvelope identificerer den større protokolversion.
- Læsere MUST afvise ukendte større versioner med en tydelig fejl.
- Inden for en kendt større version MUST afkodere afvise ukendte map-nøgler (§3.1) — skemaudvikling sker ved at introducere en ny
version, ikke ved at tilføje nøgler, som eksisterende afkodere ville springe over. (Tidligere revisioner af denne specifikation tillod, at ukendte valgfrie felter blev tolereret; den klausul er trukket tilbage — den gjordeencode(decode(x))ikke-injektiv og åbnede en vektor for skjult signeret indhold på pact-payloads.) - Content types (
content_type) og signaturordninger (sig_alg) er version-gated: nye værdier kan kun introduceres sammen med en ny protokolversion eller eksplicit registeropdatering.
12.2 Versionshistorik
| Version | Værdi | Beskrivelse |
|---|---|---|
| v1 | 0x01 |
Offentlige tekst-qubs (content_type 0x01), pact bilaterale aftaler (0x03, structured/v1-skema, ML-DSA-65 forfatter + medunderskriver), tlock, SHA3-256 |
12.3 Fremad-kompatibilitet
En v1-læser, der støder på en QubEnvelope med ukendte CBOR-map-nøgler (nøgler, der ikke er i §3.2-kanonisk rækkefølge), MUST afvise den med en afkodningsfejl (§3.1). Fremad-kompatibilitet hviler på version-feltet, ikke på nøgletolerance: fremtidige tilføjelser — selv mindre metadata — udgives under en ny version-værdi, som en v1-læser afviser med en tydelig "nyere protokol"-fejl i stedet for stiltiende at droppe indhold, som signaturerne forpligter sig til.
En v1-læser, der støder på sig_alg = 0x01 (ML-DSA-65), men som mangler ML-DSA-65-verifikationsunderstøttelse, SHOULD vise qub-indholdet med en "signatur til stede, men ikke verificerbar"-notits, ikke afvise qub'en helt. Reference-implementeringen i dag afviser enhver sig_alg-værdi ud over 0x00 og 0x01, fordi v1-registret ikke indeholder nogen anden gyldig algoritme — streng afvisning og soft-fail er observationelt identiske, indtil en tredje algoritme registreres. Soft-fail-adfærden ovenfor bliver bærende, så snart §9.2 optager en ny post, og reference-læseren vil blive opdateret til soft-fail på det tidspunkt.
12.4 Ydre wrapper-version
OuterWrapperen beskrevet i §13 bærer sin egen version-byte, uafhængig af SealedQub.version og QubEnvelope.version. De to versionsrum udvikler sig separat: en fremtidig post-kvante-sikker symmetrisk erstatning hæver wrapper-byten uden at røre den indre protokolversion, og en fremtidig protokollag-tilføjelse (f.eks. et nyt envelope-felt) hæver den indre version uden at røre wrapper-byten.
OUTER_WRAPPER_VERSION_* |
Værdi | Algoritme | Status |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM med 12-byte nonce, 16-byte authentication tag, AAD bundet til qub_id |
v1-standard |
| — | 0x02–0xFF |
Reserveret | Fremtidig |
Læsere MUST afvise ukendte wrapper-versioner med en tydelig fejl. Protokollen holder bevidst wrapper-versionsrummet smalt, indtil en konkret migrationsdriver dukker op (f.eks. NIST-vejledning, der favoriserer en anden AEAD); en 0x02-plads vil blive allokeret i samme revision, der introducerer algoritmen.
13. Ydre krypteringswrapper
13.1 Rationale
Protokollagene (QubEnvelope → tlock → SealedQub) gør en forseglet qub tidslåst: bodyen er ulæselig indtil unlock_at, og drand-runde-signaturen er blevet udgivet. Efter oplåsning er runde-signaturen imidlertid offentlig, og den kanoniske CBOR-form af SealedQub er genkendelig, så en harvester, der indekserede permanent-lager-transaktioner, kunne masse-dekryptere hele qub-korpusset.
Den ydre krypteringswrapper lukker den kanal ved at indsætte et yderligere symmetrisk AEAD-lag mellem den kanoniske SealedQubCbor og de bytes, der skrives til permanent lager. Den 256-bit nøgle K lever kun i URL-fragmentet af leveringslinket og på brugerenheder; browsere transmitterer ikke URL-fragmenter til servere, så qub.social, hver lager-gateway og hver CDN foran begge er observationelt blinde for K. Hver qub i permanent lager er derfor en uigennemsigtig ciphertext, hvis klartekst ikke kan genskabes uden den URL, skaberen valgte at dele.
Nettoeffekt:
- Optællings-immunitet som standard. Wrappede bytes i permanent lager er byte-uskelnelige fra vilkårlig ciphertext. En harvester-strategi om at "GraphQL-spørge efter qub-formede uploads, masse-dekryptere med offentlige drand-signaturer" terminerer ikke med klartekst.
- Crypto-shredding-privatlivsposition. qub.social kan bogstaveligt talt ikke dekryptere sit eget korpus. Stævninger når ciphertext, ikke klartekst.
- To-trins-fortrolighedsstige. Standard = link-kontrolleret adgang (denne sektion). Modtager-krypterede private qubs (en reserveret Fase 2-funktion, endnu ikke specificeret) lægger sig ovenpå som det andet trin.
13.2 Lagdeling
plaintext body ← QubEnvelope.body (§2.2)
↓ canonical CBOR (§3)
envelope CBOR
↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
↓ canonical CBOR (§3)
SealedQubCbor bytes ← inner wire artifact
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes ← uploaded to permanent storage (§7 step 15)
Forsegling og oplåsning på protokollaget (§7, §8) er uændrede under wrapper-grænsen; wrapperen sættes på ved kaldstedet for seal() og fjernes ved kaldstedet for unlock().
13.3 OuterWrapper-datastruktur
struct OuterWrapper {
version: u8, // 0x01, see §12.4
qub_id: [u8; 32], // copied from inner SealedQub; AEAD AAD
nonce: [u8; 12], // 96-bit AEAD nonce
ciphertext: Vec<u8>, // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}
Feltinvarianter.
versionMUST være lig med0x01for v1.0 wrapper-bytes.qub_idMUST være lig medqub_id-feltet af den SealedQub, der genskabes efter unwrap. Unwrap-trinnet håndhæver det ikke direkte (AEAD-AAD-bindingen gør byte-niveau-manipulation umulig), men oplåsningslaget kontrollerer forholdet transitivt: hvis en skaber wrapper enSealedQubCbor, hvis indrequb_idikke matcher wrapperensqub_id, fejler §8 trin 11.nonceMUST være 96 bits (12 bytes), frisk genereret af en CSPRNG ved hver wrap-operation. Genbrug af en nonce under samme nøgle tillader AEAD-nonce-reuse-angreb, der genskaber klarteksten; producenter MUST behandle (key,nonce)-par som engangs.ciphertexter AES-256-GCM-outputtet: ciphertext-bytes sammenkædet med den 16-byte authentication tag.ciphertext.len() == SealedQubCbor.len() + 16præcis.
CBOR-kodning. Kanonisk CBOR pr. §3, med samme nøgle-ordens-regel (sorteret efter kodet bytelængde stigende, derefter leksikografisk). De fire nøgler er:
| Nøgle | Kodede bytes | Rækkefølge |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
Den første byte af OuterWrapper-CBOR'en er derfor den definite-length-map-header for en 4-entry-map (0xA4).
13.4 AAD-binding til qub_id
Wrapperen binder qub_id som AEAD additional authenticated data. Dette er det bærende strukturelle forsvar mod tre angrebsklasser:
| Angreb | Forsvar |
|---|---|
Flytte ciphertext under et andet qub_id-felt i wrapperen |
AAD-mismatch → AEAD-autentificering fejler |
| Blande URL-fragmentet af qub A med permanent-lager-bytes af qub B | AAD-mismatch → AEAD-autentificering fejler |
Manipulere qub_id-feltet af wrapperen efter upload |
AAD-mismatch → AEAD-autentificering fejler |
At bære qub_id i wrapperens klartekst svækker ikke optællings-immuniteten på meningsfuld måde — qub_id er selv en SHA3-256-hash af §4.1-preimagen uden gendannelig preimage fra digesten, og en optæller, der allerede har høstet wrapper-bytene, lærer intet af det synlige qub_id, som de ikke kunne udlede af selve uploadens eksistens.
13.5 Wrap- og unwrap-algoritmer
wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
require K.len() == 32 and N.len() == 12 and Q.len() == 32
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.4
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
return P // P is the inner SealedQubCbor
Fejl-tilstands-kollaps. Forkert K, forkert nonce, AAD-mismatch og manipuleret ciphertext producerer alle samme DECRYPT_FAILED-fejl. Dette er en bevidst AEAD-egenskab: at skelne fejl-tilstanden ville skabe en sidekanal, som en fjern-angriber kunne undersøge ved at sende deforme wrappere og time svaret. Reference-implementeringer MUST kollapse alle AEAD-fejl til en enkelt fejlform.
13.6 Nøglemateriale og distribution
Wrap-nøglen K er en 256-bit ensartet tilfældig værdi genereret pr. qub af en CSPRNG. Reference-implementeringerne henter den fra:
- WASM-skaber:
getrandom(WebCrypto underwasm_js-backend). - Server-side seal-API-opkalder: dennes lokale CSPRNG; opkalderen leverer og beholder
Ksomwrapper_key_b64url. Workeren brugerKi hukommelsen til wrapperen, men MUST NOT persistere den. Dette lader et idempotent genforsøg genskabe et redigeret svar ved hjælp af opkalderens beholdte kapabilitet i stedet for at afhænge af en engangshemmelighed genereret på serveren.
Distribution: K MUST kodes som URL-sikker base64 (RFC 4648 §5, ingen padding) og tilføjes leveringslinket som fragment-komponenten:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragmentet transmitteres aldrig til nogen server af en konform browser. Genoprettelseskanaler (server-side historik-indeks, opt-in e-mail-auto-send), der persisterer det fulde leveringslink — inklusive fragmentet — ud over brugerens enhed, er et eksplicit kompromis mod standard-crypto-shredding-positionen og MUST gates ved eksplicit brugersamtykke.
Tab af fragment. Hvis en bruger mister URL-fragmentet og ikke har nogen genoprettelseskanal, er qub'en ulæselig. Dette er designets bærende kompromis og MUST oplyses brugeren på forseglingstidspunktet. MVP'en styrker forseglingstids-oplysningen med eksplicit "gem dette link"-tekst og en verificeret-e-mail-genoprettelseskanal for brugere, der vælger det til.
13.7 Uden for denne sektions omfang
- Forfatter-signering (§9) er uændret: signaturer beregnes inde i den indre
QubEnvelopeog genskabes efter unwrap → tlock-dekryptering → CBOR-parse. - Modtager-krypterede private qubs (en reserveret Fase 2-funktion, endnu ikke specificeret) komponerer ovenpå denne wrapper som et andet fortrolighedstrin; begge trin kan være aktive samtidigt.
- Pagter (§6, content_type
0x03) wrappes præcis som tekst-qubs; wrapperen er byte-blind for indre content type.
13.8 Offentlige qubs (udeladelse af wrapper)
Den ydre wrapper er valgfri på leveringslaget. En skaber kan forsegle en qub som offentlig, og i så fald skrives den kanoniske SealedQubCbor til permanent lager direkte, uden noget OuterWrapper-lag og uden nogen nøgle K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
En offentlig qub er tidslåst, men ikke link-gated: den forbliver ulæselig, indtil dens drand-runde udgives (tlock-laget er uændret), men efter oplåsning kan enhver, der har arweave_tx_id, dekryptere den — der kræves intet URL-fragment, fordi der ikke findes nogen K. Dette er det bevidste kompromis for overflader, som serveren skal drive: afsløringsnotifikations-e-mails, tredjeparts-embeds og rigere SEO efter afsløring har alle brug for et link, der virker uden en hemmelighed, serveren aldrig besidder (§13.6).
Konsekvenser, en producent MUST tage højde for:
- Ingen optællings-immunitet. Offentlige qubs giver per konstruktion afkald på §13.1-optællings-immunitetsegenskaben. Reference-upload-tjenesten stempler et
Visibility: public-tag på permanent lager på dem (og kun dem), så de bevidst er opdagelige; private qubs bærer intet sådant tag og bevarer deres byte-uskelnelighed. - Klartekst-titel eksponeret på forseglingstidspunktet. §3.2-
title-feltet er klartekst inde iSealedQubCbor. Under wrapperen er det skjult, indtil en læser levererK; uden wrapperen er det verdenslæsbart i permanent lager fra uploadøjeblikket, før oplåsning. Konforme skaber-apps MUST oplyse om dette på forseglingstidspunktet. - Detektion er strukturel. En konform læser/embed skelner de to former ved parse: bytes, der parser som
OuterWrapper, tager unwrap-med-K-vejen; bytes, der parser som en barSealedQubCbor, accepteres direkte. Der kræves intet wire-flag, ogqub_idbinder ikke synligheden — det samme indhold er byte-identisk påSealedQub-laget, uanset om det forsegles offentligt eller privat.
Privat (wrappet) forbliver standarden; offentlig er et eksplicit pr.-qub-skabervalg.
14. Testvektorer
14.1 qub_id-udledning
Input:
version = 0x01
content_type = 0x01
created_at = 1735689600 (2025-01-01 00:00:00 UTC)
unlock_at = 1736294400 (2025-01-08 00:00:00 UTC)
outcome_at = absent
drand_round = 4695445 (= (1736294400 - 1595431050) / 30, drand mainnet params §14.2)
body = "Hello, future." (UTF-8, 14 bytes)
title = absent
Intermediate:
body_hash = SHA3-256("Hello, future.")
= 76ab8b3f843c6ed4f2d0fd75b9f457b4
ad49dd4450f9c22723ae430e3af3211d
title_hash = [0u8; 32] (title absent — §4.2.1 sentinel)
Domain separator (10 bytes):
[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
Preimage (108 bytes — V1.2):
domain_separator || // 10 bytes
0x01 || // version
0x01 || // content_type
0x0000000067748580 || // created_at as i64 big-endian (1735689600)
0x00000000677DC000 || // unlock_at as i64 big-endian (1736294400)
0x0000000000000000 || // outcome_at_or_zero (outcome_at absent)
0x000000000047A595 || // drand_round as u64 big-endian (4695445)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 3a9fcb31b750d985c262fada6d4f777f
d6a28be831d941d85c131f5a4bbaf8a4
Implementeringer MUST producere identiske body_hash- og qub_id-værdier for dette input. Denne testvektor SHOULD være den første enhedstest, der skrives. De kanoniske værdier ovenfor blev beregnet af reference-implementeringen og MUST matche bit-for-bit. Historiske preimage-layouts (før-lancering — ingen live qubs afhang af disse): det 92-byte V1.0 qub_id var 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; det 100-byte V1.1 qub_id (efter at have foldet outcome_at_or_zero ind) var b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 folder drand_round ind og hæver domæneseparatoren til QUB_ID_V2.
14.2 Mapping fra oplåsning til runde
Input:
unlock_at = 1735689600
chain_genesis_time = 1595431050
chain_period_seconds = 30
Calculation:
(1735689600 - 1595431050) / 30 = 4675285.0
ceil(4675285.0) = 4675285
drand_round = 4675285
14.3 Kanonisk CBOR-rundtur
Implementeringer MUST verificere, at serialize(parse(serialize(qub))) == serialize(qub) for alle gyldige input. Dette er en property-test, ikke en enkelt vektor.
14.4 PactTerms CBOR (content_type 0x03)
Input:
pact_version = 1
title = "Scooter deposit"
terms = [
{ key: "Item", value: "Honda Metropolitan scooter" },
{ key: "Price", value: "$100" },
{ key: "Deposit", value: "$10" }
]
party_a = { label: "Alice" }
party_b = { label: "Bob", contact: "bob@example.com" }
notes = absent
Canonical CBOR key order (PactTerms):
"notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)
Canonical CBOR key order (PactTerm):
"key"(4) < "value"(6)
Canonical CBOR key order (PartyIdentifier):
"label"(6) < "contact"(8)
De kanoniske CBOR-bytes og SHA3-256 body_hash beregnes af reference-implementeringen. Implementeringer MUST producere byte-identisk CBOR for dette input.
Implementeringer MUST også verificere, at serialize(parse(serialize(pact))) == serialize(pact) for alle gyldige PactTerms-input (property-test).
14.5 Krydssprogs-vektorer for ydre wrapper
Den ydre wrapper (§13) har en separat kanonisk fixture på crates/qub-core/tests/vectors/wrapper_v1.json. Hvert tilfælde fastsætter en (key, nonce, qub_id, sealed_cbor)-tuple som uigennemsigtige hex-input og asserter et bestemt expected_wrapper_hex-output. Begge reference-implementeringer forbruger samme JSON-fil:
- Rust:
crates/qub-core/tests/wrapper_vectors.rs(cargo test -p qub-core --test wrapper_vectors). - TypeScript:
workers/api/src/crypto/__tests__/wrapper.test.ts(npm test).
Fixturen fastlåser i øjeblikket tre tilfælde:
| Tilfælde | Dækning |
|---|---|
basic-text-public |
Mindste realistiske SealedQub-form; ingen valgfrie felter. Etablerer den kanoniske wrapper-form for en v1.0-typisk qub. |
with-recipient-pubkey |
SealedQub med recipient_pubkey sat (Phase 2-sti). Andet indre CBOR-nøglesæt, andet qub_id. |
longer-body |
~4 KiB body — udøver multi-byte CBOR-længde-præfikser inde i både den indre envelope og den ydre ciphertext. |
Implementeringer MUST producere byte-identisk expected_wrapper_hex for de optagne input. Regenerering af fixturen kræver QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors og er forbeholdt bevidste formatændringer.
15. Krypto-profil-governance (fremtid)
Denne sektion er informativ for v1 og bliver normativ første gang en anden algoritme træder ind i nogen af qubs kryptografiske primitiver.
15.1 Nuværende stilling
Protokol v1 binder præcis én algoritme pr. primitiv:
- Signatur: ML-DSA-65 (
sig_alg = 0x01; 1952-byte offentlig nøgle, 3309-byte signatur) og usigneret (sig_alg = 0x00). §9.2-registret definerer ingen andre værdier; en v1-verifikator MUST afvise enhversig_alguden for{0x00, 0x01}. En fremtidig Ed25519-post forventes (§15.3), men er ikke allokeret i v1. - Timelock: kun drand quicknet — chain hash, offentlig nøgle, genesis-tid og periode er faste netværksparametre båret af reference-
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) ogconfig/drand-endpoints.json. - Ydre wrapper: kun AES-256-GCM v1 (§13).
Verifikatorer hårdkoder i øjeblikket nøgle- og signaturlængder pr. primitiv. Ingen agility-overflade er eksponeret af wire-formatet.
15.2 Tilsigtet form
Når en anden algoritme træder ind i protokollen, vil verifikatoren blive konfigureret til en navngivet CryptoProfile (f.eks. ExqubV1), der opregner det præcise sæt af tilladte værdier pr. primitiv — sig_algs, drand-kæder, wrapper-versioner, content types. Profilen er fastsat ved verifikationstid, aldrig forhandlet in-band. Enhver værdi uden for den aktive profil afvises.
Dette garanterer, at tilføjelse af ML-DSA-87 eller aktivering af Ed25519 ikke retroaktivt kan svække eksisterende verifikator-konfigurationer: en v1-verifikator forbliver en v1-verifikator, selv efter at en v2-profil er udgivet.
15.3 Udløsningsbetingelser
Forfremme §15 til normativ status, når et af følgende foreslås:
- En anden
sig_alg-byte (Ed25519-aktivering, ML-DSA-87 eller enhver ny post i §9-registret). - En anden drand-kæde i produktionsbrug.
- En anden ydre wrapper-version.
Indtil da er §15 en pladsholder, der fastsætter migrationsformen, så fremtidige PR'er lander mod et kendt mål i stedet for at gen-litigere forhandlingsoverfladen fra bunden.
16. Gennemsigtighedslog og holdbarhedsniveauer (Design — gennemgang afsluttet)
Status. Denne sektion er en designspecifikation. Wire-formaterne, hashingen og tillidsmodellen nedenfor er normative for implementeringen, men ingen gennemsigtighedslog-kode er endnu shippet. Den eksterne W5-gennemgang er afsluttet: §16.15 optegner de afklarede beslutninger og de bindende lanceringsbetingelser, der kom ud af den. Implementeringen kan fortsætte under disse betingelser. §16 forbliver fremadrettet i samme forstand som §15 — den fastlægger målet, så implementeringen lander mod et afklaret design frem for at gen-udlede tillidsmodellen i code review. Den er strengt additiv — hver eksisterende qub beholder sin individuelle Arweave-transaktion, og der er ingen ændring af
SealedQub/QubEnvelope-wire-formatet.
16.1 Rationale og holdbarhedsniveauer
I dag hviler en qubs holdbarhed og dens tidsmæssige forpligtelse begge på en enkelt Arweave-transaktion pr. qub (§11). Det kobler forseglingslatensen til Arweave-finalitet, gør uploaden pr. qub til et produktomkostningsloft (ARWEAVE_DAILY_CEILING) og giver ingen manipulationspåviselig rækkefølge på tværs af qubs. Gennemsigtighedsloggen tilføjer to lag under og omkring dette enkelte niveau:
| Niveau | Navn | Garanti | Hvornår |
|---|---|---|---|
| T1 | R2-først synkron ack | Holdbarhedsgulv — forseglede bytes skrives til holdbart lager, før forseglingen returnerer (< 300 ms p95). |
Hver qub, synkront (§16.10). |
| T2 | Batchet gennemsigtighedslog-inklusion | Universel kun-tilføj, manipulationspåviselig forpligtelse + total rækkefølge, forankret til Arweave. | Hver qub, udskudt + batchet (§16.5–16.7). |
| T3 | Arweave-permanens pr. qub | En individuel Arweave-transaktion for qub'en. | Betalt mersalg, og Arweave-utilgængeligheds-fallback (§16.8). |
T2 gør Arweave pr. qub til et valg (T3) frem for den eneste holdbarhedsvej. ARWEAVE_DAILY_CEILING udfases som produktloft og degraderes til en circuit breaker alene på den dedikerede anker-wallet (§16.7); brugerforseglinger afvises aldrig for at overskride det.
Holdbarhedshæderlighed (afklaret — §16.15 Q6). Holdbarheden regredierer ikke: T1-R2-skrivningen er synkron og skrives én gang, så en gratis-niveau-qub, der ikke købte T3, er fuldt holdbar i det øjeblik, forseglingen returnerer. Det, der bliver grovere, er den beviselige øvre grænse for forpligtelsestidspunktet: for en gratis qub bliver det ankerets bloktid frem for en transaktionsbloktid pr. qub. Ved lav volumen — den realistiske tidlige lancerings- og lavtrafiktilstand — er den fulde daglige kadence det typiske gulv, ikke en sjælden grænsetilfælde. Produktindrammingen er derfor en øvre grænse med ingen forpligtet numerisk latens — "forseglet og holdbar nu; et uafhængigt offentligt tidsstempel tilføjes ved næste loganker (typisk dagligt)" — og bevis for forpligtelse på den nøjagtige time er en betalt T3-egenskab, oplyst på niveausammenligningsoverfladen og i vilkårene (§16.11, §16.15 Q6). Enhver tidsgrænse er udelukkende en intern SLO, aldrig en markedsført SLA.
16.2 LogLeaf-struktur (to forpligtede former)
En logpost er et LogLeaf, kodet som håndskrevet kanonisk CBOR under §3.1-profilen (definite-length, ingen tags, ingen floats, korteste-form-heltal, NFC-tekst, valgfrie felter udeladt ved fravær, nøgler ordnet efter kodet bytelængde stigende, derefter byte-leksikografisk). §3.1-parse → re-encode → compare-kanonik-værnet anvendes på kodningsvejen før hashing (ikke kun ved afkodning), så to implementeringer ikke kan være uenige om leaf-bytene gennem en forskel i heltalsbredde eller nøglerækkefølge. Alle heltal er u8 / u64 / i64; alle digests er 32-byte byte strings (bstr[32]). Et lagret Arweave-transaktions-id er en rå 32-byte SHA-256-digest båret som bstr[32], aldrig en base64url-tekststreng (matcher §3.3).
Leaf'en har to former valgt af en kind-byte, fordi Workeren på standard-upload-vejen er byte-blind: POST /api/v1/upload modtager kun qub_id og unlock_at som uautoriserede klientpåstande — body_hash, drand_round, created_at og drand_chain_version er alle forseglet inde i den ydre §13-wrapper, hvis nøgle Workeren aldrig besidder. Kun server-seal-vejen (POST /api/v1/seal) udleder body_hash / drand_round fra klartekst. En enkelt leaf-form, der bar body_hash + drand_round, ville derfor forpligte værdier, operatøren aldrig verificerede for størstedelen af rigtige qubs. Opdelingen holder hver forpligtet værdi hæderlig:
| Nøgle | Kod. læng. | Type | Tilstedeværelse | Betydning |
|---|---|---|---|---|
seq |
4 | u64 |
påkrævet | Globalt 0-baseret leaf-indeks; positionen, inklusionsbeviset forpligter sig til. |
kind |
5 | u8 |
påkrævet | 0x01 attesteret (server-seal) eller 0x02 påstået (client-seal / byte-blind upload). |
ref |
4 | bstr[32] |
påkrævet | Leaf-reference-id. Attesteret → rå qub_id. Påstået → det blindede id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
6 | bstr[32] |
påkrævet | Indholdsadresse SHA3-256(stored_bytes) — den ene indholdsbinding, Workeren altid kan beregne hæderligt, på begge veje. |
unlock_at |
10 | i64 |
påkrævet | Kopieret (attesteret) eller påstået (påstået); valideret > 0, før den indgår i leaf'en. |
received_at |
12 | i64 |
påkrævet | Worker-vægur ved R2-ack. Ikke-evidentiær (operatør-påstået; §16.6). Til stede til selvbeskrivelse, aldrig et bevis. Valideret > 0. |
body_hash |
10 | bstr[32] |
kun kind=0x01 |
Udeladt ved 0x02 — Workeren mangler den under §13. |
drand_round |
12 | u64 |
kun kind=0x01 |
Udeladt ved 0x02. |
Et kind=0x02-leaf forpligter bevidst hverken body_hash eller drand_round: det attesterer forpligtelsen og rækkefølgen af en uigennemsigtig ciphertext på indholdsadressen chash, der hævder qub_id og unlock_at — ikke dens klartekst eller runde. Klartekst-/runde-benene for en påstået qub kommer fra den eksisterende §11-.qub-bundle-verifikation, ikke fra loggen (§16.11). drand_chain_version er ikke i leaf'en (den er inde i wrapperen på standardvejen); kæde-granulariteten lever på ankeret (§16.7). Encoder-disciplin: afvis et helt-nul ref eller chash, og afvis ikke-positiv unlock_at / received_at, hvilket spejler outcome_at > 0-sentinel-værnet i cbor.rs.
16.2.1 Blinding af private qubs
Loggen må ikke blive det optællingsorakel, som den ydre §13-wrapper findes for at forhindre (§13.1). For en privat (wrappet) qub forpligter det asserted-leaf den blindede identifikator SHA3-256(qub_id ‖ log_blind_secret), hvor log_blind_secret er en serverholdt hemmelighed, og udelader body_hash. En tredjepart kan ikke binde et sådant leaf til et bestemt qub_id; qub'ens indehaver, som har leveringslinket og dermed qub_id, kan genberegne blindingen for at bekræfte sin egen inklusion. En offentlig qub (allerede optællelig, bærer allerede Visibility: public-Arweave-tagget pr. §13.8) forpligter det rå qub_id. Dette er det ene sted, hvor selvstændig verificerbarhed bevidst viger for en bærende privatlivsinvariant; den selvstændige binding for private qubs er chash (§16.9).
log_blind_secret-forvaring (afklaret — §16.15 Q4). Blindingen beskytter leaf-ulinkbarhed, ikke klartekst-fortrolighed (§13-wrapperen holder den uafhængigt). Ved et log_blind_secret-kompromis kan modstanderen for ethvert qub_id, vedkommende allerede holder eller kan rekonstruere (hver qub, hvis bundle/URL vedkommende har, plus ethvert lav-entropi- eller offentligt qub_id), genberegne leaf-ref i én hash og binde det — dette er direkte sammenkædning af en kendt population, ikke en brute-force over et ukendt rum. Klassificér log_blind_secret som en korrelations-/Sybil-grad-hemmelighed i samme forvaringsniveau som andre serverhemmeligheder, og rotér kun fremad (en rotation om-blinder fremtidige leaves; den kan ikke retroaktivt ophæve sammenkædningen af allerede forankrede).
16.3 Leaf- og node-hashing
RFC 6962 §2.1 domæneadskilt hashing med SHA-256 erstattet af SHA3-256:
leaf_hash = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree = SHA3-256("") // defined but never anchored
Domæne-præfiks-bytene 0x02 (entry chain, §16.4) og 0x03 (STH-hash, §16.6) er reserverede og disjunkte fra disse. De er enkeltbytes og kan derfor ikke kollidere med de eksisterende 10-byte ASCII-domæneadskillere (QUB_ID_V2 osv.). Træet er RFC 6962-venstre-fuldt ubalanceret træ (hver indre opdeling ved den største potens af to strengt mindre end subtræets leaf-antal), hvilket lader inklusions- og konsistensbeviser dele én audit-path-algoritme. Reference-specifikationen bærer eksplicit venstre/højre-udlednings-pseudokode og fastlåser en ikke-potens-af-to (5-leaf) testvektor, så højre-kant-promoveringstilfældet — som en 4-leaf-vektor skjuler — udøves.
16.4 Hash-kædning (intern)
LogDO'en vedligeholder en intern entry chain udelukkende til crash-konsistens. Den er aldrig udgivet og aldrig verifikator-vendt:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
Den udgivne kun-tilføj-autoritet er den kumulative Merkle-rod + dens anker (§16.5–16.6), aldrig den rå rækkefølge, operatøren tilfældigvis serverer leaves i: kæden genberegner for enhver serveret rækkefølge, så kun den forankrede rod fastlåser den kanoniske position.
16.5 Kumulativt Merkle-træ og batching
Der er ét stadigt voksende RFC 6962-træ over alle leaves i seq-rækkefølge — ikke isolerede træer pr. batch. (En carry-leaf-kædet konstruktion pr. batch blev afvist: den er ikke en ægte præfiks-relation, så dens "konsistensbeviser" er usunde.) Det kumulative træ giver ægte RFC 9162-konsistensbeviser og lader et enkelt nyligt anker bevise inklusion for enhver ældre qub.
LogDO-Durable Objectet er den eneste skriver (blockConcurrencyWhile, spejler QuotaDO / EntitlementDO) — at tilføje til en delt log er read-modify-write på delt tilstand og MUST derfor gå gennem et DO, aldrig KV. Det cacher træets højre-kant-frontier (O(log n) hashes), så lukning af en batch er O(batch). En batch er det sæt af leaves, der forankres sammen; dens triggere er konfigurerbare, ikke protokol-frosne: en tree_size-fremgang på mindst LOG_BATCH_MAX_LEAVES (standard 4096), eller alder, der når anker-kadencen, eller en tvungen flush, når en betalt T3-forsegling lander. root_i er den kumulative Merkle Tree Hash over leaves 0 .. tree_size_i.
16.6 Signed Tree Head via Arweave-anker
Arweave-anker-transaktionen er Signed Tree Head og erstatter en operatørsignatur for selve træhovedet: det daglige anker behøver ingen qub-nøgle, fordi Arweave-tx-owner er signaturen. Moat-tesen holder — det uforanderlige substrat, ikke en qub-holdt hemmelighed, er bærende for den forankrede rod.
Der er præcis én varm qub-signeringsnøgle i designet, og den er fastlåst: kvitterings-nøglen pr. forsegling (§16.10). Dens offentlige nøgle er forpligtet i LogProfile (distribueret med verifikatoren) og kryds-signeret af anchor_owner, så en verifikator validerer en kvittering mod den samme fastlåste rod som ankeret. Dette er afklaringen af §16.15 Q2 — en ufastlåst, operatør-roterbar kvitteringsnøgle ville være repuderbar (operatøren kunne benægte, at nøglen var deres), hvilket ville annullere kvitteringens ansvarlighedsværdi mod den operatør-niveau-modstander, kvitteringen findes for at afskrække. Altså: qub holder ingen ufastlåst log-signeringsnøgle; kvitteringsnøglen er fastlåst og anchor_owner-kryds-signeret.
SignedTreeHead er kanonisk CBOR (nøgler efter kodet længde): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (forudgående sth_hash; genesis = 32 nul-bytes), log_id:bstr[32], first_seq:u64, anchored_at:i64. Dens hash er sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Fastlåst tillidsrod. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). En konform verifikator MUST kræve anchor_tx.owner == LogProfile.anchor_owner, hvor anchor_owner (og kvitterings-nøglens offentlige nøgle) er bagt ind i qub_core som LogProfile — ved siden af quicknet-konstanterne, der allerede er i DrandTimelockProvider::quicknet() — og distribueret med verifikator-binæren. Verifikatoren MUST også verificere Arweave-tx data → tx_id-bindingen lokalt frem for at stole på et gateway-/raw/-svar. Dette lukker rogue-wallet-equivokations-hullet: "forankret på Arweave" er meningsløst, indtil verifikatoren fastlåser hvilken wallet.
Rotation er en §15-governance-udvidelse, ikke et genbrug (afklaret — §16.15 Q3). §15.2's profil-overflade opregner i øjeblikket kun sig_algs / drand-kæder / wrapper-versioner / content types, og §15.3's triggere lister ingen af disse — LogProfile / anchor_owner er endnu ikke i §15's overflade. Rotations-governancen MUST derfor bygges: §15.3 udvides (nedenfor) til at tilføje LogProfile-triggeren, og en rotation er en signeret LogProfile-bump shippet i en verifikator-opdatering. En planlagt rotation bærer en udgående → indgående kryds-signatur; en kompromis-drevet rotation kan ikke (den udgående nøgle er uautoriseret/utilgængelig netop da) og falder tilbage til den §15-styrede bump, hvor prev-anker-fork-tjekket (nedenfor) afgrænser skaden i mellemtiden.
Equivokationsvindue (førsteklasses tillidsparameter). Et leaf er equivokationsresistent først, når dets dækkende anker er Arweave-bekræftet. Vinduet er received_at → anker-bekræftelse (≤ kadence + Arweave-finalitet). Inden for det er de eneste garantier den fastlåste forseglingskvittering (§16.10) og qubs operationelle integritet. Tre ansvarlighedsartefakter gør dette hæderligt frem for håndviftende (vidne-modellen er §16.15 Q2's afklaring):
- Fastlåst signeret forseglingskvittering — SCT-analogen returneret i upload-svaret (§16.10), signeret af den fastlåste,
anchor_owner-kryds-signerede kvitteringsnøgle. Et leaf, der droppes før sit anker, efterlader offeret en ikke-repuderbar kvittering at udgive, hvilket lukker den tavse-udeladelses-hullet. - Udgivet monitor-metodologi + prev-kæde-gennemgang — anker-
prev-kæden gennemgås hoved→genesis; en fork (to ankre ved énsizemed forskelligroot, eller en brudtprev) er udgiveligt bevis på misbrug. Equivokationsdetektion er en angivet operationel forpligtelse, ikke en tavs antagelse. - Dobbelt selvudgivne hoveder — hvert nyt hoved
{sth_hash, tree_size}posteres til et dedikeret qub-ejet offentligt, kun-tilføj GitHub-repository (det bærende manipulationspåviselige selvudgivelsesben), med et socialt opslag alene som best-effort-bekræftelse. En mislykket postering MUST page (ikke fejle tavst).
Hæderlighedsgrænse (bindende betingelse). Fordi qub kontrollerer begge posteringsoverflader, er dette selvudgivet, ikke uafhængigt bevidnet. Ingen produkt-, marketings- eller juridisk overflade må hævde, at loggen er "uafhængigt bevidnet"; den tilladte påstand er, at equivokation er detekterbar og efterlader en ikke-repuderbar kvittering. Et ægte uafhængigt tredjeparts-vidne er udskudt til en fremtidig §15-governance-bump.
received_at er operatør-påstået, og ingen påstand må læne sig på den — den fremvises aldrig som bevis eller som tvist-bekræftelse på nogen produkt-/juridisk-/API-/bevis-rendering-overflade. Arweave-anker-bloktiden T er det eneste tillidsløse tidsstempel (en øvre grænse på "logget af"). Enhver monitor-fornuftstjek på received_at MUST sammenligne mod T, ikke mod det operatør-kontrollerede anchored_at-STH-felt; et sådant tjek er et værn mod en hæderlig operatørs ur-fejl alene, ikke en ansvarlighedskontrol mod en ondsindet operatør (§16.15 Q5).
16.7 Anker-transaktionsformat og kadence
AnchorBundle er den kanoniske-CBOR Arweave-transaktions-body, skrevet via §16.8-bundleren: ver:u8, sth:bstr (kanoniske SignedTreeHead-bytes), prev_anchor:bstr (forudgående anker-tx-id rå bytes; udeladt ved genesis), chain_hash:tstr (drand-kæden i kraft — quicknet), og batchens leaf-CBOR-stream i seq-rækkefølge, så ankeret er selvindeholdt: en monitor genudleder root fra bodyen med nul qub-afhængighed. (Hvis leaf-streamen bliver stor ved høj volumen, kan en fremtidig revision forpligte kun et leaf-interval ved reference; noteret, ikke vedtaget i v1.)
Arweave-tags er bevidst optællelige — loggen er ment til at blive fundet, i modsætning til private 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 er uautoriserede hints; CBOR-bodyen er den eneste autoritet.
Kadence: dagligt som standard, revurderet med volumen (størrelses-triggeren auto-afkorter den effektive kadence under belastning); en betalt T3-forsegling tvinger et anker, så betalende kunder aldrig venter en dag. Anker-walleten er dedikeret og lav-velocity, adskilt fra upload-walleten — den MUST være sin egen JWK (en distinkt nøgle, ikke en logisk rolle på upload-walleten), så et upload-wallet-kompromis ikke kan forfalske ankre — med et hårdt anker-transaktionsbudget pr. dag (den degraderede ARWEAVE_DAILY_CEILING). Forvaringspositionen angives ligeud: en smal-scope varm nøgle med en stram circuit breaker og lav balance, ikke "kold" — en wallet, der auto-signerer dagligt, kan ikke være kold, og specifikationen foregiver ikke andet.
16.8 ANS-104-bundler
En in-house ANS-104-DataItem-encoder og deep-hash-signer, cirka 300 linjer, kun Web Crypto, nul npm-afhængigheder (begge Turbo-SDK'er fejler npm ci --ignore-scripts-supply-chain-værnet). DataItem-byte-layout:
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
Signering er Arweave deepHash — en rekursiv SHA-384-digest (Arweaves wire-krav, crypto.subtle.digest("SHA-384")) over ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — derefter RSA-PSS over deep-hashen med wallet-JWK'en via crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 her er isoleret som en kun-Arweave-wire-primitiv, aldrig en qub-tillidsprimitiv (§15 optegner hegnet; qub-tillidshashing er SHA3-256 hele vejen igennem).
Én kodevej betjener tre forbrugere: betalt T3-permanens pr. qub, Arweave-utilgængeligheds-fallbacken (sæt DataItem'et i kø, returnér R2-først-ack'en uanset — dette lukker den nuværende ARWEAVE_UNAVAILABLE-503-blindgyde) og skrivning af AnchorBundle. Signaturordning (afklaret — §16.15 Q8): v1 signerer med RSA-PSS (signaturtype 1) genbrugende den eksisterende Arweave-wallet-JWK-mekanisme (nul ny langlivet nøgle-forvaring, betjenende "én hemmelighed færre"-tesen); Ed25519 er udskudt til §15-PQ-migrationsvejen.
Den håndrullede deep-hash er den højeste-risiko, laveste-naturlige-dækning-kode i W5, så dens gating er ikke til forhandling (§16.15 Q8):
- Krydssprogs-fixturen
tlog_v1.json(Rust + TS, §14.5-wrapper_v1.json-mønstret) dækker deep-hash, DataItem-bytes + id, leaf-hashes, en 5-leaf-rod + audit path, en STH-hash, et inklusionsbevis og et konsistensbevis — i både sign- og verify-retningen (verify-retningen betyder noget, fordi §16.6's lokale tx → tx_id-tjek trækker deep-hashen ind i hver selvstændig verifikator, ikke kun skriveren). - En engangs-interop-rundtur gennem en reference-ANS-104-bundler, forbrugt som kun statiske testdata — aldrig en npm-runtime-afhængighed (kun-Web-Crypto-/ingen-install-scripts-positionen står ved magt).
- Deep-hash + RSA-PSS-vejen MUST rundture gennem de samme
crypto.subtle-primitiver, produktionen bruger, så in-house-encoderen er byte-kompatibel. - En løbende post-bundle-acceptmonitor bekræfter, at hvert anker / fallback-DataItem faktisk opnår Arweave-accept, med en alarm + circuit breaker — fordi deep-hashen også betjener Arweave-utilgængeligheds-fallback-køen, så en tavs regression ville fylde den kø med netværks-afviste poster under netop den nedetid, den findes for at dække.
16.9 Inklusions- og konsistensbeviser
Begge er RFC 9162, SHA3-256, serveret som kanonisk CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (den nøjagtige leaf-CBOR — verifikatoren genberegner selv leaf_hash og stoler aldrig på en leveret 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. En enkelt utvetydig nøgleliste, fastlåst af testvektor.
Selvstændig verifikation (ingen qub-server, udvider §11):
1. Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2. Read leaf.kind.
3a. kind=0x01 (attested):
assert leaf.ref == qub_id
assert leaf.body_hash == SHA3-256(body)
assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
assert leaf.ref == SHA3-256(qub_id || blind) // holder supplies blind
OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4. Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
using index/size; require derived root == proof.root.
5. Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
(do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
LogProfile.anchor_owner.
6. Parse AnchorBundle; require committed root == proof.root and size ==
proof.size; read the Arweave block time T.
7. Emit the claim scoped by leaf.kind (§16.11).
Bevis-serverende lager MUST være koordinat-nøglet (afklaret — §16.15 Q7, blokerende forudsætning). Bevisgenerering for et koldt leaf er korrekthedsneutral kun hvis R2-audit-materialet er et persistent Merkle-node-lager nøglet efter absolut træ-koordinat (level, index) — ikke node-deltaer pr. batch. Med et koordinat-nøglet lager er enhver (leaf i, size N)-audit-path et sæt af O(log N) direkte R2-GETs med ingen genberegning på tværs af batch-grænser; med et batch-nøglet lager er det ikke, hvilket er den lager-layout-mangel, denne afklaring lukker. Leaf-bodyerne er ligeledes indholdsadresserbare efter seq. En W5-testvektor MUST bevise et genesis-æra koldt leaf mod en meget senere rod ved hjælp af kun R2 + Arweave med LogDO-lageret slettet, så genvindings-sikkerheds-påstanden i §16.13 er underbygget frem for påstået. De O(log N) sekventielle R2-GETs hører kun hjemme på det asynkrone bevis-endpoint — aldrig på forseglings-hot-pathen (§16.10) eller en cron pr. tick.
16.10 R2-først ack-rækkefølge
POST /api/v1/upload-sekvensen bliver:
- Front-halvdel-gates (auth, validering, idempotens-shard-nøgle) — uændrede.
- Synkront
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)— holdbarhedsgulvet; lukker også W1's precache-race (tidligere etctx.waitUntilefter Arweave-submit'et). - Synkront
await LogDO.append(leaf)— én in-colo DO-RPC; den eneste skriver tildelerseq, udvider entry chain og opdaterer frontieren. (RMW på delt tilstand → DO, aldrig KV.)append-RPC'en gør kun det — detO(batch)Merkle-batch-lukke-arbejde kører uden for denne RPC på LogDO-alarmen, ellers spiker append-p95 hverLOG_BATCH_MAX_LEAVES-te forsegling. - Returnér ack'en nu — med forseglingskvitteringen (signeret af den fastlåste kvitteringsnøgle, §16.6) og
{ tx_id, log_seq, anchor_status: "pending" }. Den fler-sekunders Arweave-fan-out fjernes fra den kritiske vej. - Ét
ctx.waitUntilsætter det udskudte arbejde i kø: Arweave-submit'et pr. qub (nu best-effort / betalt; ved fejl rutes det til bundler-fallback-køen frem for at 503'e brugeren) plus de eksisterende provisoriske-meta-skrivninger. Batch-lukning og forankring kører uafhængigt fra LogDO-alarmen og den daglige anker-cron. Ingenctx.waitUntilinde i en løkke; den eksisterende idempotens-shard-nøgle bevares.
Latensbudget (afklaret — §16.15 Q7). < 300 ms p95-målet er et målt lanceringsgate, ikke en antagelse. Den hæderlige kritiske vej er front-halvdelens KV-læsninger + én R2-PUT + to serialiserede Durable Objects — den eksisterende QuotaDO-forseglingskvote-debet og den nye LogDO-append — så budgettet MUST tage højde for to in-colo DO-rundture, ikke én. Ship en LogDO-latensalarm, der spejler QuotaDO's, og behandl en p95-regression som en release-blocker.
16.11 Tillidsmodel — den præcise påstand, scopet efter leaf-kind
For kind=0x01 (attesteret): "Dette indhold — body matchende body_hash, identificeret af qub_id — blev forpligtet til qubs kun-tilføj-log på position seq og eksisterede senest ved Arweave-bloktid T; det var kryptografisk ulæseligt indtil drand-runde R = unlock_round(unlock_at)." Dette er den fulde {tlock-runde-binding + Merkle-inklusion + forankret rod}-triple.
For kind=0x02 (påstået, standarden): "En uigennemsigtig ciphertext med indholdsadresse chash, der hævder qub_id og unlock_at, blev forpligtet til kun-tilføj-loggen på position seq og eksisterede senest ved Arweave-bloktid T." Runde- og body-benene leveres af den eksisterende §11-.qub-bundle-verifikation (qub_core::unlock), ikke af loggen; hvad loggen tilføjer over en bar transaktion pr. qub er manipulationspåviselig rækkefølge, et tillidsløst øvre-grænse-forpligtelsestidspunkt og equivokationsresistens.
Begge påstande udelukker, pr. §11: forfatterskab uden sig_alg ≥ 0x01, hensigt og timing under anker-granularitet. Ingen af dem lader nogen påstand læne sig på received_at.
Påstandsloft (bindende lanceringsbetingelse — afklaret §16.15 Q1). For gratis / standard (kind=0x02)-qubs er den scopede kind=0x02-påstand ovenfor loftet for, hvad nogen produkt-, marketings-, vilkårs- eller bevis-rendering-overflade må hævde. Ingen overflade må angive eller antyde, at loggen beviser indholdet eller oplåsningsrunden af en standard-qub — loggen beviser rækkefølge + et tillidsløst øvre-grænse-forpligtelsestidspunkt for en uigennemsigtig ciphertext. Indholds- og runde-bevis kommer udelukkende fra den eksisterende §11-.qub-bundle-verifikation, som er log-uafhængig. Dette er en hård lanceringsblokering på copy, ikke en stilistisk præference; det er afklaringen, der holder den byte-blinde standardvej hæderlig.
16.12 Versionering og W3-koordinering
Der er ingen SealedQub-wire-bump og derfor ingen protokol-versions-bump (§12.1): loggen er en sidecar, der forpligter sig til eksisterende felter og bytes, så den indgår ikke i §12.2-protokol-versionshistorikken. W3's valgfrie drand_chain_version er urørt og forbliver det eneste valgfrie SealedQub-felt. Loggen introducerer i stedet sine egne uafhængige versionsrum — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — spejlende §12.4-wrapper-versions-uafhængigheden (wrapperen bærer en versions-byte uafhængig af protokolversionen, og log-versionerne følger samme adskillelse).
Bevis-levering hentes som standard, med en valgfri ride-along. Et bevis kan ikke eksistere på forseglingstidspunktet (ankeret er ikke skrevet endnu), så forseglingstids-.qub-bundlen forbliver bevis-fri. W7's verifikator henter GET …/proof én gang, eller rekonstruerer i fuldt-offline-tilstand beviset fra den offentlige AnchorBundle via en Arweave-forespørgsel på Log-Id. .qub-bundlen (W7) reserverer et valgfrit inclusion_proof-medlem — fraværende ved forsegling, udfyldt af en post-anker-re-eksport til kold arkivering — følgende samme "valgfri, udeladt som standard, additiv"-mønster som W3's drand_chain_version.
16.13 Retention
Retention-vinduer for LogDO-open-tail, R2-bevis-serverende-substratet, anker-circuit-breaker-tællerne og bundler-fallback-køen er specificeret i docs/DATA-RETENTION.md. Princip: loggens varme per-post-lager (LogDO) er genvindeligt post-anker; dens audit-materiale — det koordinat-nøglede (level, index)-Merkle-node-lager + de seq-adresserede leaf-bodyer (§16.9) + Arweave-ankrene — er permanent. At genvinde et koldt leaf fra DO'et invaliderer aldrig et udstedt bevis, fordi et bevis afgøres mod det permanente R2-node-lager og Arweave-ankeret, ikke DO'et (og §16.9's slettede-DO-testvektor beviser det).
16.14 Testvektorer
W5 shipper krydssprogs-fixturen tlog_v1.json (§16.8) plus gennemarbejdede vektorer: et kind=0x01- og et kind=0x02-leaf → leaf_hash; den 5-leaf kumulative rod; ét inklusionsbevis; ét konsistensbevis; ét AnchorBundle; og ét DataItem-id. Disse lever ved siden af §14.5-ydre-wrapper-vektorerne og udøves af både Rust- (qub-core) og TypeScript- (Worker) implementeringerne.
16.15 Gennemgangsbeslutninger (W5 — afklaret)
Den eksterne W5-gennemgang (en adversarisk designgennemgang + ejer-godkendelse) er afsluttet. Hver beslutning nedenfor er afklaret og afspejlet i §16-teksten ovenfor; de bindende lanceringsbetingelser er gentaget til sidst. Implementeringen kan fortsætte under dem.
- Standardvejs- (
kind=0x02) leaf-hæderlighed — AFKLARET. Ship den to-leaf-kind-opdeling som specificeret:kind=0x02forpligter hverkenbody_hashellerdrand_round. Intet*_body_hash-felt på den byte-blinde vej (det ville være det mest læselige falske "verificerede" signal for integratorer og er en bekvemmelighed, §11 allerede leverer fra bundlen). Kræv ikke server-seal for log-attesterede qubs (det ville tvinge klartekst gennem Workeren og ødelægge crypto-shredding-moaten). Enhver selvbeskrivende genvej hører hjemme i.qub-bundlen / bevis-envelopen som et verifikator-genberegnet felt, aldrig et leaf-felt. Ejer-bekræftet påstandsloft: §16.11. - Equivokations-/udeladelses-ansvarlighed — AFKLARET. Forseglingskvitterings-nøglen er fastlåst i
LogProfile+anchor_owner-kryds-signeret (lukker den tidligere "ingen signeringsnøgle"-modsigelse; §16.6). Lancerings-vidne-model: fastlåst kvittering + monitor-metodologi + prev-kæde-gennemgang + dobbelt selvudgivne hoveder (qub-ejet offentligt GitHub-repo, social best-effort), markedsført som detekterbar + kvitteret, aldrig uafhængigt bevidnet. Et ægte tredjeparts-vidne er udskudt til en §15-governance-bump. - Fastlåst anchor-owner-tillidsrod + rotation — AFKLARET. Vedtag
LogProfile-pinnet (§16.6); verifikatoren tjekkeranchor_tx.owner == anchor_ownerog verificerer tx data → tx_id-bindingen lokalt. Rotations-governance er en §15-udvidelse at bygge (§15.3-trigger tilføjet), ikke et genbrug; planlagte rotationer kryds-signerer, kompromis-drevne rotationer falder tilbage til §15-bumpen med fork-tjekket afgrænsende skaden. - Blinding af private qub-leaves — AFKLARET. Behold blinding for private qubs (
ref = SHA3-256(qub_id ‖ log_blind_secret)), råqub_idfor offentlige qubs (allerede §16.2.1),chashsom den selvstændige binding.log_blind_secreter en korrelations-/Sybil-grad-hemmelighed, kun-rotér-fremad (§16.2.1). received_at— AFKLARET. Behold den i leaf'en, forpligtet men eksplicit ikke-evidentiær; aldrig fremvist som bevis eller tvist-bekræftelse på nogen overflade. Ethvert monitor-fornuftstjek sammenligner mod Arweave-bloktidenT, ikke det operatør-kontrolleredeanchored_at(§16.6).- Gratis-niveau beviselig timing — AFKLARET (ejer-godkendelse). Holdbarheden regredierer ikke; kun det beviselige øvre-grænse-forpligtelsestidspunkt grovgøres til ankerets bloktid. Gratis-niveau-copy bruger ingen numerisk SLA ("…tilføjet ved næste loganker, typisk dagligt"); nøjagtig-time-bevis er en betalt T3-egenskab, oplyst på niveausammenligningsoverfladen + i vilkårene (§16.1).
- Kumulativt træ på Workers — AFKLARET. Enkelt kumulativt RFC 9162-træ + frontier-cachet eneste-skriver-LogDO (komfortabel headroom vs. det ~1k skrivninger/sek DO-loft; udskyd Merkle-of-shard-roots-sharding, indtil tæt på det). Blokerende forudsætning: koordinat-nøglet
(level, index)-R2-node-lager + den slettede-DO-koldt-leaf-testvektor (§16.9);< 300 mser et målt lanceringsgate over to serialiserede DO'er (§16.10). - ANS-104-signaturordning + deep-hash — AFKLARET. RSA-PSS (sig type 1, genbrugende den dedikerede anker-wallet-JWK); Ed25519 udskudt til §15-PQ-vejen. Den håndrullede SHA-384-deep-hash er gated på den begge-retninger-krydsimplementerings-fixture, et kun-statisk reference-bundler-interop-tjek, den delte-
crypto.subtle-rundtur og post-bundle-Arweave-accept-monitoren (§16.8).
Bindende lanceringsbetingelser (bær over i implementering + produkt-/juridisk gennemgang):
- Påstandsloft (Q1/Q6). Ingen overflade må sige, at loggen beviser en standard-qubs indhold eller oplåsningsrunde; den tilladte påstand er ordnet, manipulationspåviseligt, med et tillidsløst øvre-grænse-forpligtelsestidspunkt. Gratis-niveau-tidsstempel-copy bærer ingen numerisk latens; nøjagtig-time-bevis er kun betalt T3.
- Vidne-hæderlighed (Q2). Markedsfør equivokation som detekterbar + kvitteret, aldrig uafhængigt bevidnet.
- Kvitterings- + anker-nøgler (Q2/Q8). Kvitteringsnøglen er fastlåst + kryds-signeret; anker-walleten er sin egen JWK distinkt fra upload-walleten.
- Deep-hash-gate (Q8). Intet anker eller T3-tx shipper, før begge-retninger-fixturen + interop-tjekket består; acceptmonitoren pager ved fejl.
- Lager-forudsætning (Q7). Koordinat-nøglet node-lager + slettet-DO-koldt-leaf-vektor er forudsætninger for "genvinding invaliderer aldrig et bevis"-garantien.