qub-protokollspecifikation

qub är ett protokoll för kryptografiska temporala åtaganden: ett system för att försegla ord till ett framtida datum och bevisa, när det datumet kommer, exakt vad som sades och när.

Tre primitiver får det att fungera. drand är en decentraliserad slumpbaken — avslöjandedatumet upprätthålls av fysik, inte av någon parts goda vilja. Permanent offentlig lagring är ett manipuleringssäkert offentligt arkiv — ingen part kan redigera eller radera en qub när den har förseglats. ML-DSA-65 är en postkvantsignatur — varje qub är knuten till ett nyckelpar vars hemliga del aldrig lämnar författarens enhet.

Tillsammans gör dessa primitiver ett uttalande som är tidslåst, manipuleringsuppenbart och tillskrivbart — ett kvitto vars värde växer i takt med att världens förmåga att fabricera det förflutna förbättras.

Resten av detta dokument är den normativa specifikationen som krävs för interoperabla implementationer.


qub-protokollspecifikation

Fält Värde
Version 1.0 (protokollversion 0x01, yttre omslag version 0x01)
Datum 2026-05-01
Status Utkast
Granskat till 2026-05-01

Detta dokument är den normativa protokollspecifikationen för qub-systemet för tidsbestämda åtaganden. Det definierar datastrukturer, serialiseringsregler, härledningsformler och verifieringsprocedurer som krävs för interoperabla implementationer.

Omfattning: protokollskiktet är avsiktligt språkneutralt — qub-kroppen är ogenomskinlig klartext / markdown / paktbyte, och språkmedveten rendering är läsarens ansvar (qub.social-webbapp, <qub-embed>-iframe, MCP-klienter, etc.).


1. Notation och konventioner

Notation Betydelse
u8, u64, i64 Osignerade/signerade heltal med specificerad bitbredd
[u8; N] Bytearray med fast längd om N byte
Vec<u8> Bytearray med variabel längd
Option<T> Värde av typ T, eller frånvarande
String UTF-8-textsträng, NFC-normaliserad
`
SHA3-256(x) NIST SHA3-256-hash av bytesträngen x (FIPS 202)
ceil(x) Takfunktion: minsta heltal ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Mest signifikant byte först

Alla heltal i pre-image-konstruktioner kodas som big-endian bytearrayer med fast bredd (i64 → 8 byte, u8 → 1 byte) om inte annat anges.

Alla tidsstämplar är Unix-sekunder i UTC.


2. Datastrukturer

2.1 ComposeQub (skaparens tillstånd i minnet)

Serialiseras inte till CBOR. Skrivs inte till permanent lagring. Lokal för skaparappen.

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 (dekrypterad nyttolast)

Serialiseras med kanonisk CBOR (§3). Krypterad inuti SealedQub. Detta är strukturen som bevisar innehållsintegritet 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 — när verkligheten avkunnar sin dom (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
}

Baslinje (osignerad text-qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, alla Option-fält frånvarande.

Andra v1-konfigurationer: content_type = 0x03 (paktkropp, se §6.1); sig_alg = 0x01 (ML-DSA-65) med author_signature och author_pubkey närvarande (se §9.3); cosigner_pubkey och cosigner_signature närvarande tillsammans för motundertecknade pakter (se §9.7); reply_to satt till föräldra-qubbens qub_id för svarskedjor (se §9.3 för signaturomfångskonsekvenserna).

2.3 SealedQub (kanoniskt trådformat)

Serialiseras med kanonisk CBOR (§3). Skrivs till permanent lagring. Detta är artefakten på kedjan.

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 — visas på verdict-watch-CTA
                                        //   före avslöjande; speglar QubEnvelope.outcome_at;
                                        //   bundet till qub_id via §4.1-pre-imagen.
    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äsarens applikationstillstånd)

Serialiseras inte till CBOR. Lokal för läsarappen. Konstrueras efter framgångsrik dekryptering och verifiering.

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 — bärs vidare från QubEnvelope.outcome_at / SealedQub.outcome_at; driver avslöjandesidans verdiktbevakningsblock (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

All serialisering av SealedQub och QubEnvelope MUST följa denna profil. Två implementationer som ges samma logiska struktur MUST producera identiska byte.

3.1 Kodningsregler

Regel Specifikation
Standard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Nyckelordning i kartor Sorterade efter kodad bytelängd först (kortare före längre), sedan lexikografiskt (byte-för-byte för kodningar av samma längd)
Heltalskodning Kortaste form: 0–23 i initial byte; 24–255 i 2 byte; 256–65535 i 3 byte; etc.
Längdkodning Endast bestämda längder. Inga arrayer, kartor, bytestrings eller textstrings av obestämd längd (additional info = 31 är förbjudet).
Taggar Inga CBOR-taggar (major-typ 6 är förbjudet).
Flyttal Inga flyttal (major-typ 7 värden 0xF9–0xFB är förbjudna).
Textstrings UTF-8-kodade, NFC-normaliserade (Unicode Normalization Form C).
Bytestrings Råa byte. Ingen base64-kodning i CBOR-skiktet.
Dubblettnycklar Avvisa med fel. Tolkare MUST NOT tyst acceptera dubblettnycklar i kartor.
Okända nycklar Avvisa med fel. Tolkare MUST NOT tolerera kartnycklar utanför typens kanoniska nyckeluppsättning — två distinkta kanoniska bytesträngar får aldrig avkodas till samma värde (encode(decode(x)) == x), och för signerade nyttolaster vore en extra nyckel dolt innehåll som båda signaturerna åtar sig. Schemautveckling sker genom version, aldrig genom extra nycklar.
Enkla värden Endast true (0xF5), false (0xF4) och null (0xF6) är tillåtna.
Frivilliga fält Frånvarande frivilliga fält utelämnas helt från CBOR-kartan (kodas inte som null). Närvarande frivilliga fält inkluderas i sorterad nyckelordning.

3.2 Verifierade kanoniska nyckelordningar

Dessa nyckelordningar är normativa. Implementationer MUST avge nycklar i exakt denna ordning. Debug-assertioner SHOULD verifiera ordning i icke-release-byggen.

QubEnvelope (version 0x01, osignerad, alla frivilliga fält frånvarande):

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

Härledning av QubEnvelopes nyckelordning: varje nyckel är en CBOR-textsträng. Kodad längd = 1 byte header + stränglängd (för strängar under 24 byte). Sortera först efter total kodad längd, sedan lexikografiskt för nycklar av samma längd.

SealedQub (version 0x01, publik, ingen mottagare):

"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 (paktkropp, 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 (rad i terms-arrayen):

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

PartyIdentifier (party_a / party_b-karta):

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

3.3 Referens för bytekodning

Typ CBOR-kodning Exempel
SHA3-256-hash (32 byte) 0x58 0x20 + 32 byte body_hash, qub_id
Tidsstämplar (i64) Major-typ 0 (positiv) eller 1 (negativ), kortaste kodning Unix-sekunder
Version (u8, värde 1) 0x01 (enskild byte)
Innehållstyp (u8, värde 1) 0x01 (enskild byte)
sig_alg (u8, värde 0) 0x00 (enskild byte)
ML-DSA-65-signatur (3 309 byte) 0x59 0x0C 0xED + 3 309 byte author_signature, cosigner_signature
ML-DSA-65 publik nyckel (1 952 byte) 0x59 0x07 0xA0 + 1 952 byte author_pubkey, cosigner_pubkey

4. Normativa härledningar

4.1 qub_id

qub_id identifierar unikt en qub och binder QubEnvelope till SealedQub. Den härleds deterministiskt från envelopinnehåll.

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 av domänseparator: Strängen "QUB_ID_V2" är 9 ASCII-byte. En enskild 0x00-utfyllnadsbyte läggs till för att nå 10 byte för justering. Implementationer MUST använda exakt dessa 10 byte: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Kodning av outcome_at: V1.1 utvidgade pre-imagen från 92 till 100 byte för att vika in det frivilliga outcome_at-fältet i bindningen. Frånvarande outcome_at kodas som 8 nollbyte; protokollvalidatorerna avvisar outcome_at <= 0 överallt så att detta sentinelvärde inte kan kollidera med ett legitimt värde. Se §3.2 (trådformat) och den interna tasks/verdict-uplift-plan.md för verdiktmekaniken som motiverar detta fält.

Kodning av drand_round: V1.2 utvidgade pre-imagen från 100 till 108 byte för att vika in drand_round (mål-drand-rundan, §4.3) i bindningen, och höjde domänseparatorn till QUB_ID_V2. Detta binder tidslåsrundan in i qub-identiteten: en gateway kan inte binda om chiffertexten till en annan runda (t.ex. en redan passerad) än den som det visade unlock_at antyder. Upplåsningsproceduren (§8) verifierar dessutom att rundan som är inbakad i tlock-chiffertextens stanza matchar unlock_round(unlock_at), så att den visade upplåsningstiden bevisligen är den runda som styr dekrypteringen.

Egenskaper:

4.2 body_hash

body_hash = SHA3-256(body)

Där body är den råa Vec<u8>-innehållsnyttolasten. För text-qubs är detta den UTF-8-kodade qub-kroppen.

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

Där title är den frivilliga klartexttiteln som visas på läsarens nedräkning före avslöjande (se §3.2). NFC-normalisering körs vid hashtid så att sammanfattningen är stabil över visuellt likvärdiga kodpunktssekvenser. Sentinelvärdet med alla nollor är reserverat för det frånvarande fallet; en tom sträng avvisas vid den kanoniska CBOR-gränsen som en icke-kanonisk kodning av "frånvarande" (den kanoniska kodningen utelämnar fältet helt).

4.3 Mappning av upplåsningsrunda

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Parameter Källa Exempel
unlock_at Användarvald Unix-sekund 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äljer första drand-rundan vars avslöjandetid är ≥ unlock_at. Detta säkerställer att qubben inte blir dekrypterbar före den valda upplåsningstiden.

Gränsfall: om (unlock_at - chain_genesis_time) är exakt jämnt delbart med chain_period_seconds är resultatet exakt den rundan — qubben låses upp precis vid den rundans avslöjandetid.

Validering: unlock_at MUST ligga i framtiden vid förseglingstid. unlock_at MUST NOT vara mer än 10 år från created_at (för att begränsa risken av långhorisont-drand-beroende; gränssnittet SHOULD varna för upplåsningsdatum bortom 2 år).


5. Newtypes för trådformat

Newtypes för trådformat ger kompileringstidssäkerhet mot att förväxla CBOR-byte med JSON, rå klartext eller andra bytekodningar.

Typ Innehåller Producerad av Konsumerad av
SealedQubCbor Kanonisk CBOR av SealedQub serialize_sealed_qub() Uppladdning till permanent lagring, läsarhämtning
QubEnvelopeCbor Kanonisk CBOR av QubEnvelope serialize_qub_envelope() tlock-krypteringsinmatning, tlock-dekrypteringsutmatning

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 vid konstruktion

from_encoded() SHOULD validera att indata börjar med ett giltigt CBOR-kartheader. Fullständig strukturell validering sker vid tolkningstid, inte konstruktionstid, för att undvika dubbeltolkning.


6. Register för innehållstyper

Värde Typ Max kroppsstorlek Anmärkningar
0x00 Reserverad (ogiltig) MUST NOT användas
0x01 Klartext (UTF-8, begränsad Markdown) 50 KB betald / 10 KB gratis Se §10 för renderingsregler. Uppdelningen gratis / betald upprätthålls av uppladdningstjänsten; protokollskiktets hårda tak är 50 KB.
0x02 Reserverad (framtida) Tilldelad för en framtida innehållstyp; ogiltig i v1. Läsare MUST avvisa enligt regeln nedan.
0x03 Pakt (bilateralt avtal, CBOR-kropp) 100 KB Kroppen är kanonisk CBOR PactTerms (§6.1). Motundertecknarsignering enligt §9.7.
0x04 Verdikt (skaparens självbetygsättning, CBOR-kropp) 8 KB Kroppen är kanonisk CBOR VerdictBody (§6.2). Avges endast av den systemsidiga verdict-intentionen. Föräldrarelationen ligger på Parent-Tx-Id-Arweave-taggen, inte på kroppen. Se verdict-uplift-plan §3.4.

Läsare MUST avvisa okända innehållstyper med ett tydligt användarsynligt felmeddelande. Läsare MUST NOT försöka rendera okända typer som text.

6.1 Paktkropp (content_type = 0x03)

En paktkropp är den kanoniska CBOR-kodningen av ett PactTerms-värde:

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

Kanoniska CBOR-nyckelordningar för alla tre kartorna anges i §3.2. Totalt serialiserad pakt-CBOR MUST NOT överskrida 100 KB (matchar §6).

Schemadiskriminator. Den första raden i terms för en structured/v1-pakt MUST vara { key: "pact_schema", value: "structured/v1" }. Rader utan denna markör är "custom"-pakter och får ingen strukturerad validering eller schemamedveten rendering.

Frusna bekräftelseplatser. structured/v1-pakter bär exakt fyra bekräftelserader under dessa nycklar:

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

value för var och en är en av åtta frusna engelska strängar valda av paret (role, kind), där role ∈ { seller, buyer, provider, client } och kind ∈ { standard, capacity }. Strängarna själva är normativa protokolldata — båda parternas ML-DSA-65-signaturer förbinder sig till de exakta bytena via body_hash. De är INTE lokaliserade; den signerade kroppen är språkneutral. Varje formuleringsändring kräver en ny schemaversion (structured/v2).

De åtta strängarna, deras uppslagning (acknowledgement_for(role, kind)) och motiveringen för var och en är fastställda av referensimplementationen. Konforma implementationer MUST avge byte-identiska bekräftelsevärden; golden-fixture SHA3-256 body-hash-tester som täcker alla fyra rollkombinationer fångar all drift.

Visningsordning för läsare. Bekräftelsesträngarna innehåller fraser såsom "described above", som förutsätter att beskrivning / omfattning-raderna renderas före bekräftelserna. Läsare MUST rendera terms-arrayen i CBOR-ordning; omordning bryter prosasemantiken.

Motpartskontakt. När Part B:s contact är en giltig e-postadress skickar qub-uppladdningstjänsten automatiskt en granskning / motundertecknings-inbjudan via e-post vid stage-tid och binder den slutliga motunderskriften till verifiering av samma adress (§9.7). Pakter vars Part B-kontakt är frånvarande kan fortfarande motundertecknas, men endast via en kanal utanför bandet — tjänsten avvisar motunderskriftsförfrågningar som inte kan producera en matchande 15-minuters e-postverifieringsmarkör.

6.2 Verdiktkropp (content_type = 0x04)

En verdiktkropp är den kanoniska CBOR-kodningen av ett VerdictBody-värde:

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-nyckelordning:

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

Totalt serialiserad verdikt-CBOR MUST NOT överskrida 8 KB (matchar registerraden ovan).

Utfallsenum. Wire-byten är intentionsneutral; de fyra kategorierna Right / Partial / Wrong / Unfalsifiable täcker varje verdiktbärande intentions utfallsrymd. Per-intentionsetiketter ("Rätt sagt" / "Höll det" / "Levererat" / "Bekräftad" för Right, etc.) är en läsarsidig renderingsfråga som löses mot föräldra-qubbens intention — wire förblir språk- och intentionsneutral. Värden utanför 1..=4 MUST avvisas vid avkodning.

Föräldralänkning. En verdikt-qub bär INTE föräldrareferensen i sin kropp. Föräldra-qubbens Arweave-transaktions-id avges som Parent-Tx-Id-lagringstaggen vid uppladdning (§7 lagringstaggsskikt). Detta håller kroppen som ett självständigt signerat uttalande av självbedömning; granskningskedjan ("rätt om vad?") etableras via Arweave-taggsökningen.

Säkerhet för bevis-URL (normativt). När evidence_url är närvarande MUST validerare (kompositionssidan, wire-sidan, Worker-kanten) upprätthålla:

  1. Endast HTTPS. Strängen MUST börja med bytesekvensen https://. Varje annat schema — http, ftp, javascript, data, file, etc. — avvisas.
  2. Längdtak. ≤ 2 048 byte (webbläsarens praktiska URL-gräns).
  3. NFC + kontroll av fientliga kodpunkter. Samma regel som för title och reflection — bidi-override / nollbredd / tag-block / BOM / C0 / C1-kodpunkter avvisas. Definitionen matchar Rust-crate::handle::contains_hostile_text_codepoint och TS-workers/api/src/utils/unicode.ts::isHostileCodepoint (håll i lås).
  4. Inga blanksteg, inga ASCII-styrtecken. Blanksteg / DEL / byte under 0x20 var som helst i URL:en avvisas — stänger \n/\t-injektionsvektorn som bidi-regeln inte täcker.
  5. Icke-tomt värdsegment. Allt mellan https:// och första /, ? eller # MUST vara icke-tomt.

Ingen serversidig hämtning. Workern MUST NOT proxyera, hämta eller förhandsgranska URL:en. Protokollet lagrar en sträng; renderingen sker läsarsidigt med rel="nofollow noopener noreferrer" target="_blank" och en synlig värd visad bredvid länktexten.

Reflektion. Frivillig skaparskriven reflektionstext ("vad förändrades, vad lärde du dig"). Samma NFC + kontroll av fientliga kodpunkter som för title. Tom / endast-blanksteg-inmatning faller till frånvarande vid konstruktion.

Schemaversion. v1 stöder endast verdict_version = 0x01. Framtida schemarevisioner bumpar denna byte och landar tillsammans med en ny protokollversion enligt §12.


7. Förseglingsprotokoll

Den fullständiga förseglingssekvensen. Varje steg är 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.

Lagringstaggskikt (utanför bandet). qub-uppladdningstjänsten fäster en avsiktligt liten uppsättning lagringstransaktionstaggar tillsammans med den inslagna nyttolasten. Content-Type=application/octet-stream krävs normativt. Referenstjänsten fäster dessutom tre frivilliga taggar när skaparen väljer att lyfta fram dem: Intent (allowlist-validerad kompositionsavsikt — t.ex. quote, reply, commitment), Author (skaparens §9.3-pubkey-fingeravtryck som 64-teckens gemen hex) och Parent-Tx-Id (föräldra-qubbens lagringstransaktions-ID för svarskedjor, 43-teckens base64url).

Author-taggen är opt-in per qub: referensskaparappen fäster den endast när användaren uttryckligen aktiverar offentlig tillskrivning vid förseglingstid. När växeln är av — standardläget — skrivs ingen Author-tagg och qubben är otillskriven på kedjan: inget i permanent lagring länkar uppladdningen till en skapares handtag, e-post eller andra qubs. När växeln är på löses Author-fingeravtrycket till skaparens valda @handle via §9.5-attesteringskedjan. Svarskedjerelationer och Intent är icke-identifierande. Det yttre omslaget (§13) skyddar den inre kroppen från chiffertextkorrelation — och förhindrar att en skördare känner igen och massdekrypterar qub-formade uppladdningar efter att deras drand-runda publiceras.

Referenstjänsten fäster avsiktligt INTE App-Name-, App-Version- eller Type-taggar: varje sådant ensamvärdesfilter skulle returnera hela qub-korpusen till en GraphQL-fråga, vilket är inkonsekvent med omslagets kroppsbara konfidentialitetsomfång.

En konform verifierare MUST NOT vara beroende av någon lagringstagg för §11 tredjepartsverifiering; body-hashen / qub_id / signaturen förbinder sig endast till den inre CBOR:en, aldrig till taggsetet.


8. Upplåsningsprotokoll

Den fullständiga upplåsningssekvensen. Varje steg är 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. Signering av författarskap

9.1 Motivering

qubs lagras i permanent lagring. Signaturer för författarskap måste förbli oförfalskbara på obestämd tid, varför v1.0 använder det postkvantsäkra schemat ML-DSA-65 (FIPS 204) snarare än ett klassiskt schema vars säkerhet kan försämras inom qubbens permanenta livstid.

9.2 Algoritmregister

sig_alg Schema Nyckelstorlek Signaturstorlek
0x00 Ingen signatur (osignerad)
0x01 ML-DSA-65 (FIPS 204) 1 952 byte 3 309 byte

Läsare MUST avvisa okända sig_alg-värden.

9.3 Konstruktion av signerad pre-image

Två pre-image-versioner har funnits. Alla signaturer MUST använda V2, och verifierare MUST endast acceptera V2. Den äldre V1-pre-imagen (dokumenterad nedan som historisk referens) accepterades som en enbart-verifierings-fallback under migreringen till V2; den fallbacken har avvecklats och en signatur som endast är V1 avvisas nu.

V2 (aktuell — produceras av all ny författarsignering, och av båda signaturerna i paktens staging-/motundertecknarflöde):

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öljer samma frånvaro-sentinel-konvention som title_hash (§4.2.1): 32 nollbyte är inte en giltig SHA3-256-utdata, så "frånvarande" kan aldrig kollidera med en närvarande etikett. Alla fält är av fast bredd, så pre-imagen är otvetydig utan längdprefix.

V1 (äldre — AVVECKLAD; produceras inte längre och accepteras inte längre vid verifiering):

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-pre-imagen utelämnade sender_label och reply_to. Den accepterades som en enbart-verifierings-fallback under migreringen till V2; den fallbacken har sedan dess avvecklats — verifierare MUST endast acceptera V2-pre-imagen. Definitionen behålls här som historisk referens och för att förklara domänseparatorn nedan. En signatur som endast verifierar mot V1 MUST behandlas som ett verifieringsfel.

Domänseparatorer: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" är 17 ASCII-byte vardera ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Ingen utfyllnad. Den skilda separatorn domänseparerar de två konstruktionerna, så en signatur över en pre-image kan aldrig verifiera som den andra.

org_id_present-byte: byten efter unlock_at MUST vara 0x00. Referensimplementationen exponerar detta som konstanten ORG_ID_PRESENT_INDIVIDUAL = 0x00 i crates/qub-core/src/signing.rs; läsare som rekonstruerar sig_input för verifiering MUST avge samma byte.

Signaturomfång — vad som är och inte är täckt. V2-sig_input förbinder sig direkt till version, qub_id, body_hash, unlock_at, sender_label och reply_to (plus den fasta domänseparatorn och org_id_present-byten). qub_id härleds själv från version, content_type, created_at, unlock_at, outcome_at, drand_round och body_hash via §4.1-pre-imagen, så varje ändring av dessa fält producerar ett annat qub_id och ogiltigförklarar signaturen transitivt. Den autentiserade ytan är därför:

Fält Autentiserad av signatur Hur
version Direkt indata till sig_input
qub_id Direkt indata
body_hash Direkt indata
unlock_at Direkt indata
sender_label Direkt indata via sender_label_hash (V2-pre-image — den enda accepterade formen)
reply_to Direkt indata via reply_to_or_zero (V2-pre-image — den enda accepterade formen)
content_type Transitivt, via qub_id pre-image
created_at Transitivt, via qub_id pre-image
outcome_at Transitivt, via qub_id pre-image
drand_round Transitivt, via qub_id pre-image (V1.2)
body Transitivt, via body_hash = SHA3-256(body)
author_pubkey — (implicit) Nyckeln som verifierade signaturen är författaren per definition
cosigner_pubkey / cosigner_signature Oberoende signerad över samma sig_input (se §9.7)
drand_chain_id, tlock_ciphertext, visibility Yttre SealedQub-fält, inte inuti envelopen — täckta av sina egna strukturella invarianter (runda / kedjekonsistens) men inte av författarsignaturen. (drand_round binds nu transitivt via qub_id-pre-imagen — se ovan.)

Varför V2 är den enda accepterade pre-imagen.

Implementationer som visar sender_label eller reply_to för slutanvändare MUST lyfta fram den autentiserade identiteten (pubkey-fingeravtryck, attestering) som den primära identitetssignalen, inte etiketten.

9.4 Verifieringsprocedur

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

Signaturverifiering är den dyraste operationen (särskilt ML-DSA-65). Den SHOULD utföras efter att alla billigare kontroller (hash, qub_id, unlock_at) har passerat.

9.5 Identitetsattesteringar

Identitetsattesteringar — mappningen av author_pubkey till mänskligt igenkännbara identitetspåståenden såsom ett qub-handtag, en e-postadress, ett socialt handtag eller en passkey-uppgift — är en progressiv förbättring på läsarsidan och är inte obligatoriska för signaturverifiering. Läsare som löser attesteringar till en visningsidentitet MUST tillämpa företrädet:

handle > email > social > fingerprint

Fingeravtrycks-fallbacken är gemen hex av SHA3-256(author_pubkey); den är alltid tillgänglig för varje signerad qub. Läsare MAY förkorta den för visning — referensläsaren renderar qub: följt av de första och sista fyra byten (qub:<8 hex>…<8 hex>).

En konform verifierare kan slutföra varje kontroll i §9.4 utan att kontakta qub-API:et, utan något nätverk utöver permanent lagring och drand, och utan någon serversideuppslagning. Attesteringsupplösning är ett separat steg på bästa möjliga sätt som utförs endast efter att signaturverifiering har lyckats.

9.6 Storlekspåverkan

Ed25519 ML-DSA-65
Signatur 64 byte 3 309 byte
Publik nyckel 32 byte 1 952 byte
Totalt per qub 96 byte 5 261 byte
Lagringskostnadsdelta (vid ~5 USD/MB) ~0,0005 USD ~0,026 USD

För en text-qub på 500–2 000 byte tredubblar ML-DSA-65 ungefär den lagrade storleken. Den absoluta kostnaden är försumbar.

9.7 Motundertecknarverifiering (bilaterala paktavtal)

För bilaterala avtal (content_type = 0x03) bevisar ett andra signaturskikt att båda parter samtyckte till samma villkor.

Envelopfält:

Båda fält MUST vara närvarande tillsammans eller båda frånvarande. Om exakt ett är närvarande MUST läsare rapportera ett integritetsfel.

Verifieringsprocedur:

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

Egenskaper:

Grind för e-postbindning (operativt). När en staged pakt bär en Part B-e-postkontakt (§6.1) MUST qub-uppladdningstjänsten avvisa motundertecknarförfrågan om inte en kortlivad e-postverifieringsmarkör finns som matchar både staging-ID:t och den normaliserade e-posthashen av den kontakten. Markören skrivs av /api/v1/auth/verify när magic-link-token bär en staging_id och den verifierade adressen matchar SHA-256(normalise_email(party_b.contact)) — där normalise_email(addr) bevarar lokaldelens skiftläge och gemener endast domändelen (enligt RFC 5321 §2.3.11), och SHA-256 här är NIST FIPS 180-4-hashen (skild från SHA3-256 som används i §4-härledningar) — och förfaller 900 sekunder (15 minuter) efter utfärdande. Detta är en operativ anti-imitations-grind, INTE en del av qub-beviset på kedjan — en tredjepartsverifierare som spelar upp §11 behöver endast permanent lagring och drand, utan någon serversideuppslagning. Markören finns endast på serversidan och är aldrig en del av den signerade kroppen.

Storlekspåverkan (ML-DSA-65 författare + motundertecknare):

Komponent Storlek
Författarsignatur 3 309 byte
Författares publika nyckel 1 952 byte
Motundertecknarsignatur 3 309 byte
Motundertecknares publika nyckel 1 952 byte
Totalt kryptoöverhang 10 522 byte
Lagringskostnadsdelta ~0,05 USD

10. Markdown-rendering och sanering

Denna sektion är säkerhetskritisk. Läsaren renderar text-qubs (content_type = 0x01) med en begränsad Markdown-delmängd.

10.1 Tillåtna element

10.2 Förbjudna element

Element Hantering
Rå HTML (<div>, <script>, etc.) Tas helt bort. Ingen HTML passerar igenom.
Bilder (![alt](url)) Tas bort. Bildsyntax tas bort från utdata.
Länkar ([text](url)) URL renderas som synlig klartext. Inte auto-länkad. Inte klickbar utan explicit användaråtgärd.
Farliga URL-scheman javascript:, data:, vbscript:, file: — tas bort.
Iframes, embeds, objects Tas bort.
HTML-entiteter Avkodas till visningstecken endast om de är säkra.

10.3 Implementation

Implementationer MUST använda en strikt allowlist-tolkare, inte en blocklist. Den rekommenderade metoden:

  1. Tolka Markdown med pulldown-cmark (eller motsvarande).
  2. Gå igenom AST:n och släng varje nod som inte finns i allowlist (§10.1).
  3. För länknoder: avge URL:en som synlig text, inte som ett klickbart <a>-element.
  4. Konvertera den filtrerade AST:n till en typad mellanrepresentation (t.ex. en MarkdownNode-enum med endast säkra varianter). Rå HTML är strukturellt icke-representerbar i denna IR.
  5. Rendera från den typade IR:n till mållagrets vy (t.ex. reaktiva vykomponenter, DOM-noder). Ingen HTML-strängkonkatenering eller innerHTML vid någon punkt.

Blocklist-metoder är ömtåliga eftersom nya Markdown-utvidgningar eller tolkningskvistar kan introducera ofiltrerade element. Den typade AST-metoden gör XSS strukturellt omöjligt — det finns ingen variant som kan bära godtycklig HTML.

10.4 Storleks- och strukturgränser


11. Tredjepartsverifiering

Vilken tredje part som helst kan verifiera en publik qub utan qubs medverkan. Verifieringsproceduren:

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.

Vad verifiering bevisar:

Bevis Vad det fastställer
Åtagande Chiffertexten existerade vid lagringsblockets tidsstämpel.
Integritet Klartextkroppen matchar den åtagna hashen och har inte ändrats.
Tidsbestämning Innehållet var oläsbart fram till drand-rundan, som motsvarar den valda upplåsningstiden (med förbehåll för tlock- och drand-säkerhetsantaganden).

Vad verifiering INTE bevisar:

Icke-bevis Varför
Författarskap sender_label är dekorativ. Utan sig_alg0x01 kan vem som helst ha förseglat detta innehåll.
Avsikt qubben bevisar innehåll och tidsbestämning, inte vad skaparen subjektivt menade.
Före-händelsetidsbestämning Lagringsblockinkludering kan släpa efter faktisk uppladdning med minuter. Åtagandetidsstämpeln är blocktiden, inte det ögonblick användaren tryckte "försegla".

12. Versionshantering

12.1 Protokollversion

version-fältet (u8) i både SealedQub och QubEnvelope identifierar huvudprotokollversionen.

12.2 Versionshistorik

Version Värde Beskrivning
v1 0x01 Publika text-qubs (content_type 0x01), bilaterala paktavtal (0x03, schemat structured/v1, ML-DSA-65 författare + motundertecknare), tlock, SHA3-256

12.3 Framåtkompatibilitet

En v1-läsare som stöter på en QubEnvelope med okända CBOR-kartnycklar (nycklar som inte finns i §3.2-kanonisk ordning) MUST avvisa den med ett avkodningsfel (§3.1). Framåtkompatibilitet vilar på version-fältet, inte på nyckeltolerans: framtida tillägg — även mindre metadata — levereras under ett nytt version-värde, som en v1-läsare avvisar med ett tydligt "nyare protokoll"-fel snarare än att tyst släppa innehåll som signaturerna åtar sig.

En v1-läsare som stöter på sig_alg = 0x01 (ML-DSA-65) men saknar ML-DSA-65-verifieringsstöd SHOULD visa qub-innehållet med ett meddelande om "signatur närvarande men inte verifierbar", inte avvisa qubben helt. Referensimplementationen idag avvisar varje sig_alg-värde annat än 0x00 och 0x01 eftersom v1-registret inte innehåller någon annan giltig algoritm — strikt avvisande och soft-fail är observationellt identiska tills en tredje algoritm registreras. Soft-fail-beteendet ovan blir bärande när §9.2 inför en ny post, och referensläsaren kommer att uppdateras för att soft-faila vid den tidpunkten.

12.4 Yttre omslagsversion

OuterWrappern som beskrivs i §13 bär sin egen version-byte, oberoende av SealedQub.version och QubEnvelope.version. De två versionsutrymmena utvecklas separat: en framtida postkvantsäker symmetrisk ersättning ökar omslagsbyten utan att röra den inre protokollversionen, och ett framtida protokollskiktstillägg (t.ex. ett nytt envelopfält) ökar den inre versionen utan att röra omslagsbyten.

OUTER_WRAPPER_VERSION_* Värde Algoritm Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM med 12-byte nonce, 16-byte autentiseringstagg, AAD bunden till qub_id v1-standard
0x020xFF Reserverad Framtid

Läsare MUST avvisa okända omslagsversioner med ett tydligt felmeddelande. Protokollet håller avsiktligt omslagsversionsutrymmet smalt tills en konkret migrationsdrivare dyker upp (t.ex. NIST-vägledning som föredrar en annan AEAD); en 0x02-plats kommer att tilldelas i samma revision som introducerar algoritmen.


13. Yttre krypteringsomslag

13.1 Motivering

Protokollskikten (QubEnvelope → tlock → SealedQub) gör en förseglad qub tidslåst: kroppen är oläsbar fram till unlock_at och drand-rundsignaturen har publicerats. Efter upplåsning är emellertid rundsignaturen offentlig och den kanoniska CBOR-formen för SealedQub är igenkännbar, så en skördare som indexerade transaktioner i permanent lagring skulle kunna massdekryptera hela qub-korpusen.

Det yttre krypteringsomslaget stänger den kanalen genom att lägga in ett ytterligare symmetriskt AEAD-skikt mellan den kanoniska SealedQubCbor och de byte som skrivs till permanent lagring. Den 256-bitars nyckeln K lever endast i URL-fragmentet på leveranslänken och på användarenheter; webbläsare överför inte URL-fragment till servrar, så qub.social, varje lagringsgateway och varje CDN framför båda är observationellt blind för K. Varje qub i permanent lagring är därför en ogenomskinlig chiffertext vars klartext är oåterhämtningsbar utan URL:en som skaparen valde att dela.

Nettoeffekt:

13.2 Skiktning

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)

Försegling och upplåsning vid protokollskiktet (§7, §8) är oförändrade under omslagsgränsen; omslaget fästs vid anropsplatsen för seal() och tas bort vid anropsplatsen för unlock().

13.3 Datastruktur för OuterWrapper

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
}

Fältinvarianter.

CBOR-kodning. Kanonisk CBOR enligt §3, med samma nyckelordningsregel (sorterad efter kodad bytelängd stigande, sedan lexikografiskt). De fyra nycklarna är:

Nyckel Kodade byte Ordning
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Den första byten av OuterWrapper-CBOR:en är därför det bestämda kart-headern för en 4-posters karta (0xA4).

13.4 AAD-bindning till qub_id

Omslaget binder qub_id som AEAD ytterligare autentiserade data. Detta är det bärande strukturella försvaret mot tre attackklasser:

Attack Försvar
Flytta chiffertext under ett annat qub_id-fält i omslaget AAD-omatchning → AEAD-autentisering misslyckas
Blanda URL-fragmentet för qub A med byten i permanent lagring för qub B AAD-omatchning → AEAD-autentisering misslyckas
Manipulera qub_id-fältet i omslaget efter uppladdning AAD-omatchning → AEAD-autentisering misslyckas

Att bära qub_id i omslagets klartext försvagar inte uppräkningsimmuniteten meningsfullt — qub_id är själv en SHA3-256-hash av §4.1-pre-imagen utan återställbar pre-image från sammanfattningen, och en uppräknare som redan skördade omslagsbyten lär sig inget från det synliga qub_id som de inte kunde sluta sig till från själva uppladdningens existens.

13.5 Inslagnings- och uppackningsalgoritmer

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

Kollaps av felläge. Fel K, fel nonce, AAD-omatchning och manipulerad chiffertext producerar alla samma DECRYPT_FAILED-fel. Detta är en avsiktlig AEAD-egenskap: att skilja felläget skulle skapa en sidokanal som en fjärrangripare kunde sondera genom att skicka felformade omslag och tida svaret. Referensimplementationer MUST kollapsa alla AEAD-fel till en enskild felform.

13.6 Nyckelmaterial och distribution

Inslagningsnyckeln K är ett 256-bitars enhetligt slumpmässigt värde som genereras per qub av en CSPRNG. Referensimplementationerna hämtar det från:

Distribution: K MUST kodas som URL-säker base64 (RFC 4648 §5, ingen utfyllnad) och läggas till leveranslänken som fragmentkomponenten:

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

Fragmentet överförs aldrig till någon server av en konform webbläsare. Återställningskanaler (serverside-historikindex, opt-in automatisk e-postsändning) som persisterar den fullständiga leveranslänken — inklusive fragmentet — bortom användarens enhet är en uttrycklig avvägning mot den standardmässiga krypto-strimlings-hållningen och MUST gating på explicit användarsamtycke.

Fragmentförlust. Om en användare förlorar URL-fragmentet och inte har någon återställningskanal är qubben oläsbar. Detta är den bärande avvägningen i designen och MUST avslöjas för användaren vid förseglingstid. MVP:n förstärker förseglingstidsavslöjandet med uttrycklig "spara denna URL"-text och en verifierad-e-poståterställningskanal för användare som väljer att delta.

13.7 Utanför omfattning för denna sektion

13.8 Offentliga qubs (utelämnande av omslag)

Det yttre omslaget är valfritt på leveransskiktet. En skapare kan försegla en qub som offentlig, varvid den kanoniska SealedQubCbor skrivs till permanent lagring direkt, utan något OuterWrapper-skikt och utan någon nyckel K:

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

En offentlig qub är tidslåst men inte länkstyrd: den förblir oläsbar fram till att dess drand-runda publiceras (tlock-skiktet är oförändrat), men efter upplåsning kan vem som helst som har arweave_tx_id dekryptera den — inget URL-fragment krävs, eftersom det inte finns någon K. Detta är den avsiktliga avvägningen för ytor som servern måste driva: avslöjandenotifierings-e-post, tredjepartsinbäddningar och rikare SEO efter avslöjande behöver alla en länk som fungerar utan en hemlighet som servern aldrig håller (§13.6).

Följder som en producent MÅSTE ta hänsyn till:

Privat (inslaget) förblir standard; offentligt är ett uttryckligt skaparval per qub.


14. Testvektorer

14.1 qub_id-härledning

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

Implementationer MUST producera identiska body_hash- och qub_id-värden för denna indata. Denna testvektor SHOULD vara den första enhetstest som skrivs. De kanoniska värdena ovan beräknades av referensimplementationen och MUST matcha bit-för-bit. Historiska pre-image-layouter (före lansering — inga aktiva qubs berodde på dessa): det 92-byte stora V1.0-qub_id var 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; det 100-byte stora V1.1-qub_id (efter invikning av outcome_at_or_zero) var b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 viker in drand_round och höjer domänseparatorn till QUB_ID_V2.

14.2 Mappning av upplåsningsrunda

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-rundresa

Implementationer MUST verifiera att serialize(parse(serialize(qub))) == serialize(qub) för alla giltiga indata. Detta är ett egenskapstest, inte en enskild 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 kanoniska CBOR-byten och SHA3-256 body_hash beräknas av referensimplementationen. Implementationer MUST producera byte-identisk CBOR för denna indata.

Implementationer MUST också verifiera att serialize(parse(serialize(pact))) == serialize(pact) för alla giltiga PactTerms-indata (egenskapstest).

14.5 Tvärspråkliga vektorer för yttre omslag

Det yttre omslaget (§13) har en separat kanonisk fixtur i crates/qub-core/tests/vectors/wrapper_v1.json. Varje fall fixerar en tupel (key, nonce, qub_id, sealed_cbor) som ogenomskinlig hex-indata och hävdar en specifik expected_wrapper_hex-utdata. Båda referensimplementationerna konsumerar samma JSON-fil:

Fixturen nålar för närvarande tre fall:

Fall Täckning
basic-text-public Minsta realistiska SealedQub-form; inga frivilliga fält. Etablerar den kanoniska omslagsformen för en v1.0-typisk qub.
with-recipient-pubkey SealedQub med recipient_pubkey satt (Fas 2-väg). Annan inre CBOR-nyckeluppsättning, annat qub_id.
longer-body ~4 KiB kropp — utövar fleruppdelade CBOR-längdsprefix inuti både den inre envelopen och den yttre chiffertexten.

Implementationer MUST producera byte-identiskt expected_wrapper_hex för de registrerade indata. Att regenerera fixturen kräver QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors och är reserverat för avsiktliga formatändringar.


15. Styrning av kryptoprofil (framtid)

Denna sektion är informativ för v1 och blir normativ första gången en andra algoritm träder in i någon av qubs kryptografiska primitiver.

15.1 Nuvarande hållning

Protokoll v1 binder exakt en algoritm per primitiv:

Verifierare hårdkodar för närvarande nyckel- och signaturlängder per primitiv. Ingen agilitetsyta exponeras av trådformatet.

15.2 Avsedd form

När en andra algoritm träder in i protokollet kommer verifieraren att konfigureras för en namngiven CryptoProfile (t.ex. ExqubV1) som listar den exakta uppsättningen tillåtna värden per primitiv — sig_algs, drand-kedjor, omslagsversioner, innehållstyper. Profilen fastställs vid verifieringstid, aldrig förhandlas i bandet. Varje värde utanför den aktiva profilen avvisas.

Detta garanterar att tillägg av ML-DSA-87 eller aktivering av Ed25519 inte retroaktivt kan försvaga befintliga verifierarkonfigurationer: en v1-verifierare förblir en v1-verifierare även efter att en v2-profil publiceras.

15.3 Utlösningsvillkor

Befordra §15 till normativ status när något av följande föreslås:

Tills dess är §15 en platshållare som fastställer migrationsformen så att framtida PR:er landar mot ett känt mål snarare än att åter-förhandla förhandlingsytan från grunden.


16. Transparensslogg och hållbarhetsnivåer (Design — granskning klar)

Status. Denna sektion är en designspecifikation. Trådformaten, hashningen och förtroendemodellen nedan är normativa för implementationen, men ingen transparensloggkod har levererats ännu. Den externa W5-granskningen är klar: §16.15 dokumenterar de avgjorda besluten och de bindande lanseringsvillkor som kom ur den. Implementation får fortgå under dessa villkor. §16 förblir framåtblickande i samma mening som §15 — den fixerar målet så att implementationen landar mot en fastställd design snarare än att åter-härleda förtroendemodellen i kodgranskning. Den är strikt additiv — varje befintlig qub behåller sin individuella Arweave-transaktion och det sker ingen ändring av trådformatet för SealedQub / QubEnvelope.

16.1 Motivering och hållbarhetsnivåer

Idag vilar en qubs hållbarhet och dess temporala åtagande båda på en enda Arweave-transaktion per qub (§11). Det kopplar förseglingslatens till Arweave-finalitet, gör uppladdningen per qub till ett produktkostnadstak (ARWEAVE_DAILY_CEILING) och ger ingen manipulationsdetekterande ordning mellan qubbar. Transparensloggen lägger till två skikt under och kring den enda nivån:

Nivå Namn Garanti När
T1 R2-först synkron ack Hållbarhetsgolv — förseglade byte skrivs till hållbar lagring innan förseglingen returnerar (< 300 ms p95). Varje qub, synkront (§16.10).
T2 Batchad transparensloggsinkludering Universellt append-only, manipulationsdetekterande åtagande + total ordning, förankrat till Arweave. Varje qub, uppskjutet + batchat (§16.5–16.7).
T3 Arweave-permanens per qub En individuell Arweave-transaktion för qubben. Betald uppförsäljning, och fallback vid Arweave-otillgänglighet (§16.8).

T2 gör Arweave per qub till ett val (T3) snarare än den enda hållbarhetsvägen. ARWEAVE_DAILY_CEILING avvecklas som ett produkttak och degraderas till en kretsbrytare enbart på den dedikerade förankringsplånboken (§16.7); användarförseglingar avvisas aldrig för att de överskrider det.

Hållbarhetshederlighet (avgjort — §16.15 Q6). Hållbarheten regredierar inte: T1-R2-skrivningen är synkron och skriv-en-gång, så en gratisnivå-qub som inte köpte T3 är fullt hållbar i samma ögonblick förseglingen returnerar. Det som grovkornas är den bevisbara övre gränsen för åtagandetiden: för en gratis-qub blir den förankringsblockets tid snarare än en transaktionsblocktid per qub. Vid låg volym — det realistiska tillstånd som råder tidigt efter lansering och under lågtrafik — är den fullständiga dagliga kadensen det typiska golvet, inte ett sällsynt gränsfall. Produktframställningen är därför en övre gräns utan någon utfäst numerisk latens — "förseglad och hållbar nu; en oberoende offentlig tidsstämpel läggs till vid nästa loggförankring (vanligtvis dagligen)" — och exakt-timme-åtagandebevis är en betald T3-egenskap, som avslöjas på nivåjämförelseytan och i villkoren (§16.11, §16.15 Q6). Varje tidsgräns är enbart en intern SLO, aldrig en marknadsförd SLA.

16.2 LogLeaf-struktur (två åtagna former)

En loggpost är ett LogLeaf, kodat som handskriven kanonisk CBOR enligt §3.1-profilen (bestämd längd, inga taggar, inga flyttal, kortast-formade heltal, NFC-text, frivilliga fält utelämnade när de saknas, nycklar ordnade efter kodad bytelängd stigande sedan lexikografiskt). Den kanoniska parse → re-encode → compare-vakten från §3.1 tillämpas på kodningsvägen före hashning (inte bara vid avkodning), så att två implementationer inte kan vara oense om leaf-byten genom en heltalsbredd- eller nyckelordningsskillnad. Alla heltal är u8 / u64 / i64; alla sammanfattningar är 32-byte bytesträngar (bstr[32]). Ett lagrat Arweave-transaktions-id är en rå 32-byte SHA-256-sammanfattning som bärs som bstr[32], aldrig en base64url-textsträng (matchar §3.3).

Leaf-strukturen har två former valda av en kind-byte, eftersom Workern på standarduppladdningsvägen är byte-blind: POST /api/v1/upload tar emot endast qub_id och unlock_at som obetrodda klientpåståendenbody_hash, drand_round, created_at och drand_chain_version är alla förseglade inuti det yttre §13-omslaget, vars nyckel Workern aldrig håller. Endast serverförseglingsvägen (POST /api/v1/seal) härleder body_hash / drand_round från klartext. En enda leaf-form som bär body_hash + drand_round skulle därför åta sig värden som operatören aldrig verifierade för majoriteten av riktiga qubbar. Uppdelningen håller varje åtaget värde hederligt:

Nyckel Kod. längd Typ Närvaro Betydelse
seq 4 u64 obligatorisk Globalt 0-baserat leaf-index; positionen som inkluderingsbeviset åtar sig till.
kind 5 u8 obligatorisk 0x01 attesterad (serverförsegling) eller 0x02 påstådd (klientförsegling / byte-blind uppladdning).
ref 4 bstr[32] obligatorisk Leaf-referens-id. Attesterad → rå qub_id. Påstådd → det blindade id:t SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 6 bstr[32] obligatorisk Innehållsadress SHA3-256(stored_bytes) — den enda innehållskopplingen Workern alltid kan beräkna hederligt, på båda vägarna.
unlock_at 10 i64 obligatorisk Kopierad (attesterad) eller påstådd (påstådd); validerad > 0 innan den går in i leaf-strukturen.
received_at 12 i64 obligatorisk Workerns väggklocka vid R2-ack. Icke-evidentiell (operatörspåstådd; §16.6). Närvarande för självbeskrivning, aldrig ett bevis. Validerad > 0.
body_hash 10 bstr[32] endast kind=0x01 Utelämnad på 0x02 — Workern saknar den under §13.
drand_round 12 u64 endast kind=0x01 Utelämnad på 0x02.

Ett kind=0x02-leaf åtar sig avsiktligt varken body_hash eller drand_round: det attesterar åtagandet och ordningen för en ogenomskinlig chiffertext på innehållsadressen chash, med påstående om qub_id och unlock_at — inte dess klartext eller runda. Klartext- och rundbenen för en påstådd qub kommer från den befintliga §11 .qub-buntverifieringen, inte från loggen (§16.11). drand_chain_version finns inte i leaf-strukturen (den ligger inuti omslaget på standardvägen); kedjegranulariteten lever på förankringen (§16.7). Kodningsdisciplin: avvisa ett enbart-nollor ref eller chash, och avvisa icke-positivt unlock_at / received_at, vilket speglar sentinelvakten outcome_at > 0 i cbor.rs.

16.2.1 Blindning av privata qubbar

Loggen får inte bli det uppräkningsorakel som det yttre §13-omslaget finns till för att förhindra (§13.1). För en privat (inslagen) qub åtar sig asserted-leaf-strukturen den blindade identifieraren SHA3-256(qub_id ‖ log_blind_secret), där log_blind_secret är en serverhållen hemlighet, och utelämnar body_hash. En tredje part kan inte knyta ett sådant leaf till ett specifikt qub_id; qubbens innehavare, som har leveranslänken och därför qub_id, kan beräkna om blindningen för att bekräfta sin egen inkludering. En publik qub (redan uppräkningsbar, bär redan Arweave-taggen Visibility: public enligt §13.8) åtar sig det råa qub_id. Detta är den enda platsen där fristående verifierbarhet avsiktligt ger vika för en bärande integritetsinvariant; den fristående kopplingen för privata qubbar är chash (§16.9).

Förvar av log_blind_secret (avgjort — §16.15 Q4). Blindningen skyddar leaf-okopplingsbarhet, inte klartextkonfidentialitet (det yttre §13-omslaget håller det oberoende). Vid en log_blind_secret-kompromiss, för varje qub_id som angriparen redan håller eller kan rekonstruera (varje qub vars bunt/URL den har, plus varje lågentropi- eller publikt qub_id), beräknar den om leaf-ref i en hash och kopplar det — detta är direkt koppling av en känd population, inte en uttömmande sökning över ett okänt utrymme. Klassificera log_blind_secret som en korrelations-/Sybil-gradig hemlighet i samma förvarsnivå som andra serverhemligheter, och rotera enbart framåt (en rotation blindar om framtida leaf-strukturer; den kan inte retroaktivt avkoppla redan förankrade).

16.3 Leaf- och nodhashning

RFC 6962 §2.1 domänseparerad hashning med SHA-256 utbytt mot 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änprefixbyten 0x02 (postkedja, §16.4) och 0x03 (STH-hash, §16.6) är reserverade och disjunkta från dessa. De är enskilda byte och kan därför inte kollidera med de befintliga 10-byte ASCII-domänseparatorerna (QUB_ID_V2, etc.). Trädet är det RFC 6962 vänster-fulla obalanserade trädet (varje inre uppdelning vid den största tvåpotensen strikt mindre än subträdets leaf-antal), vilket låter inkluderings- och konsistensbevis dela en gemensam granskningsvägsalgoritm. Referensspecifikationen bär explicit vänster/höger-härledningspseudokod och nålar en icke-tvåpotens-(5-leaf-)testvektor så att höger-kantbefordringsfallet — som en 4-leaf-vektor döljer — utövas.

16.4 Hashkedjning (intern)

LogDO upprätthåller en intern postkedja enbart för kraschkonsistens. Den publiceras aldrig och är aldrig verifierarvänd:

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

Den publicerade append-only-auktoriteten är den kumulativa Merkle-roten + dess förankring (§16.5–16.6), aldrig den råa ordning i vilken operatören råkar servera leaf-strukturer: kedjan beräknas om för varje serverad ordning, så endast den förankrade roten nålar den kanoniska positionen.

16.5 Kumulativt Merkle-träd och batchning

Det finns ett ständigt växande RFC 6962-träd över alla leaf-strukturer i seq-ordning — inte isolerade per-batch-träd. (En carry-leaf-kedjad per-batch-konstruktion avvisades: den är inte en äkta prefixrelation, så dess "konsistensbevis" är osunda.) Det kumulativa trädet ger äkta RFC 9162-konsistensbevis och låter en enda nyligen tillkommen förankring bevisa inkludering för vilken äldre qub som helst.

Det durabla objektet LogDO är den enda skribenten (blockConcurrencyWhile, speglar QuotaDO / EntitlementDO) — att lägga till i en delad logg är läs-ändra-skriv på delad tillstånd och MÅSTE därför gå genom en DO, aldrig KV. Den cachar trädets högerkantsfrontlinje (O(log n) hashar) så att stängning av en batch är O(batch). En batch är mängden leaf-strukturer som förankras tillsammans; dess utlösare är konfigurerbara, inte protokollfrysta: ett tree_size-framsteg på minst LOG_BATCH_MAX_LEAVES (standard 4096), eller att åldern når förankringskadensen, eller en framtvingad tömning när en betald T3-försegling landar. root_i är den kumulativa Merkle-trädhashen över leaf-strukturer 0 .. tree_size_i.

16.6 Signerat trädhuvud via Arweave-förankring

Arweave-förankringstransaktionen är det signerade trädhuvudet och ersätter en operatörssignatur för själva trädhuvudet: den dagliga förankringen behöver ingen qub-nyckel eftersom Arweave-transaktionens owner är signaturen. Vallgravstesen håller — det oföränderliga substratet, inte en qub-hållen hemlighet, är bärande för den förankrade roten.

Det finns exakt en varm qub-signeringsnyckel i designen, och den är nålad: kvittonyckeln per försegling (§16.10). Dess publika nyckel åtas i LogProfile (distribuerad med verifieraren) och korssigneras av anchor_owner, så att en verifierare validerar ett kvitto mot samma nålade rot som förankringen. Detta är lösningen på §16.15 Q2 — en onålad, operatörsroterbar kvittonyckel skulle vara förnekbar (operatören kunde förneka att nyckeln var deras), vilket skulle upphäva kvittots ansvarsvärde mot den operatörsnivåangripare som kvittot finns till för att avskräcka. Alltså: qub håller ingen onålad loggsigneringsnyckel; kvittonyckeln är nålad och korssignerad av anchor_owner.

SignedTreeHead är kanonisk CBOR (nycklar efter kodad längd): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (föregående sth_hash; genesis = 32 nollbyte), log_id:bstr[32], first_seq:u64, anchored_at:i64. Dess hash är sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

Nålad förtroenderot. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). En konform verifierare MÅSTE kräva anchor_tx.owner == LogProfile.anchor_owner, där anchor_owner (och kvittonyckelns publika nyckel) är inbakad i qub_core som LogProfile — vid sidan av quicknet-konstanterna som redan finns i DrandTimelockProvider::quicknet() — och distribuerad med verifierarbinären. Verifieraren MÅSTE också verifiera Arweave-transaktionens data → tx_id-bindning lokalt snarare än att lita på ett gateway-/raw/-svar. Detta stänger hålet för olovlig-plånboks-tvetydighet: "förankrad på Arweave" är meningslöst tills verifieraren nålar vilken plånbok.

Rotation är en §15-styrningsutvidgning, inte ett återbruk (avgjort — §16.15 Q3). §15.2:s profilyta uppräknar för närvarande endast sig_algs / drand-kedjor / omslagsversioner / innehållstyper, och §15.3:s utlösare listar ingen av dessa — LogProfile / anchor_owner är ännu inte i §15:s yta. Rotationsstyrning måste därför byggas: §15.3 utvidgas (nedan) för att lägga till LogProfile-utlösaren, och en rotation är en signerad LogProfile-höjning som levereras i en verifierar-uppdatering. En planerad rotation bär en utgående → inkommande korssignatur; en kompromissdriven rotation kan inte (den utgående nyckeln är obetrodd/otillgänglig just då) och faller tillbaka till den §15-styrda höjningen, med föregående-förankrings-forkkontrollen (nedan) som begränsar skadan under mellantiden.

Tvetydighetsfönster (förstklassig förtroendeparameter). Ett leaf är tvetydighetsbeständigt först när dess täckande förankring är Arweave-bekräftad. Fönstret är received_at → förankringsbekräftelse (≤ kadens + Arweave-finalitet). Inom det är de enda garantierna det nålade förseglingskvittot (§16.10) och qubs operationella integritet. Tre ansvarsartefakter gör detta hederligt snarare än bortviftat (vittnesmodellen är §16.15 Q2:s lösning):

  1. Nålat signerat förseglingskvitto — SCT-analogen som returneras i uppladdningssvaret (§16.10), signerat av den nålade, anchor_owner-korssignerade kvittonyckeln. Ett leaf som tappas före sin förankring lämnar offret ett icke-förnekbart kvitto att publicera, vilket stänger hålet för tyst utelämnande.
  2. Publicerad övervakningsmetodik + föregående-kedjevandring — förankringens prev-kedja vandras huvud→genesis; en fork (två förankringar vid en size med olika root, eller en bruten prev) är publicerbart bevis på missuppförande. Tvetydighetsdetektion är ett uttalat operationellt åtagande, inte ett tyst antagande.
  3. Dubbla självpublicerade huvuden — varje nytt huvud {sth_hash, tree_size} postas till ett dedikerat qub-ägt offentligt, append-only GitHub-arkiv (det bärande manipulationsdetekterande självpubliceringsbenet), med ett socialt inlägg som best-effort-bekräftelse enbart. En misslyckad postning MÅSTE personsöka (inte misslyckas tyst).

Hederlighetsgräns (bindande villkor). Eftersom qub kontrollerar båda postningsytorna är detta självpublicerat, inte oberoende bevittnat. Ingen produkt-, marknadsförings- eller juridisk yta får hävda att loggen är "oberoende bevittnad"; det tillåtna påståendet är att tvetydighet är detekterbar och lämnar ett icke-förnekbart kvitto. Ett äkta oberoende tredjepartsvittne skjuts upp till en framtida §15-styrningshöjning.

received_at är operatörspåstådd och inget påstående får luta sig mot den — den ytas aldrig som bevis eller som tvistbekräftelse på någon produkt-/juridisk/API-/bevisåtergivningsyta. Arweave-förankringsblocktiden T är den enda förtroendefria tidsstämpeln (en övre gräns för "loggad senast"). Varje övervakningsförnuftskontroll på received_at MÅSTE jämföra mot T, inte mot det operatörskontrollerade STH-fältet anchored_at; en sådan kontroll är en vakt mot en hederlig operatörs klockfel enbart, inte en ansvarskontroll mot en illvillig operatör (§16.15 Q5).

16.7 Förankringstransaktionsformat och kadens

AnchorBundle är den kanonisk-CBOR Arweave-transaktionskroppen, skriven via §16.8-bundlaren: ver:u8, sth:bstr (kanoniska SignedTreeHead-byte), prev_anchor:bstr (föregående förankrings-transaktions-id i råa byte; utelämnad vid genesis), chain_hash:tstr (drand-kedjan i kraft — quicknet), och batchens leaf-CBOR-ström i seq-ordning så att förankringen är självständig: en övervakare härleder om root från kroppen med noll qub-beroende. (Om leaf-strömmen blir stor vid hög volym kan en framtida revision åta sig endast ett leaf-intervall genom referens; noterat, inte antaget i v1.)

Arweave-taggar är avsiktligt uppräkningsbara — loggen är menad att hittas, till skillnad från privata qubbar: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Taggar är obetrodda ledtrådar; CBOR-kroppen är den enda auktoriteten.

Kadens: dagligen som standard, omprövad med volymen (storleksutlösaren auto-förkortar den effektiva kadensen under belastning); en betald T3-försegling framtvingar en förankring så att betalande kunder aldrig väntar en dag. Förankringsplånboken är dedikerad och låghastig, separat från uppladdningsplånboken — den MÅSTE vara sin egen JWK (en distinkt nyckel, inte en logisk roll på uppladdningsplånboken) så att en uppladdningsplånbokskompromiss inte kan förfalska förankringar — med en hård budget för förankringstransaktioner per dag (det degraderade ARWEAVE_DAILY_CEILING). Förvarshållningen anges rakt ut: en smal-omfångad het nyckel med en snäv kretsbrytare och låg saldo, inte "kall" — en plånbok som auto-signerar dagligen kan inte vara kall, och specifikationen låtsas inte om annat.

16.8 ANS-104-bundlare

En egenutvecklad ANS-104 DataItem-kodare och deep-hash-signerare, ungefär 300 rader, enbart Web Crypto, noll npm-beroenden (båda Turbo-SDK:erna underkänns av §npm ci --ignore-scripts-leveranskedjegrinden). DataItem-bytelayout:

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 är Arweave deepHash — en rekursiv SHA-384-sammanfattning (Arweaves trådkrav, crypto.subtle.digest("SHA-384")) över ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — sedan RSA-PSS över deep-hashen med plånbokens JWK via crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 här är karantänsatt som en enbart-Arweave-tråd-primitiv, aldrig en qub-förtroendeprimitiv (§15 dokumenterar stängslet; qub-förtroendehashning är SHA3-256 genomgående).

En kodväg betjänar tre konsumenter: betald T3-permanens per qub, fallbacken vid Arweave-otillgänglighet (köa DataItem:et, returnera R2-först-acken oavsett — detta stänger den nuvarande ARWEAVE_UNAVAILABLE 503-återvändsgränden), och skrivning av AnchorBundle. Signaturschema (avgjort — §16.15 Q8): v1 signerar med RSA-PSS (signaturtyp 1) och återbrukar den befintliga Arweave-plånboks-JWK-mekanismen (noll nytt långlivat nyckelförvar, vilket betjänar tesen "en hemlighet färre"); Ed25519 skjuts upp till §15-PQ-migrationsvägen.

Den handrullade deep-hashen är den högst riskfyllda, lägst naturligt täckta koden i W5, så dess grindning är icke-förhandlingsbar (§16.15 Q8):

  1. Den tvärspråkliga fixturen tlog_v1.json (Rust + TS, §14.5-mönstret wrapper_v1.json) täcker deep-hash, DataItem-byte + id, leaf-hashar, en 5-leaf-rot + granskningsväg, en STH-hash, ett inkluderingsbevis och ett konsistensbevis — i både sign- och verify-riktningen (verify-riktningen är viktig eftersom §16.6:s lokala tx → tx_id-kontroll drar in deep-hashen i varje fristående verifierare, inte bara skribenten).
  2. En engångs-interop-rundresa genom en referens-ANS-104-bundlare, konsumerad som enbart statisk testdata — aldrig ett npm-runtime-beroende (den enbart-Web-Crypto-/inga-installationsskript-hållningen står fast).
  3. Deep-hash- + RSA-PSS-vägen MÅSTE rundresa genom samma crypto.subtle-primitiver som produktionen använder, så att den egenutvecklade kodaren är byte-kompatibel.
  4. En pågående post-bunt-accept-övervakare bekräftar att varje förankrings-/fallback-DataItem faktiskt uppnår Arweave-accept, med ett larm + kretsbrytare — eftersom deep-hashen även betjänar fallback-kön vid Arweave-otillgänglighet, så att en tyst regression skulle fylla den kön med nätverksavvisade poster under just det avbrott den finns till för att täcka.

16.9 Inkluderings- och konsistensbevis

Båda är RFC 9162, SHA3-256, serverade som kanonisk CBOR.

InclusionProofGET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (den exakta leaf-CBOR:en — verifieraren beräknar om leaf_hash själv och litar aldrig på en tillhandahållen 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 }.

ConsistencyProofGET /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 enda otvetydig nyckellista, nålad av testvektor.

Fristående verifiering (ingen qub-server, utvidgar §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).

Bevisserverande lagring MÅSTE vara koordinatnycklad (avgjort — §16.15 Q7, blockerande förutsättning). Kall-leaf-bevisgenerering är korrekthetsneutral endast om R2-granskningsmaterialet är ett persistent Merkle-nodlager nycklat efter absolut trädkoordinat (level, index) — inte per-batch-noddeltan. Med ett koordinatnycklat lager är varje (leaf i, size N)-granskningsväg en mängd O(log N) direkta R2-GET med ingen omberäkning över batchgränser; med ett batchnycklat lager är den det inte, vilket är den lagringslayoutlucka denna lösning stänger. Leaf-kropparna är likaledes innehållsadresserbara efter seq. En W5-testvektor MÅSTE bevisa ett genesis-era kallt leaf mot en mycket senare rot med enbart R2 + Arweave och LogDO-lagringen utraderad, så att återvinningssäkerhetspåståendet i §16.13 är underbyggt snarare än påstått. De O(log N) sekventiella R2-GET hör hemma enbart på den asynkrona bevis-endpointen — aldrig på förseglings-hotvägen (§16.10) eller en per-tick-cron.

16.10 R2-först-ack-ordning

POST /api/v1/upload-sekvensen blir:

  1. Framhalv-grindar (auth, validering, idempotensshardnyckel) — oförändrade.
  2. Synkront await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes) — hållbarhetsgolvet; stänger också W1:s precache-kapplöpning (tidigare en ctx.waitUntil efter Arweave-inlämningen).
  3. Synkront await LogDO.append(leaf) — en in-colo DO-RPC; den enda skribenten tilldelar seq, utvidgar postkedjan och uppdaterar frontlinjen. (RMW på delad tillstånd → DO, aldrig KV.) append-RPC:n gör endast det — O(batch)-Merkle-batchstängningsarbetet körs utanför denna RPC på LogDO-larmet, annars spikar append-p95 vid var LOG_BATCH_MAX_LEAVES:te försegling.
  4. Returnera acken nu — med förseglingskvittot (signerat av den nålade kvittonyckeln, §16.6) och { tx_id, log_seq, anchor_status: "pending" }. Den flersekunders Arweave-utspridningen tas bort från den kritiska vägen.
  5. En ctx.waitUntil köar det uppskjutna arbetet: Arweave-inlämningen per qub (nu best-effort / betald; vid fel routas den till bundlar-fallback-kön snarare än att 503:a användaren) plus de befintliga provisoriska metaskrivningarna. Batchstängning och förankring körs oberoende från LogDO-larmet och den dagliga förankrings-cron:en. Ingen ctx.waitUntil inuti en loop; den befintliga idempotensshardnyckeln bevaras.

Latensbudget (avgjort — §16.15 Q7). < 300 ms-p95-målet är en mätt lanseringsgrind, inte ett antagande. Den hederliga kritiska vägen är framhalv-KV-läsningarna + en R2-PUT + två serialiserade durabla objekt — den befintliga QuotaDO-förseglingskvotsdebiteringen och den nya LogDO-tillägget — så budgeten måste ta hänsyn till två in-colo-DO-rundresor, inte en. Leverera ett LogDO-latenslarm som speglar QuotaDO:s och behandla en p95-regression som en release-blockerare.

16.11 Förtroendemodell — det exakta påståendet, omfångat av leaf-typ

För kind=0x01 (attesterad): "Detta innehåll — kropp som matchar body_hash, identifierad av qub_id — åtogs till qubs append-only-logg vid position seq och existerade senast vid Arweave-blocktiden T; det var kryptografiskt oläsbart fram till drand-rundan R = unlock_round(unlock_at)." Detta är den fullständiga {tlock-rundbindning + Merkle-inkludering + förankrad rot}-tripeln.

För kind=0x02 (påstådd, standarden): "En ogenomskinlig chiffertext med innehållsadressen chash, med påstående om qub_id och unlock_at, åtogs till append-only-loggen vid position seq och existerade senast vid Arweave-blocktiden T." Rund- och kroppsbenen tillhandahålls av den befintliga §11 .qub-buntverifieringen (qub_core::unlock), inte av loggen; det loggen tillför utöver en bar transaktion per qub är manipulationsdetekterande ordning, en förtroendefri övre-gräns-åtagandetid och tvetydighetsbeständighet.

Båda påståendena utesluter, enligt §11: författarskap utan sig_alg ≥ 0x01, avsikt, och sub-förankrings-granularitetstidsbestämning. Inget av dem låter något påstående luta sig mot received_at.

Påståendetak (bindande lanseringsvillkor — avgjort §16.15 Q1). För gratis-/standard-(kind=0x02)-qubbar är det omfångade kind=0x02-påståendet ovan taket för vad någon produkt-, marknadsförings-, villkors- eller bevisåtergivningsyta får hävda. Ingen yta får påstå eller antyda att loggen bevisar innehållet eller upplåsningsrundan för en standard-qub — loggen bevisar ordning + en förtroendefri övre-gräns-åtagandetid för en ogenomskinlig chiffertext. Innehålls- och rundbevis kommer uteslutande från den befintliga §11 .qub-buntverifieringen, som är loggoberoende. Detta är en hård lanseringsblockerare på copy, inte en stilistisk preferens; det är lösningen som håller den byte-blinda standardvägen hederlig.

16.12 Versionshantering och W3-koordinering

Det sker ingen SealedQub-trådhöjning och därför ingen protokollversionshöjning (§12.1): loggen är en sidovagn som åtar sig befintliga fält och byte, så den går inte in i §12.2-protokollversionshistoriken. W3:s frivilliga drand_chain_version är orörd och förblir det enda frivilliga SealedQub-fältet. Loggen inför i stället sina egna oberoende versionsutrymmen — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — som speglar §12.4-omslagsversionsoberoendet (omslaget bär en versionsbyte oberoende av protokollversionen, och loggversionerna följer samma separation).

Bevisleverans hämtas som standard, med en frivillig medåkning. Ett bevis kan inte existera vid förseglingstid (förankringen har inte skrivits ännu), så förseglingstidens .qub-bunt förblir bevisfri. W7:s verifierare hämtar GET …/proof en gång, eller rekonstruerar i fullt offline-läge beviset från den offentliga AnchorBundle via en Arweave-fråga på Log-Id. .qub-bunten (W7) reserverar en frivillig inclusion_proof-medlem — frånvarande vid försegling, fylld av en post-förankrings-omexport för kall arkivering — som följer samma "frivillig, utelämnad som standard, additiv"-mönster som W3:s drand_chain_version.

16.13 Retention

Retentionsfönster för LogDO-öppna-svansen, R2-bevisserverande substratet, förankrings-kretsbrytarräknarna och bundlar-fallback-kön specificeras i docs/DATA-RETENTION.md. Princip: loggens heta per-post-lagring (LogDO) är återvinningsbar efter förankring; dess granskningsmaterial — det koordinatnycklade (level, index)-Merkle-nodlagret + de seq-adresserade leaf-kropparna (§16.9) + Arweave-förankringarna — är permanent. Att återvinna ett kallt leaf från DO:n ogiltigförklarar aldrig ett utfärdat bevis, eftersom ett bevis löses mot det permanenta R2-nodlagret och Arweave-förankringen, inte DO:n (och §16.9:s utraderad-DO-testvektor bevisar det).

16.14 Testvektorer

W5 levererar den tvärspråkliga fixturen tlog_v1.json (§16.8) plus utarbetade vektorer: ett kind=0x01- och ett kind=0x02-leaf → leaf_hash; den 5-leaf kumulativa roten; ett inkluderingsbevis; ett konsistensbevis; en AnchorBundle; och ett DataItem-id. Dessa lever vid sidan av §14.5-yttre-omslagsvektorerna och utövas av både Rust-(qub-core)- och TypeScript-(Worker)-implementationerna.

16.15 Granskningsbeslut (W5 — avgjorda)

Den externa W5-granskningen (en adversariell designgenomgång + ägargodkännande) är klar. Varje beslut nedan är fastställt och återspeglat i §16-texten ovan; de bindande lanseringsvillkoren upprepas i slutet. Implementation får fortgå under dem.

  1. Standardvägs-(kind=0x02)-leaf-hederlighet — LÖST. Leverera tvåleaf-typ-uppdelningen som specificerad: kind=0x02 åtar sig varken body_hash eller drand_round. Inget *_body_hash-fält på den byte-blinda vägen (det skulle vara den mest läsbara falska "verifierad"-signalen för integratörer och är en bekvämlighet §11 redan tillhandahåller från bunten). Kräv inte serverförsegling för loggattesterade qubbar (det skulle tvinga klartext genom Workern och förstöra krypto-strimlings-vallgraven). Varje självbeskrivande kortslutning hör hemma i .qub-bunten / bevis-kuvertet som ett verifierar-omberäknat fält, aldrig ett leaf-fält. Ägarbekräftat påståendetak: §16.11.
  2. Tvetydighets-/utelämnandeansvar — LÖST. Förseglingskvittonyckeln är nålad i LogProfile + anchor_owner-korssignerad (stänger den tidigare "ingen signeringsnyckel"-motsägelsen; §16.6). Lanseringsvittnesmodell: nålat kvitto + övervakningsmetodik + föregående-kedjevandring + dubbla självpublicerade huvuden (qub-ägt offentligt GitHub-arkiv, socialt best-effort), marknadsfört som detekterbart + kvitterat, aldrig oberoende bevittnat. Ett äkta tredjepartsvittne skjuts upp till en §15-styrningshöjning.
  3. Nålad förankringsägar-förtroenderot + rotation — LÖST. Anta LogProfile-nålningen (§16.6); verifieraren kontrollerar anchor_tx.owner == anchor_owner och verifierar tx-data → tx_id-bindningen lokalt. Rotationsstyrning är en §15-utvidgning att bygga (§15.3-utlösare tillagd), inte ett återbruk; planerade rotationer korssignerar, kompromissdrivna rotationer faller tillbaka till §15-höjningen med forkkontrollen som begränsar skadan.
  4. Privat-qub-leaf-blindning — LÖST. Behåll blindning för privata qubbar (ref = SHA3-256(qub_id ‖ log_blind_secret)), rått qub_id för publika qubbar (redan §16.2.1), chash som den fristående kopplingen. log_blind_secret är en korrelations-/Sybil-gradig hemlighet, rotera-framåt enbart (§16.2.1).
  5. received_at — LÖST. Behåll den i leaf-strukturen, åtagen men explicit icke-evidentiell; aldrig ytad som bevis eller tvistbekräftelse på någon yta. Varje övervakningsförnuftskontroll jämför mot Arweave-blocktiden T, inte mot det operatörskontrollerade anchored_at (§16.6).
  6. Gratisnivå-bevisbar-tidsbestämning — LÖST (ägargodkännande). Hållbarheten regredierar inte; endast den bevisbara övre-gräns-åtagandetiden grovkornas till förankringsblocktiden. Gratisnivå-copy använder ingen numerisk SLA ("…läggs till vid nästa loggförankring, vanligtvis dagligen"); exakt-timme-bevis är en betald T3-egenskap, avslöjad på nivåjämförelseytan + i villkoren (§16.1).
  7. Kumulativt träd på Workers — LÖST. Enda kumulativa RFC 9162-träd + frontlinje-cachad enskild-skribent-LogDO (bekvämt utrymme mot ~1k skrivningar/sek-DO-taket; skjut upp Merkle-av-shard-rötter-shardning tills nära det). Blockerande förutsättning: koordinatnycklat (level, index)-R2-nodlager + utraderad-DO-kall-leaf-testvektorn (§16.9); < 300 ms är en mätt lanseringsgrind över två serialiserade DO:er (§16.10).
  8. ANS-104-signaturschema + deep-hash — LÖST. RSA-PSS (sig-typ 1, återbrukar den dedikerade förankrings-plånboks-JWK:n); Ed25519 uppskjutet till §15-PQ-vägen. Den handrullade SHA-384-deep-hashen grindas på båda-riktningars tvär-impl-fixturen, en enbart-statisk referens-bundlar-interop-kontroll, den delade-crypto.subtle-rundresan, och post-bunt-Arweave-accept-övervakaren (§16.8).

Bindande lanseringsvillkor (bär in i implementation + produkt-/juridisk granskning):