qub-Protokollspezifikation

qub ist ein Protokoll für kryptografische temporale Verpflichtungen: ein System, das Worte auf ein zukünftiges Datum versiegelt und später exakt verifiziert, was versiegelt wurde, welche drand-Runde seine Freigabe steuerte und—wenn eine Speichertransaktion oder ein Transparenz-Log-Beweis verfügbar ist—eine unabhängig mit Zeitstempel versehene Obergrenze dafür, wann der Chiffretext festgeschrieben wurde.

Drei Primitive ermöglichen dies. drand ist ein dezentrales Zufallsbeacon—der Enthüllungszeitpunkt wird kryptografisch statt durch das Wohlwollen von qub erzwungen. Dauerhafter Speicher und ein Append-only-Transparenz-Log bewahren versiegelte Bytes und verankern gebündelte Verpflichtungen im dauerhaften öffentlichen Speicher; der kostenpflichtige T3-Pfad schreibt außerdem eine einzelne Transaktion in den dauerhaften Speicher. ML-DSA-65 ist eine post-quantensichere digitale Signatur—wenn die Urheberschaft aktiviert ist, ist der qub an ein Schlüsselpaar gebunden, dessen Geheimnis das Gerät der erstellenden Person nie verlässt.

Zusammen erzeugen diese Primitive eine zeitversiegelte und manipulationssichtbare, optional zurechenbare und unabhängig mit Zeitstempel versehbare Aussage—eine Quittung, deren Wert mit der Fähigkeit der Welt wächst, die Vergangenheit zu fälschen.

Der Rest dieses Dokuments ist die normative Spezifikation, die für interoperable Implementierungen erforderlich ist.


qub-Protokollspezifikation

Feld Wert
Dokument-Release 1.0.0 (protocol-v1.0.0)
Wire-Protokoll 0x01
Äußerer Wrapper 0x01
Gültig ab 2026-09-23
Status Aktuell
Geprüft bis 2026-09-23

Dieses Dokument ist die normative Protokollspezifikation für das qub-System für zeitlich versiegelte Verpflichtungen. Es definiert Datenstrukturen, Serialisierungsregeln, Ableitungsformeln und Verifizierungsverfahren, die für interoperable Implementierungen erforderlich sind.

Geltungsbereich: Die Protokollebene ist absichtlich sprachneutral — der qub-Body ist opaker Klartext / Markdown / Pakt-Bytes, und das gebietsschemaabhängige Rendering liegt in der Verantwortung des Empfängers (qub.social-Webanwendung, <qub-embed>-Iframe, MCP-Clients usw.).


1. Notation und Konventionen

Notation Bedeutung
u8, u64, i64 Vorzeichenlose / vorzeichenbehaftete Ganzzahlen der angegebenen Bitbreite
[u8; N] Byte-Array fester Länge von N Bytes
Vec<u8> Byte-Array variabler Länge
Option<T> Wert vom Typ T oder abwesend
String UTF-8-Textzeichenkette, NFC-normalisiert
`
SHA3-256(x) NIST-SHA3-256-Hash der Bytefolge x (FIPS 202)
ceil(x) Aufrundungsfunktion: kleinste ganze Zahl ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Höchstwertiges Byte zuerst

Alle Ganzzahlen in Preimage-Konstruktionen werden als big-endian-Byte-Arrays fester Breite codiert (i64 → 8 Bytes, u8 → 1 Byte), sofern nicht anders angegeben.

Alle Zeitstempel sind Unix-Sekunden in UTC.


2. Datenstrukturen

2.1 ComposeQub (In-Memory-Zustand des Erstellers)

Nicht in CBOR serialisiert. Nicht im dauerhaften Speicher gespeichert. Lokal in der Ersteller-App.

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

2.2 QubEnvelope (entschlüsselte Nutzlast)

Serialisiert mittels kanonischem CBOR (§3). Verschlüsselt innerhalb des SealedQub. Dies ist die Struktur, die nach der Entschlüsselung die Inhaltsintegrität nachweist.

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

Basislinie (unsignierter Text-qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; Signatur- und Mitunterzeichnerfelder sind abwesend. Andere optionale Metadatenfelder können vorhanden sein.

Andere v1-Konfigurationen: content_type = 0x03 (Pakt-Body, siehe §6.1); sig_alg = 0x01 (ML-DSA-65) mit vorhandenen author_signature und author_pubkey (siehe §9.3); cosigner_pubkey und cosigner_signature zusammen vorhanden für mitunterzeichnete Pakte (siehe §9.7); reply_to gesetzt auf den qub_id des übergeordneten qubs für qubs in Antwortketten (siehe §9.3 für die Auswirkungen auf den Signaturumfang).

2.3 SealedQub (kanonisches Wire-Format)

Serialisiert mittels kanonischem CBOR (§3). Dies ist das innere Wire-Artefakt: Bei öffentlicher Zustellung werden diese Bytes unverpackt gespeichert, bei privater Zustellung vor der Speicherung in OuterWrapper verpackt (§13).

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

2.4 RevealedQub (Anwendungszustand des Empfängers)

Nicht in CBOR serialisiert. Lokal im Viewer. Konstruiert nach erfolgreicher Entschlüsselung und Verifizierung.

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

3. Kanonisches CBOR-Profil

Jegliche Serialisierung von SealedQub und QubEnvelope MUSS diesem Profil entsprechen. Zwei Implementierungen, denen dieselbe logische Struktur vorliegt, MÜSSEN identische Bytes erzeugen.

3.1 Codierungsregeln

Regel Spezifikation
Standard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Reihenfolge der Map-Schlüssel Sortiert zuerst nach codierter Bytelänge (kürzer vor länger), anschließend lexikografisch (Byte für Byte bei gleicher Länge)
Ganzzahl-Codierung Kürzeste Form: 0–23 im initialen Byte; 24–255 in 2 Bytes; 256–65535 in 3 Bytes; usw.
Längencodierung Nur definite Längen. Keine Arrays, Maps, Byte-Strings oder Text-Strings unbestimmter Länge (additional info = 31 ist verboten).
Tags Keine CBOR-Tags (Major Type 6 ist verboten).
Gleitkomma Keine Floats (Major Type 7 Werte 0xF9–0xFB sind verboten).
Text-Strings UTF-8-codiert, NFC-normalisiert (Unicode Normalization Form C).
Byte-Strings Rohe Bytes. Keine Base64-Codierung auf der CBOR-Ebene.
Doppelte Schlüssel Mit Fehler ablehnen. Parser DÜRFEN doppelte Map-Schlüssel NICHT stillschweigend akzeptieren.
Unbekannte Schlüssel Mit Fehler ablehnen. Parser DÜRFEN Map-Schlüssel außerhalb des kanonischen Schlüsselsatzes des Typs NICHT tolerieren — zwei unterschiedliche kanonische Byte-Strings dürfen niemals zum selben Wert decodieren (encode(decode(x)) == x), und bei signierten Nutzlasten wäre ein zusätzlicher Schlüssel verborgener Inhalt, auf den sich beide Signaturen festlegen. Schemaevolution erfolgt über version, niemals über zusätzliche Schlüssel.
Einfache Werte Nur true (0xF5), false (0xF4) und null (0xF6) sind zulässig.
Optionale Felder Abwesende optionale Felder werden vollständig aus der CBOR-Map weggelassen (nicht als null codiert). Vorhandene optionale Felder werden in sortierter Schlüsselreihenfolge eingefügt.

3.2 Verifizierte kanonische Schlüsselreihenfolgen

Diese Schlüsselreihenfolgen sind normativ. Implementierungen MÜSSEN die Schlüssel exakt in dieser Reihenfolge ausgeben. Debug-Assertions SOLLTEN die Reihenfolge in Nicht-Release-Builds prüfen.

QubEnvelope (Version 0x01, unsigniert, alle optionalen Felder abwesend):

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

Herleitung der QubEnvelope-Schlüsselreihenfolge: Jeder Schlüssel ist ein CBOR-Text-String. Codierte Länge = 1 Byte Header + Stringlänge (für Strings unter 24 Bytes). Sortieren Sie zuerst nach codierter Gesamtlänge, dann lexikografisch bei gleicher Länge.

SealedQub (Version 0x01, öffentlich, ohne Empfänger):

"title"             (6 encoded bytes)   ← only if present
"qub_id"            (7 encoded bytes)
"version"           (8 encoded bytes)
"unlock_at"         (10 encoded bytes)
"outcome_at"        (11 encoded bytes)  ← only if present (verdict mechanic)
"visibility"        (11 encoded bytes)
"drand_round"       (12 encoded bytes)
"drand_chain_id"    (15 encoded bytes)
"recipient_pubkey"  (17 encoded bytes)  ← only if present
"tlock_ciphertext"  (17 encoded bytes)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)

PactTerms (Pakt-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 (Zeile des terms-Arrays):

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

Typ CBOR-Codierung Beispiel
SHA3-256-Hash (32 Bytes) 0x58 0x20 + 32 Bytes body_hash, qub_id
Zeitstempel (i64) Major Type 0 (positiv) oder 1 (negativ), kürzeste Codierung Unix-Sekunden
Version (u8, Wert 1) 0x01 (einzelnes Byte)
Content-Typ (u8, Wert 1) 0x01 (einzelnes Byte)
sig_alg (u8, Wert 0) 0x00 (einzelnes Byte)
ML-DSA-65-Signatur (3.309 Bytes) 0x59 0x0C 0xED + 3.309 Bytes author_signature, cosigner_signature
ML-DSA-65-Public-Key (1.952 Bytes) 0x59 0x07 0xA0 + 1.952 Bytes author_pubkey, cosigner_pubkey

4. Normative Ableitungen

4.1 qub_id

Die qub_id identifiziert einen qub eindeutig und bindet das QubEnvelope an den SealedQub. Sie wird deterministisch aus dem Inhalt des Envelopes abgeleitet.

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

Codierung des Domain-Separators: Die Zeichenkette "QUB_ID_V2" umfasst 9 ASCII-Bytes. Ein einzelnes 0x00-Padding-Byte wird angehängt, um die 10 Bytes für die Ausrichtung zu erreichen. Implementierungen MÜSSEN exakt diese 10 Bytes verwenden: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Codierung von outcome_at: Eine Implementierungsrevision vor dem Release hat das Preimage von 92 auf 100 Bytes erweitert, um das optionale outcome_at-Feld in die Bindung einzubeziehen. Ein abwesendes outcome_at wird als 8 Nullbytes codiert; die Protokoll-Validatoren lehnen outcome_at <= 0 überall ab, sodass dieser Sentinel nicht mit einem legitimen Wert kollidieren kann. Siehe §3.2 (Wire-Format) und den im Repository liegenden Plan tasks/verdict-uplift-plan.md für die Verdikt-Mechanik, die dieses Feld motiviert.

Codierung von drand_round: Eine spätere Implementierungsrevision vor dem Release hat das Preimage von 100 auf 108 Bytes erweitert, um drand_round (die Ziel-drand-Runde, §4.3) in die Bindung einzubeziehen, und den Domain-Separator auf QUB_ID_V2 angehoben. Dies bindet die Timelock-Runde in die qub-Identität ein: Ein Gateway kann den Chiffretext nicht an eine andere (z. B. bereits vergangene) Runde binden, als das angezeigte unlock_at impliziert. Das Entsperrverfahren (§8) verifiziert zusätzlich, dass die in der tlock-Chiffretext-Stanza eingebettete Runde mit unlock_round(unlock_at) übereinstimmt, sodass der angezeigte Entsperrzeitpunkt nachweislich die Runde ist, die die Entschlüsselung freigibt.

Eigenschaften:

4.2 body_hash

body_hash = SHA3-256(body)

Wobei body die rohe Vec<u8>-Inhaltsnutzlast ist. Bei Text-qubs ist dies der UTF-8-codierte 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

Wobei title der optionale Klartext-Titel ist, der im Viewer-Countdown vor der Enthüllung angezeigt wird (siehe §3.2). Die NFC-Normalisierung wird zum Hash-Zeitpunkt durchgeführt, sodass der Digest über visuell äquivalente Codepunkt-Sequenzen hinweg stabil ist. Der All-Nullen-Sentinel ist für den Abwesend-Fall reserviert; eine leere Zeichenkette wird an der kanonischen CBOR-Grenze als nicht-kanonische Codierung von „abwesend" abgelehnt (die kanonische Codierung lässt das Feld vollständig weg).

4.3 Zuordnung von Entsperrzeitpunkt zu Runde

drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
Parameter Quelle Beispiel
unlock_at Vom Benutzer gewählte Unix-Sekunden 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

Dies ist die Referenz-tlock-Zuordnung (CurrentRound von drand). drand veröffentlicht Runde N zu chain_genesis_time + (N - 1) * chain_period_seconds; die Formel wählt daher die bei unlock_at aktuelle Runde—die Runde, deren Signatur ein bei unlock_at eintreffender Viewer als erste verwenden kann.

Ausrichtungseigenschaft (der praktisch relevante Fall): Wenn (unlock_at - chain_genesis_time) exakt durch chain_period_seconds teilbar ist, wird die Signatur der gewählten Runde exakt bei unlock_at, niemals davor veröffentlicht. Dies gilt stets für die Referenzbereitstellung: Die Genesis-Zeit von quicknet (1692803367) ist durch ihre 3-Sekunden-Periode teilbar, und die Referenz-Apps legen Entsperrzeiten auf ganze Minuten. Bei einem nicht ausgerichteten unlock_at wird die Signatur der gewählten Runde strikt weniger als eine Periode vor unlock_at veröffentlicht—die zeitliche Präzision der Verpflichtung entspricht einer Beacon-Periode.

Legacy-Zuordnung vor dem Release und entsperrseitige Toleranz: Die ursprüngliche Zuordnung war ceil((unlock_at - chain_genesis_time) / chain_period_seconds), die—im oben beschriebenen periodenausgerichteten Fall—die eine volle Periode vor unlock_at veröffentlichte Runde auswählte und den Chiffretext damit exakt eine Periode zu früh entschlüsselbar machte. Die zwei Zuordnungen unterscheiden sich genau um +1, wenn die Differenz durch die Periode teilbar ist, und stimmen sonst überein. Da drand_round in das unveränderliche qub_id-Preimage (§4.1) einfließt, können unter der Legacy-Zuordnung versiegelte Artefakte nicht neu abgeleitet werden; Verifizierer, die in §8 Schritt 6a die Runde gegenprüfen, MÜSSEN daher eine gespeicherte drand_round akzeptieren, die entweder der abgeleiteten Runde oder der abgeleiteten Runde minus eins entspricht (und MÜSSEN verlangen, dass die Runde der tlock-Stanza exakt der gespeicherten Runde entspricht). Die Toleranz verschiebt die früheste Freigabesignatur höchstens um eine Periode. Der Dienst für die Pakt-Vorbereitung verwendet dieselbe Toleranz, wenn er die qub_id eines vorbereiteten Pakts neu ableitet (bei Vorbereitung und Mitunterzeichnung): Wenn die aktuelle Zuordnung die festgeschriebene qub_id nicht reproduziert und die Differenz durch die Periode teilbar ist, versucht er es erneut mit der Runde minus eins und versiegelt den fertiggestellten Pakt für die Runde, an die die qub_id tatsächlich bindet—niemals blind für die neu berechnete Runde, denn dadurch wäre das Artefakt dauerhaft nicht ableitbar.

Validierung: unlock_at MUSS zum Zeitpunkt der Versiegelung in der Zukunft liegen. unlock_at DARF NICHT mehr als 10 Jahre nach created_at liegen (um das Risiko langfristiger drand-Abhängigkeit zu begrenzen; die Benutzeroberfläche SOLLTE bei Entsperrdaten jenseits von 2 Jahren warnen).


5. Newtypes des Wire-Formats

Newtypes des Wire-Formats bieten Compile-Zeit-Sicherheit gegen das Verwechseln von CBOR-Bytes mit JSON, rohem Klartext oder anderen Byte-Codierungen.

Typ Enthält Erzeugt von Konsumiert von
SealedQubCbor Kanonisches CBOR von SealedQub serialize_sealed_qub() Inneres Wire-Artefakt; bei öffentlicher Zustellung unverpackt, bei privater Zustellung gewrappt gespeichert und anschließend vom Viewer wiederhergestellt
QubEnvelopeCbor Kanonisches CBOR von QubEnvelope serialize_qub_envelope() tlock-Encrypt-Eingabe, tlock-Decrypt-Ausgabe

5.1 Konstruktionsregeln

// 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 Validierung bei der Konstruktion

from_encoded() SOLLTE prüfen, dass die Eingabe mit einem gültigen CBOR-Map-Header beginnt. Die vollständige strukturelle Validierung erfolgt zum Parse-Zeitpunkt, nicht zum Konstruktionszeitpunkt, um doppeltes Parsen zu vermeiden.


6. Registry der Inhaltstypen

Wert Typ Maximale Body-Größe Anmerkungen
0x00 Reserviert (ungültig) — DARF NICHT verwendet werden
0x01 Klartext (UTF-8, eingeschränktes Markdown) 50 KB bezahlt / 10 KB kostenlos Siehe §10 für Rendering-Regeln. Die Aufteilung kostenlos / bezahlt wird vom Upload-Dienst durchgesetzt; die harte Obergrenze auf Protokollebene beträgt 50 KB.
0x02 Reserviert (zukünftig) — Für einen zukünftigen Inhaltstyp zugewiesen; nicht gültig in v1. Empfänger MÜSSEN gemäß der nachstehenden Regel ablehnen.
0x03 Pakt (bilaterale Vereinbarung, CBOR-Body) 100 KB Body ist kanonisches CBOR PactTerms (§6.1). Mitunterzeichner-Signierung gemäß §9.7.
0x04 Verdikt (Selbstbenotung der Ersteller:in, CBOR-Body) 8 KB Body ist kanonisches CBOR VerdictBody (§6.2). Wird ausschließlich durch die systemseitige verdict-Intention ausgegeben. Die Eltern-Beziehung steht auf dem Arweave-Tag Parent-Tx-Id, nicht im Body. Siehe verdict-uplift-plan §3.4.

Empfänger MÜSSEN unbekannte Inhaltstypen mit einem klaren, für den Benutzer sichtbaren Fehler ablehnen. Empfänger DÜRFEN unbekannte Typen NICHT als Text zu rendern versuchen.

6.1 Pakt-Body (content_type = 0x03)

Ein Pakt-Body ist die kanonische CBOR-Codierung eines PactTerms-Werts:

PactTerms {
    pact_version:  u8,                    // 0x01 for structured/v1
    title:         String,                // ≤ 200 bytes, NFC
    terms:         Vec<PactTerm>,         // ≤ 20 rows
    party_a:       PartyIdentifier,       // initiator
    party_b:       PartyIdentifier,       // counter-signer
    notes:         Option<String>,        // ≤ 5,000 bytes, NFC; absent key if none
}

PactTerm       { key: String (≤ 100 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }

Die kanonischen CBOR-Schlüsselreihenfolgen für alle drei Maps sind in §3.2 angegeben. Die insgesamt serialisierten Pakt-CBOR-Bytes DÜRFEN 100 KB NICHT überschreiten (entspricht §6).

Schema-Diskriminator. Die erste Zeile in terms für einen structured/v1-Pakt MUSS { key: "pact_schema", value: "structured/v1" } sein. Zeilen ohne diesen Marker sind „custom"-Pakte und erhalten keine strukturierte Validierung oder schemabewusste Darstellung.

Eingefrorene Bestätigungs-Slots. structured/v1-Pakte tragen genau vier Bestätigungszeilen unter diesen Schlüsseln:

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

Der value für jede ist eine von acht eingefrorenen englischen Zeichenketten, ausgewählt durch das Paar (role, kind), wobei role ∈ { seller, buyer, provider, client } und kind ∈ { standard, capacity }. Die Zeichenketten selbst sind normative Protokolldaten — die ML-DSA-65-Signaturen beider Parteien verpflichten sich auf die exakten Bytes über body_hash. Sie werden NICHT lokalisiert; der signierte Body ist sprachneutral. Jede Wortlautänderung erfordert eine neue Schemaversion (structured/v2).

Die acht Zeichenketten, ihre Auflösung (acknowledgement_for(role, kind)) und die Begründung jeder einzelnen sind durch die Referenzimplementierung fixiert. Konforme Implementierungen MÜSSEN bytidentische Bestätigungswerte ausgeben; SHA3-256-Body-Hash-Tests mit Golden-Fixtures, die alle vier Rollenkombinationen abdecken, fangen jegliche Drift ab.

Anzeigereihenfolge im Empfänger. Die Bestätigungszeichenketten enthalten Wendungen wie „described above", die voraussetzen, dass die Beschreibungs- / Umfangszeilen vor den Bestätigungen gerendert werden. Empfänger MÜSSEN das terms-Array in CBOR-Reihenfolge rendern; eine Umordnung bricht die Textsemantik.

Kontakt der Gegenpartei. Wenn der contact von Partei B eine gültige E-Mail-Adresse ist, versendet der qub-Upload-Dienst beim Staging automatisch eine Einladungs-E-Mail zur Prüfung / Mitunterzeichnung und bindet die spätere Mitunterzeichnung an die Verifizierung derselben Adresse (§9.7). Pakte, deren Kontakt der Partei B abwesend ist, können dennoch mitunterzeichnet werden, jedoch nur über einen Out-of-Band-Kanal — der Dienst lehnt Mitunterzeichnungsanfragen ab, die keinen passenden 15-minütigen E-Mail-Verifizierungsmarker erzeugen können.

6.2 Verdikt-Body (content_type = 0x04)

Ein Verdikt-Body ist die kanonische CBOR-Codierung eines VerdictBody-Werts:

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
}

Kanonische CBOR-Schlüsselreihenfolge:

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

Die insgesamt serialisierten Verdikt-CBOR-Bytes DÜRFEN 8 KB NICHT überschreiten (entspricht der obigen Registry-Zeile).

Ausgangs-Enum. Das Wire-Byte ist intentionsneutral; die vier Kategorien Right / Partial / Wrong / Unfalsifiable decken den Ausgangsraum jeder verdiktfähigen Intention ab. Pro-Intention-Bezeichnungen („Richtig getippt" / „Eingehalten" / „Ausgeliefert" / „Bestätigt" für Right usw.) sind eine empfängerseitige Rendering-Angelegenheit, die gegen die Intention des Eltern-qubs aufgelöst wird — die Leitung bleibt sprach- und intentionsneutral. Werte außerhalb von 1..=4 MÜSSEN beim Decodieren abgelehnt werden.

Eltern-Verknüpfung. Ein Verdikt-qub trägt die Eltern-Referenz NICHT in seinem Body. Die Arweave-Transaktions-ID des Eltern-qubs wird beim Upload als Speicher-Tag Parent-Tx-Id ausgegeben (§7 Speicher-Tag-Schicht). Dadurch bleibt der Body eine in sich geschlossene signierte Selbsteinschätzungs-Aussage; die Audit-Kette („wobei recht?") wird über das Arweave-Tag-Lookup hergestellt.

Sicherheit der Beleg-URL (normativ). Wenn evidence_url vorhanden ist, MÜSSEN Validatoren (compose-seitig, wire-seitig, Worker-Edge) Folgendes durchsetzen:

  1. Nur HTTPS. Die Zeichenkette MUSS mit der Byte-Sequenz https:// beginnen. Jedes andere Schema — http, ftp, javascript, data, file usw. — wird abgelehnt.
  2. Längenbegrenzung. ≤ 2.048 Bytes (praktische Browser-URL-Grenze).
  3. NFC + Prüfung auf feindliche Codepunkte. Dieselbe Regel wie für title und reflection — Bidi-Override-, Zero-Width-, Tag-Block-, BOM-, C0- und C1-Codepunkte werden abgelehnt. Die Definition entspricht dem Rust-crate::handle::contains_hostile_text_codepoint und dem TS-workers/api/src/utils/unicode.ts::isHostileCodepoint (im Gleichschritt halten).
  4. Keine Leerzeichen, keine ASCII-Steuerzeichen. Leerzeichen / DEL / Bytes unter 0x20 an beliebiger Stelle in der URL werden abgelehnt — schließt den \n/\t-Injektionsvektor, den die Bidi-Regel nicht abdeckt.
  5. Nicht-leeres Host-Segment. Alles zwischen https:// und dem ersten /, ? oder # MUSS nicht leer sein.

Kein serverseitiges Abrufen. Der Worker DARF die URL NICHT per Proxy abrufen, laden oder voranzeigen. Das Protokoll speichert eine Zeichenkette; das Rendering erfolgt empfängerseitig mit rel="nofollow noopener noreferrer" target="_blank" und einem sichtbaren Host, der neben dem Link-Text angezeigt wird.

Reflexion. Optionaler, von der Ersteller:in verfasster Reflexionstext („was hat sich verändert, was haben Sie gelernt"). Dieselbe NFC- und Prüfung auf feindliche Codepunkte wie bei title. Leere oder nur aus Leerraum bestehende Eingaben werden zur Konstruktionszeit auf abwesend kollabiert.

Schemaversion. v1 unterstützt ausschließlich verdict_version = 0x01. Künftige Schemarevisionen erhöhen dieses Byte und werden zusammen mit einer neuen Protokollversion gemäß §12 eingeführt.


7. Versiegelungsprotokoll

Die vollständige Versiegelungssequenz. Jeder Schritt ist normativ.

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

Speicher-Tag-Schicht (out-of-band). Der qub-Upload-Dienst hängt neben der ausgewählten Upload-Nutzlast einen bewusst kleinen Satz von Speicher-Transaktions-Tags an. Content-Type=application/octet-stream ist normativ vorgeschrieben. Der Referenzdienst hängt zusätzlich drei optionale Tags an, wenn der Ersteller sie offenlegen möchte: Intent (gegen die genaue Allowlist validierte Compose-Intention: announcement, thesis, prediction, letter, secret, commitment, proof oder das systemseitig ausgegebene verdict), Author (Fingerabdruck des §9.3-Pubkeys des Erstellers als 64-stelliger Kleinbuchstaben-Hex) und Parent-Tx-Id (Speicher-Transaktions-ID des übergeordneten qubs für Antwortketten, 43-stellige base64url).

Der Author-Tag ist opt-in pro qub: Die Referenz-Ersteller-App hängt ihn nur an, wenn der Benutzer die öffentliche Zuschreibung zum Versiegelungszeitpunkt explizit aktiviert. Wenn der Schalter aus ist — der Standard — wird kein Author-Tag geschrieben und der qub bleibt auf der Chain unzugeordnet: nichts im dauerhaften Speicher verknüpft den Upload mit dem Handle, der E-Mail oder anderen qubs eines Erstellers. Wenn der Schalter an ist, löst sich der Author-Fingerabdruck über die §9.5-Attestierungskette zum vom Ersteller gewählten @handle auf. Antwortkettenbeziehungen und Intent sind nicht identifizierend. Bei privater Zustellung verschlüsselt der äußere Wrapper (§13) das erkennbare innere SealedQub-Artefakt; das Sammeln gespeicherter Wrapper und der Bezug öffentlicher drand-Signaturen reichen daher weiterhin nicht aus, um den Body ohne K wiederherzustellen. Speicher-Tags bleiben bewusst öffentliche Metadaten.

Der Referenzdienst hängt absichtlich KEINE Tags App-Name, App-Version oder Type an: Jeder solche Filter mit einzelnem Wert würde den gesamten qub-Korpus an eine GraphQL-Abfrage zurückgeben, was mit dem rein körperbezogenen Vertraulichkeitsumfang des Wrappers unvereinbar ist.

Ein konformer Verifizierer DARF sich für die Drittparteien-Verifizierung gemäß §11 NICHT auf irgendeinen Speicher-Tag verlassen; der Body-Hash / die qub_id / die Signatur verpflichten sich nur auf das innere CBOR, niemals auf den Tag-Satz.


8. Entsperrprotokoll

Die vollständige Entsperrsequenz. Jeder Schritt ist normativ.

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

9. Autorensignatur

9.1 Begründung

qubs werden im dauerhaften Speicher gespeichert. Autorensignaturen müssen unbegrenzt unfälschbar bleiben, weshalb v1.0 das post-quantensichere ML-DSA-65-Verfahren (FIPS 204) verwendet anstelle eines klassischen Verfahrens, dessen Sicherheit innerhalb der dauerhaften Lebensdauer des qubs degradieren könnte.

9.2 Algorithmus-Registry

sig_alg Verfahren Schlüsselgröße Signaturgröße Status
0x00 Keine Signatur (unsigniert) — — Aktiv
0x01 ML-DSA-65 (FIPS 204) 1.952 Bytes 3.309 Bytes Aktiv
0x02 Ed25519 32 Bytes 64 Bytes Reservierte Konstante; in Protokoll v1 nicht unterstützt

Viewer des Protokolls v1 MÜSSEN jeden Wert außerhalb von {0x00, 0x01} ablehnen, einschließlich des reservierten Werts 0x02. Die Reservierung verhindert eine versehentliche Wiederverwendung; sie stellt keine Aktivierung dar. Ihre Aktivierung erfordert die in §15 beschriebene kontrollierte Änderung.

9.3 Konstruktion des signierten Preimage

Es haben zwei Preimage-Versionen existiert. Alle Signaturen MÜSSEN V2 verwenden, und Verifizierer MÜSSEN ausschließlich V2 akzeptieren. Das veraltete V1-Preimage (nachfolgend zur historischen Referenz dokumentiert) wurde während der V2-Migration als reiner Verifizierungs-Fallback akzeptiert; dieser Fallback wurde eingestellt, und eine reine V1-Signatur wird nun abgelehnt.

V2 (aktuell — erzeugt von jeder neuen Autorensignierung sowie von beiden Signaturen des Pakt-Staging-/Mitunterzeichnungs-Flows):

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 folgt derselben Abwesenheits-Sentinel-Konvention wie title_hash (§4.2.1): 32 Nullbytes sind keine gültige SHA3-256-Ausgabe, sodass „abwesend" niemals mit einem vorhandenen Label kollidieren kann. Alle Felder haben feste Breite, sodass das Preimage ohne Längenpräfixe eindeutig ist.

V1 (veraltet — EINGESTELLT; wird nicht mehr erzeugt und bei der Verifizierung nicht mehr akzeptiert):

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

Das V1-Preimage ließ sender_label und reply_to aus. Es wurde während der Migration zu V2 als reiner Verifizierungs-Fallback akzeptiert; dieser Fallback wurde seither eingestellt — Verifizierer MÜSSEN ausschließlich das V2-Preimage akzeptieren. Die Definition wird hier zur historischen Referenz und zur Erläuterung des nachfolgenden Domain-Separators beibehalten. Eine Signatur, die nur gegen V1 verifiziert, MUSS als Verifizierungsfehler behandelt werden.

Domain-Separatoren: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" umfassen jeweils 17 ASCII-Bytes ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Kein Padding. Der abweichende Separator trennt die beiden Konstruktionen domänenmäßig, sodass eine Signatur über das eine Preimage niemals als das andere verifiziert werden kann.

org_id_present-Byte: Das auf unlock_at folgende Byte MUSS 0x00 sein. Die Referenzimplementierung legt dies als Konstante ORG_ID_PRESENT_INDIVIDUAL = 0x00 in crates/qub-core/src/signing.rs offen; Empfänger, die sig_input zur Verifizierung rekonstruieren, MÜSSEN dasselbe Byte ausgeben.

Signaturumfang — was abgedeckt ist und was nicht. Das V2-sig_input verpflichtet sich direkt auf version, qub_id, body_hash, unlock_at, sender_label und reply_to (zuzüglich des festen Domain-Separators und des org_id_present-Bytes). qub_id selbst wird über das §4.1-Preimage aus version, content_type, created_at, unlock_at, outcome_at, drand_round und body_hash abgeleitet, sodass jede Änderung an diesen Feldern eine andere qub_id erzeugt und die Signatur transitiv invalidiert. Die authentifizierte Oberfläche ist somit:

Feld Durch Signatur authentifiziert Wie
version ✓ Direkte Eingabe für sig_input
qub_id ✓ Direkte Eingabe
body_hash ✓ Direkte Eingabe
unlock_at ✓ Direkte Eingabe
sender_label ✓ Direkte Eingabe über sender_label_hash (V2-Preimage — die einzige akzeptierte Form)
reply_to ✓ Direkte Eingabe über reply_to_or_zero (V2-Preimage — die einzige akzeptierte Form)
content_type ✓ Transitiv, über das qub_id-Preimage
created_at ✓ Transitiv, über das qub_id-Preimage
outcome_at ✓ Transitiv, über das qub_id-Preimage
drand_round ✓ Transitiv, über das qub_id-Preimage
body ✓ Transitiv, über body_hash = SHA3-256(body)
author_pubkey — (implizit) Der Schlüssel, der die Signatur verifiziert hat, ist per Definition der Autor
cosigner_pubkey / cosigner_signature — Unabhängig über dasselbe sig_input signiert (siehe §9.7)
drand_chain_id, tlock_ciphertext, visibility — Felder des äußeren SealedQub, außerhalb des Envelopes — abgedeckt durch ihre eigenen strukturellen Invarianten (Konsistenz von Runde / Chain), aber nicht durch die Autorensignatur. (drand_round ist nun transitiv über das qub_id-Preimage gebunden — siehe oben.)

Warum V2 das einzige akzeptierte Preimage ist.

Implementierungen, die sender_label oder reply_to Endbenutzern anzeigen, MÜSSEN die authentifizierte Identität (Pubkey-Fingerabdruck, Attestierung) als primäres Identitätssignal hervorheben, nicht das Label.

9.4 Verifizierungsverfahren

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

Die Signaturverifizierung ist die teuerste Operation (insbesondere ML-DSA-65). Sie SOLLTE durchgeführt werden, nachdem alle günstigeren Prüfungen (Hash, qub_id, unlock_at) bestanden wurden.

9.5 Identitäts-Attestierungen

Identitäts-Attestierungen — die Zuordnung von author_pubkey zu menschlich erkennbaren Identitätsansprüchen wie einem qub-Handle, einer E-Mail-Adresse, einem Social-Handle oder einem Passkey-Credential — sind eine Progressive Enhancement auf Empfängerseite und sind für die Signaturverifizierung nicht erforderlich. Empfänger, die Attestierungen zu einer Anzeigeidentität auflösen, MÜSSEN folgende Präzedenz anwenden:

handle > email > social > fingerprint

Das Fingerabdruck-Fallback ist der Kleinbuchstaben-Hex von SHA3-256(author_pubkey); es ist immer für jeden signierten qub verfügbar. Empfänger DÜRFEN es zur Anzeige abkürzen — der Referenz-Empfänger rendert qub: gefolgt von den ersten und letzten vier Bytes (qub:<8 hex>…<8 hex>).

Ein konformer Verifizierer kann jede Prüfung in §9.4 abschließen, ohne die qub-API zu kontaktieren, ohne ein Netzwerk außer dem dauerhaften Speicher und drand und ohne irgendeine serverseitige Suche. Die Attestierungsauflösung ist ein separater Best-Effort-Schritt, der erst nach erfolgreicher Signaturverifizierung durchgeführt wird.

9.6 Größenauswirkung

Ed25519 ML-DSA-65
Signatur 64 Bytes 3.309 Bytes
Public Key 32 Bytes 1.952 Bytes
Insgesamt pro qub 96 Bytes 5.261 Bytes
Speicherkostendelta (bei ~$5/MB) ~$0,0005 ~$0,026

Für einen Text-qub von 500–2.000 Bytes verdreifacht ML-DSA-65 die gespeicherte Größe in etwa. Die absoluten Kosten sind vernachlässigbar.

9.7 Mitunterzeichner-Verifizierung (bilaterale Pakt-Vereinbarungen)

Für bilaterale Vereinbarungen (content_type = 0x03) beweist eine zweite Signaturschicht, dass beide Parteien denselben Bedingungen zugestimmt haben.

Envelope-Felder:

Beide Felder MÜSSEN gemeinsam vorhanden oder gemeinsam abwesend sein. Wenn genau eines vorhanden ist, MÜSSEN Empfänger einen Integritätsfehler melden.

Verifizierungsverfahren:

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

Eigenschaften:

E-Mail-Bindungs-Gate (operativ). Wenn ein gestagter Pakt einen E-Mail-Kontakt der Partei B trägt (§6.1), MUSS der qub-Upload-Dienst die Mitunterzeichnungsanfrage ablehnen, sofern kein kurzlebiger E-Mail-Verifizierungsmarker existiert, der sowohl die Staging-ID als auch den Hash der normalisierten E-Mail dieses Kontakts erfasst. Der Marker wird von /api/v1/auth/verify geschrieben, wenn das Magic-Link-Token eine staging_id trägt und die verifizierte Adresse mit SHA-256(normalise_email(party_b.contact)) übereinstimmt — wobei normalise_email(addr) die Groß-/Kleinschreibung des Local-Parts beibehält und nur den Domain-Teil in Kleinbuchstaben umwandelt (gemäß RFC 5321 §2.3.11) und SHA-256 hier der NIST-FIPS-180-4-Hash ist (verschieden vom in den §4-Ableitungen verwendeten SHA3-256) — und läuft 900 Sekunden (15 Minuten) nach der Ausstellung ab. Dies ist ein operatives Anti-Imitations-Gate, KEIN Bestandteil des On-Chain-qub-Beweises — ein Drittparteien-Verifizierer, der §11 nachvollzieht, benötigt nur den dauerhaften Speicher und drand, ohne irgendeine serverseitige Suche. Der Marker existiert ausschließlich serverseitig und ist niemals Teil des signierten Bodys.

Größenauswirkung (ML-DSA-65 Autor + Mitunterzeichner):

Komponente Größe
Autorensignatur 3.309 Bytes
Autoren-Public-Key 1.952 Bytes
Mitunterzeichner-Signatur 3.309 Bytes
Mitunterzeichner-Public-Key 1.952 Bytes
Krypto-Gesamtoverhead 10.522 Bytes
Speicherkostendelta ~$0,05

10. Markdown-Rendering und -Sanitisierung

Dieser Abschnitt ist sicherheitskritisch. Der Empfänger rendert Text-qubs (content_type = 0x01) unter Verwendung einer eingeschränkten Markdown-Untermenge.

10.1 Erlaubte Elemente

10.2 Verbotene Elemente

Element Behandlung
Rohes HTML (<div>, <script>, etc.) Vollständig entfernt. Kein HTML wird durchgereicht.
Bilder (![alt](url)) Entfernt. Bildsyntax wird aus der Ausgabe gestrichen.
Links ([text](url)) URL als sichtbarer Klartext gerendert. Nicht automatisch verlinkt. Nicht klickbar ohne explizite Benutzeraktion.
Gefährliche URL-Schemata javascript:, data:, vbscript:, file: — entfernt.
Iframes, Embeds, Objects Entfernt.
HTML-Entities Nur dann zu Anzeigezeichen dekodiert, wenn dies sicher ist.

10.3 Implementierung

Implementierungen MÜSSEN einen strikten Allowlist-Parser verwenden, keine Blocklist. Der empfohlene Ansatz:

  1. Markdown mit pulldown-cmark (oder gleichwertig) parsen.
  2. Den AST durchlaufen und jeden Knoten verwerfen, der nicht in der Allowlist (§10.1) enthalten ist.
  3. Für Link-Knoten: Die URL als sichtbaren Text ausgeben, nicht als anklickbares <a>-Element.
  4. Den gefilterten AST in eine typisierte Zwischendarstellung umwandeln (z. B. ein MarkdownNode-Enum mit ausschließlich sicheren Varianten). Rohes HTML ist in dieser IR strukturell nicht repräsentierbar.
  5. Aus der typisierten IR auf die Zielsicht-Ebene rendern (z. B. reaktive View-Komponenten, DOM-Knoten). Keine HTML-Stringverkettung oder innerHTML an irgendeiner Stelle.

Blocklist-Ansätze sind brüchig, da neue Markdown-Erweiterungen oder Parser-Eigenheiten ungefilterte Elemente einführen können. Der Ansatz mit typisiertem AST macht XSS strukturell unmöglich — es gibt keine Variante, die beliebiges HTML tragen kann.

10.4 Größen- und Strukturgrenzen


11. Drittparteien-Verifizierung

Jede dritte Partei, die über die gespeicherten Bytes (und bei einem privaten/verpackten qub über K) verfügt, kann das kryptografische Artefakt ohne Mitwirkung von qub verifizieren. Eine unabhängig mit Zeitstempel versehene Existenzaussage erfordert zusätzlich entweder eine verifizierte Aufnahme der einzelnen qub-Transaktion in den dauerhaften Speicher oder einen verifizierten Transparenz-Log-Beweis gemäß §16.

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

Was die Verifizierung beweist:

Beweiseingabe Was sie feststellt
Gültiges Bündel / versiegeltes Artefakt + drand-Signatur Der wiederhergestellte Body entspricht body_hash; die in qub_id gebundenen Metadaten sind intakt; der Chiffretext ist an die angegebene drand-Runde gebunden; und diese Runde ist verstrichen. Dies belegt nicht, wann der Chiffretext erstellt wurde.
Gültige V2-Autoren-/Mitunterzeichnersignatur Die besitzende Person bzw. die besitzenden Personen der entsprechenden geheimen Schlüssel haben die signierte Oberfläche aus §9.3 authentifiziert.
Unabhängig verifizierte Speichertransaktion pro qub Der exakte gespeicherte Chiffretext existierte spätestens zu ihrem Blockzeitstempel.
Gültiger verankerter Transparenz-Log-Beweis Die blattartspezifische Aussage aus §16.11, einschließlich einer zeitlichen Obergrenze der Verpflichtung aus dem Ankerblock.

Was die Verifizierung NICHT beweist:

Nicht-Beweis Warum
Urheberschaft Das sender_label ist dekorativ. Ohne sig_alg ≥ 0x01 könnte jeder diesen Inhalt versiegelt haben.
Absicht Das Artefakt beweist Bytes und kryptografische Beziehungen, nicht das, was die erstellende Person subjektiv meinte.
Vorbestehende Verpflichtung allein aus .qub Eine erstellende Person kann nach Ablauf der gebundenen Runde ein gültiges Bündel zusammenstellen. Die eingebettete drand-Signatur beweist, dass die Runde verstrichen ist, nicht, dass der Chiffretext vorher existierte.
Exakte Zeit des Klicks auf „Versiegeln" Der Zeitstempel eines Speicher- oder Ankerblocks ist eine unabhängig verifizierbare Obergrenze und kann der lokalen Aktion des Benutzers nachlaufen. Aussagen zu sealed_at / received_at haben keine Beweiskraft.

Das implementierte Transparenz-Log (§16) erweitert die Verifizierung über mehrere qubs hinweg um eine manipulationssichtbare Reihenfolge und eine vertrauenslose zeitliche Obergrenze der Verpflichtung (die Ankerblockzeit), eingegrenzt nach Blattart (§16.11). Es fügt weder Urheberschaft noch Absicht hinzu; beim standardmäßigen byteblinden Upload-Pfad beweist es selbst weder body_hash noch drand_round, die weiterhin aus den Artefaktprüfungen stammen.


12. Versionierung und Release-Steuerung

Dokument-Releases, das innere Wire-Protokoll und der äußere Wrapper sind getrennte Versionsräume. Eine reine Klarstellung im Dokument ändert daher nicht stillschweigend Bytes, und eine künftige Wire-Migration kann sich nicht als redaktionelle Revision ausgeben.

12.1 Dokument-Release-Version

Diese Spezifikation verwendet semantische Dokument-Releases (MAJOR.MINOR.PATCH) und ein unveränderliches Git-Tag namens protocol-v<release>.

Der Release-Status ist Entwurf (noch nicht normativ), Aktuell (das einzige empfohlene Implementierungsziel) oder Abgelöst (für historische Verifizierung aufbewahrt). Die unversionierte Route /protokoll zeigt das aktuelle Release; das Release-Tag bewahrt dessen exakte Quelle und jedes damit veröffentlichte Gebietsschema. Eine Änderung des Status oder der Release-Nummer erfordert, diese Tabelle und den Release-Verlauf in derselben geprüften Änderung zu aktualisieren.

Dokument-Release Gültig ab Status Wire-Protokoll Wrapper Quelle
1.0.0 2026-09-23 Aktuell 0x01 0x01 protocol-v1.0.0

12.2 Protokollversion

Das Feld version (u8) sowohl in SealedQub als auch in QubEnvelope identifiziert die Hauptversion des Protokolls.

12.3 Protokollversionsverlauf

Version Wert Beschreibung
v1 0x01 Private/verpackte und öffentliche/unverpackte Bereitstellung; Text- (0x01), Pakt- (0x03) und Verdikt-Bodies (0x04); ML-DSA-65-V2-Autoren-/Mitunterzeichnersignierung; drand-quicknet-tlock; SHA3-256.

12.4 Vorwärtskompatibilität

Ein v1-Empfänger, der auf ein QubEnvelope mit unbekannten CBOR-Map-Schlüsseln (Schlüssel, die nicht in der kanonischen Reihenfolge gemäß §3.2 enthalten sind) trifft, MUSS es mit einem Decodierfehler ablehnen (§3.1). Vorwärtskompatibilität stützt sich auf das Feld version, nicht auf Schlüsseltoleranz: Zukünftige Ergänzungen — selbst kleinere Metadaten — erscheinen unter einem neuen version-Wert, den ein v1-Empfänger mit einem klaren „neueres Protokoll"-Fehler ablehnt, statt stillschweigend Inhalte zu verwerfen, auf die sich die Signaturen festlegen.

Ein v1-Empfänger, der auf sig_alg = 0x01 (ML-DSA-65) trifft, jedoch keine ML-DSA-65-Verifizierungsunterstützung besitzt, SOLLTE den qub-Inhalt mit dem Hinweis „Signatur vorhanden, aber nicht verifizierbar" anzeigen, anstatt den qub vollständig abzulehnen. Die Referenzimplementierung lehnt heute jeden sig_alg-Wert außer 0x00 und 0x01 ab, weil die v1-Registry keinen weiteren gültigen Algorithmus enthält — strikte Ablehnung und Soft-Fail sind beobachtbar identisch, bis ein dritter Algorithmus registriert wird. Das oben beschriebene Soft-Fail-Verhalten wird tragend, sobald §9.2 einen neuen Eintrag zulässt, und der Referenz-Empfänger wird zu diesem Zeitpunkt auf Soft-Fail umgestellt.

12.5 Äußere Wrapper-Version

Der in §13 beschriebene OuterWrapper trägt sein eigenes version-Byte, unabhängig von SealedQub.version und QubEnvelope.version. Die beiden Versionsräume entwickeln sich getrennt: Ein zukünftiger post-quantensicherer symmetrischer Ersatz erhöht das Wrapper-Byte, ohne die innere Protokollversion zu berühren, und eine zukünftige Ergänzung auf Protokollebene (z. B. ein neues Envelope-Feld) erhöht die innere Version, ohne das Wrapper-Byte zu berühren.

OUTER_WRAPPER_VERSION_* Wert Algorithmus Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM mit 12-Byte-Nonce, 16-Byte-Authentifizierungs-Tag, AAD an qub_id gebunden Für private Zustellung aktiv
— 0x02–0xFF Reserviert Zukunft

Empfänger MÜSSEN unbekannte Wrapper-Versionen mit einem klaren Fehler ablehnen. Das Protokoll hält den Wrapper-Versionsraum bewusst eng, bis ein konkreter Migrationstreiber auftritt (z. B. NIST-Empfehlung für ein anderes AEAD); ein 0x02-Slot wird in derselben Revision zugewiesen, die den Algorithmus einführt.


13. Äußerer Verschlüsselungs-Wrapper

13.1 Begründung

Die Protokollschichten (QubEnvelope → tlock → SealedQub) machen einen versiegelten qub zeitversiegelt: Der Body ist bis unlock_at und bis zur Veröffentlichung der drand-Rundensignatur unlesbar. Nach der Entsperrung ist die Rundensignatur jedoch öffentlich, und die kanonische CBOR-Form von SealedQub ist erkennbar, sodass ein Harvester, der Transaktionen im dauerhaften Speicher indexiert hätte, den gesamten qub-Korpus massenhaft entschlüsseln könnte.

Für die private Bereitstellung schließt der äußere Verschlüsselungs-Wrapper diesen Kanal, indem er eine zusätzliche symmetrische AEAD-Schicht zwischen das kanonische SealedQubCbor und die gespeicherten Bytes setzt. Im Browser-Versiegelungspfad lebt der 256-Bit-Schlüssel K nur im URL-Fragment des Lieferlinks und auf den Geräten der Benutzer; Browser übertragen URL-Fragmente nicht an Server, sodass qub.social, jedes Speicher-Gateway und jedes davorliegende CDN für K beobachtbar blind sind. Die gespeicherte Darstellung eines privaten qubs ist daher undurchsichtiger Chiffretext, dessen Klartext ohne die von der erstellenden Person gewählte Liefer-URL nicht wiederherstellbar ist. Die öffentliche Bereitstellung lässt diese Schicht bewusst weg (§13.8).

Nettoeffekt:

13.2 Schichtung

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

Versiegelung und Entsperrung auf der Protokollebene (§7, §8) bleiben unterhalb der Wrapper-Grenze unverändert; der Wrapper wird an der Aufrufstelle von seal() angefügt und an der Aufrufstelle von unlock() abgelöst.

13.3 OuterWrapper-Datenstruktur

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

Feldinvarianten.

CBOR-Codierung. Kanonisches CBOR gemäß §3, mit derselben Schlüsselsortierungsregel (sortiert nach codierter Bytelänge aufsteigend, dann lexikografisch). Die vier Schlüssel sind:

Schlüssel Codierte Bytes Reihenfolge
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Das erste Byte des OuterWrapper-CBOR ist daher der Map-Header definiter Länge für eine 4-Eintrags-Map (0xA4).

13.4 AAD-Bindung an qub_id

Der Wrapper bindet qub_id als zusätzliche authentifizierte AEAD-Daten. Dies ist die tragende strukturelle Verteidigung gegen drei Angriffsklassen:

Angriff Verteidigung
Ciphertext unter ein anderes qub_id-Feld im Wrapper verschieben AAD-Mismatch → AEAD-Authentifizierung schlägt fehl
Das URL-Fragment von qub A mit den gespeicherten Bytes von qub B mischen Falscher Schlüssel (und unabhängig gebundene AAD) → AEAD-Authentifizierung schlägt fehl
Das qub_id-Feld des Wrappers nach dem Upload manipulieren AAD-Mismatch → AEAD-Authentifizierung schlägt fehl

Das Mitführen von qub_id im Wrapper-Klartext schwächt die Aufzählungsimmunität nicht wesentlich — qub_id ist selbst ein SHA3-256-Hash des §4.1-Preimage ohne aus dem Digest wiederherstellbares Preimage, und ein Aufzählender, der die Wrapper-Bytes bereits gesammelt hat, lernt aus der sichtbaren qub_id nichts hinzu, was er nicht bereits aus der Existenz des Uploads selbst ableiten könnte.

13.5 Wrap- und Unwrap-Algorithmen

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

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

Kollaps der Fehlermodi. Falscher K, falsche Nonce, AAD-Mismatch und manipulierter Ciphertext erzeugen alle denselben DECRYPT_FAILED-Fehler. Dies ist eine bewusste AEAD-Eigenschaft: Eine Unterscheidung des Fehlermodus würde einen Seitenkanal schaffen, den ein entfernter Angreifer durch das Senden von missgestalteten Wrappern und das Messen der Antwortzeit sondieren könnte. Referenzimplementierungen MÜSSEN alle AEAD-Fehler zu einer einzigen Fehlerform zusammenfassen.

13.6 Schlüsselmaterial und Verteilung

Der Wrapping-Schlüssel K ist ein 256-Bit-uniformer-Zufallswert, der pro qub von einem CSPRNG generiert wird. Die Referenzimplementierungen beziehen ihn von:

Verteilung: K MUSS als URL-sicheres Base64 (RFC 4648 §5, ohne Padding) codiert und als Fragment-Komponente an die Liefer-URL angehängt werden:

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

Das Fragment wird von einem konformen Browser niemals an irgendeinen Server übertragen. Wiederherstellungskanäle (serverseitiger Verlaufsindex, opt-in E-Mail-Auto-Versand), die den vollständigen Lieferlink — einschließlich des Fragments — über das Gerät des Benutzers hinaus persistieren, sind ein expliziter Trade gegen die standardmäßige Crypto-Shredding-Haltung und MÜSSEN an ausdrückliche Benutzerzustimmung gekoppelt sein.

Verlust des Fragments. Wenn ein Benutzer das URL-Fragment verliert und keinen Wiederherstellungskanal hat, ist der qub unlesbar. Dies ist der tragende Trade-off des Designs und MUSS dem Benutzer zum Versiegelungszeitpunkt offengelegt werden. Das MVP verstärkt die Offenlegung zum Versiegelungszeitpunkt mit explizitem „diese URL speichern"-Text und einem verifizierten E-Mail-Wiederherstellungskanal für Benutzer, die zustimmen.

13.7 Außerhalb des Geltungsbereichs dieses Abschnitts

13.8 Öffentliche qubs (Weglassen des Wrappers)

Der äußere Wrapper ist auf der Lieferebene optional. Eine Ersteller:in kann einen qub als öffentlich versiegeln; dann gelangt das kanonische SealedQubCbor direkt in die Speicherpipeline, ohne OuterWrapper-Schicht und ohne Schlüssel K:

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

Ein öffentlicher qub ist zeitversiegelt, aber nicht linkgesteuert: Er bleibt unlesbar, bis seine drand-Runde veröffentlicht wird (die tlock-Schicht ist unverändert), aber nach der Entsperrung kann ihn jeder mit der Speichertransaktions-ID entschlüsseln — es ist kein URL-Fragment erforderlich, weil es kein K gibt. Dies ist der bewusste Trade-off für Oberflächen, die der Server ansteuern muss: Enthüllungs-Benachrichtigungs-E-Mails, fragmentlose oEmbed-/Auto-Embed-Links und reichhaltigere SEO nach der Enthüllung benötigen einen Link, der ohne ein Geheimnis funktioniert, das der Server nie hält (§13.6). Ein privater qub kann weiterhin die ausdrückliche Form <qub-embed src="full_delivery_url"> verwenden, wenn die veröffentlichende Person die vollständige fragmenttragende Berechtigung bereitstellt.

Konsequenzen, die ein Erzeuger berücksichtigen MUSS:

Privat (gewrappt) bleibt der Standard; öffentlich ist eine explizite Ersteller-Wahl pro qub.


14. Testvektoren

14.1 qub_id-Ableitung

Input:
  version      = 0x01
  content_type = 0x01
  created_at   = 1735689600 (2025-01-01 00:00:00 UTC)
  unlock_at    = 1736294400 (2025-01-08 00:00:00 UTC)
  outcome_at   = absent
  drand_round  = 4695446  (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, drand mainnet params §14.2)
  body         = "Hello, future."  (UTF-8, 14 bytes)
  title        = absent

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

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

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

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

Implementierungen MÜSSEN für diese Eingabe identische body_hash- und qub_id-Werte erzeugen. Dieser Testvektor SOLLTE der erste geschriebene Unit-Test sein. Die oben angegebenen kanonischen Werte wurden von der Referenzimplementierung berechnet und MÜSSEN bitgenau übereinstimmen. Historische Prototyp-Layouts vor dem Launch (von den ersten beiden hingen keine Live-qubs ab) verwendeten 92 Bytes vor outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) und 100 Bytes nach dem Hinzufügen von outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Das aktuelle 108-Byte-Layout fügte anschließend drand_round und den Domain-Separator QUB_ID_V2 hinzu. Ein früher 108-Byte-Vektor verwendete die Legacy-ceil-Rundenzuordnung (drand_round = 4695445) und ergab 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—weiterhin eine gültige qub_id für diese Rundeneingabe, während das obige Beispiel der aktuellen Rundenzuordnung aus §4.3 folgt.

14.2 Zuordnung von Entsperrzeitpunkt zu Runde

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

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

drand_round = 4675286

Runde 4675286 wird zu 1595431050 + (4675286 - 1) * 30 = 1735689600 veröffentlicht—exakt bei unlock_at, niemals davor. (Die Legacy-ceil-Zuordnung vor dem Release ergab 4675285, veröffentlicht zu 1735689570—30 Sekunden zu früh; Verifizierer akzeptieren diese Legacy-Runde gemäß §4.3.)

14.3 Kanonisches CBOR-Round-Trip

Implementierungen MÜSSEN überprüfen, dass serialize(parse(serialize(qub))) == serialize(qub) für alle gültigen Eingaben gilt. Dies ist ein Property-Test, kein einzelner 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)

Die kanonischen CBOR-Bytes und der SHA3-256-body_hash werden von der Referenzimplementierung berechnet. Implementierungen MÜSSEN für diese Eingabe bytidentisches CBOR erzeugen.

Implementierungen MÜSSEN außerdem überprüfen, dass serialize(parse(serialize(pact))) == serialize(pact) für alle gültigen PactTerms-Eingaben gilt (Property-Test).

14.5 Cross-Language-Vektoren des äußeren Wrappers

Der äußere Wrapper (§13) hat eine separate kanonische Fixture unter crates/qub-core/tests/vectors/wrapper_v1.json. Jeder Fall fixiert ein Tupel (key, nonce, qub_id, sealed_cbor) als opake Hex-Eingaben und behauptet eine spezifische expected_wrapper_hex-Ausgabe. Beide Referenzimplementierungen konsumieren dieselbe JSON-Datei:

Die Fixture fixiert derzeit drei Low-Level-Wrapper-Fälle. Sie testen die deterministische OuterWrapper-Codierung und die AEAD-Interoperabilität unabhängig von der Invariante der Zustellungsform aus §13.8; insbesondere machen der historische Name basic-text-public und seine innere visibility = 0x01 die resultierenden gewrappten Bytes nicht zu einer konformen öffentlichen Zustellung. Ein Erzeuger MUSS öffentliche innere Bytes weiterhin unverpackt speichern und nur private (0x00) innere Bytes wrappen.

Fall Abdeckung
basic-text-public Historischer Name des Low-Level-Fixtures. Kleinste realistische SealedQub-Form ohne optionale Felder; testet nur Wrapper-Bytes und ist keine konforme gespeicherte Zustellung gemäß §13.8.
with-recipient-pubkey SealedQub mit gesetztem recipient_pubkey (reservierter zukünftiger Pfad). Übt einen anderen inneren CBOR-Schlüsselsatz aus; der unterschiedliche Fixture-Inhalt ergibt unabhängig eine andere qub_id (recipient_pubkey selbst ist nicht im §4.1-Preimage enthalten).
longer-body Body mit ~4 KiB — übt Multi-Byte-CBOR-Längenpräfixe sowohl im inneren Envelope als auch im äußeren Ciphertext.

Implementierungen MÜSSEN für die aufgezeichneten Eingaben bytidentisches expected_wrapper_hex erzeugen. Die Neuerzeugung der Fixture erfordert QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors und ist deliberaten Formatänderungen vorbehalten.


15. Governance des Krypto-Profils (Zukünftig)

Dieser Abschnitt ist für v1 informativ und wird normativ, sobald zum ersten Mal ein zweiter Algorithmus in eines der kryptografischen Primitive von qub eintritt.

15.1 Aktuelle Haltung

Protokoll v1 bindet pro Primitiv genau einen Algorithmus:

Verifizierer kodieren Schlüssel- und Signaturlängen derzeit pro aktivem Primitiv fest. Die Bytes für sig_alg und die Wrapper-Version sind explizite Selektoren, doch v1 führt keine In-Band-Aushandlung durch und lässt nur die oben aufgeführten aktiven Werte zu.

15.2 Vorgesehene Form

Wenn ein zweiter Algorithmus in das Protokoll eintritt, wird der Verifizierer für ein benanntes CryptoProfile (z. B. ExqubV1) konfiguriert, das den exakten Satz zulässiger Werte pro Primitiv auflistet — sig_algs, drand-Chains, Wrapper-Versionen, Inhaltstypen. Das Profil wird zum Verifizierungszeitpunkt festgelegt, niemals in-band ausgehandelt. Jeder Wert außerhalb des aktiven Profils wird abgelehnt.

Dies garantiert, dass das Hinzufügen von ML-DSA-87 oder die Aktivierung von Ed25519 bestehende Verifizierer-Konfigurationen nicht rückwirkend schwächen kann: Ein v1-Verifizierer bleibt ein v1-Verifizierer, auch nachdem ein v2-Profil veröffentlicht wurde.

15.3 Auslösebedingungen

Stufen Sie §15 in den normativen Status hoch, sobald eines der Folgenden vorgeschlagen wird:

Bis dahin ist §15 ein Platzhalter, der die Migrationsform fixiert, damit zukünftige PRs gegen ein bekanntes Ziel landen, anstatt die Aushandlungsoberfläche von Grund auf neu zu verhandeln.


16. Transparenzprotokoll und Haltbarkeitsebenen (implementiert — Überprüfung abgeschlossen)

Status. Dieser Abschnitt ist implementiert (W5/UP-B1, Stufen 1–8), wobei der Produzent und der Trust-Root-Bereich hier angegeben sind. Die Drahtformate, Hashing-Methoden und Verifiziererpfade sind aktiv: die Kern-Merkle- + canonical-CBOR-Typen (qub-core), das TypeScript-Spiegelbild + ANS-104-Bündler (workers/api/src/crypto/), der Single-Writer LogDO + koordinatenbasiertes R2-Knotenspeicher, der /upload Log-Append-Versuch, die täglichen Anchor- + Bündler-Drain-Crons, die GET /api/v1/qub/:tx_id/proof (Inklusions-) und GET /api/v1/log/consistency (RFC 9162) Prüf-Endpunkte, der typisierte Inklusionsnachweis im .qub-Bündel (§17.5), der native ANS-104 Anchor-Verifizierer (tools/qub-verify) und der Dual-Self-Published-Heads-Hook (§16.6). Ein erfolgreicher /upload ist immer R2-dauerhaft, aber nur protokollgedeckt, wenn LOG_DO konfiguriert ist und das Inline-Append erfolgreich ist; erst dann liefert seine Antwort log_seq, receipt und anchor_status. Ist RECEIPT_SK abwesend oder ungültig, ist das sig_b64url dieser Quittung leer und bietet keine Nichtabstreitbarkeit. Die aktuellen /seal- und Pakt-Veröffentlichungspfade planen einzelne Arweave-Transaktionen, fügen jedoch kein Protokollblatt hinzu. Derzeit führt kein Code die im /upload-Kommentar vorgeschlagene spätere Abgleichung nach einem Append-Fehler durch. Die W5-Externe Überprüfung ist abgeschlossen: §16.15 zeichnet Designentscheidungen und Startbeschränkungen auf, aber diese Beschränkungen erweitern nicht die zuvor angegebene Produzentenabdeckung. Drei Vertrauens-/Bereitstellungspunkte bleiben gesperrt: (a) die dedizierte Anchor-Wallet (ANCHOR_JWK; LogProfile.anchor_owner ist noch der [0xAB; 32]-Platzhalter); (b) der Quittungs-Signaturschlüssel und der passende Public-Key-Pin (RECEIPT_SK ist optional und LogProfile.receipt_pubkey derzeit leer); und (c) das Self-Published-Heads GitHub-Repository + Token (§16.6). Bis die Anchor-/Profil-Pins bereitgestellt sind, meldet ein eigenständiger Verifizierer den Nachweisstatus ehrlich, anstatt eine vollständig verankerte, gepinnte Überprüfung zu behaupten. Das Design ist streng additiv, und es gibt keine Änderung am SealedQub / QubEnvelope-Drahtformat.

16.1 Begründung und Haltbarkeitsebenen

Die aktuellen Veröffentlichungspfade entkoppeln die Bestätigung von der Arweave-Bestätigung: Sie leiten eine individuelle Transaktion ab und signieren sie, speichern das Artefakt und den genauen Einreichungsstatus in R2 und veröffentlichen es dann asynchron. Das Transparenzprotokoll fügt eine unabhängig verankerte Ordnungsebene für die Teilmenge allgemeiner /upload-Anfragen hinzu, deren LogDO-Append erfolgreich ist:

Tier Name Garantie Wann
T1 R2-erste synchrone Bestätigung Dauerhaftigkeitsebene — versiegelte Bytes und exakter Veröffentlichungsstatus werden in dauerhaften Speicher geschrieben, bevor der Erfolg zurückgegeben wird. Implementiert über aktuelle Veröffentlichungswege.
T2 Gehäufte Einbindung in Transparenzprotokoll Nur anfügbares, manipulationssicheres Commitment + Gesamtordnung, sobald inkludiert und verankert. Aktueller Produzent: erfolgreiche LogDO Anhänge von /upload; die Antwort trägt das Empfangs-Tupel. Nicht universell.
T3 Arweave-Permanenz pro Qub Eine einzelne Arweave-Transaktion für den Qub. Derzeit für jede akzeptierte Veröffentlichung vorbereitet und asynchron gepostet; die exakte signierte Transaktion verbleibt im drainierbaren Postausgang, bis sie geliefert wird.

Die Ebenen beschreiben unterschiedliche Beweis- und Dauerhaftigkeitseigenschaften, nicht den aktuellen kommerziellen Plan. Der aktuelle Code plant weiterhin eine einzelne Arweave-Transaktion für jede akzeptierte Veröffentlichung; T3 wird nicht nur als kostenpflichtiges Upsell angeboten. API-Schlüssel-/Kontolimits bleiben separate Anwendungskontrollen.

Dauerhaftigkeitshonestät. Der T1-Schreibvorgang ist synchron, daher stellt eine erfolgreiche Antwort die Anwendungs-Ebene der Dauerhaftigkeit her, ohne auf ein Arweave-Gateway zu warten. Sie stellt nicht selbst einen unabhängigen Zeitstempel bereit. Eine bestätigte einzelne Transaktion liefert ihre obere Blockzeit-Grenze. Für eine Antwort, die das vollständige T2-Empfangs-Tupel enthält, kann die nächste bestätigte Verankerung den unten beschriebenen Protokollnachweis liefern. Wenn das Tupel fehlt, kann keine Oberfläche implizieren, dass dieser Qub bereits im Transparenzprotokoll vorhanden ist. Verankerungs- und Veröffentlichungsverzögerungen haben keine zahlenmäßige SLA auf Protokollebene.

16.2 LogLeaf-Struktur (zwei festgeschriebene Formen)

Ein Logeintrag ist ein LogLeaf, kodiert als handschriftlich kanonisches CBOR unter dem §3.1-Profil (definierte Länge, keine Tags, keine Fließkommazahlen, Ganzzahlen in kürzester Form, NFC-Text, optionale Felder werden weggelassen, wenn sie fehlen, Schlüssel nach aufsteigender Anzahl kodierter Bytes und dann byteweise geordnet). Der §3.1 parse → re-encode → compare kanonische Schutz wird auf dem Kodierungsweg vor dem Hashing angewendet (nicht nur beim Dekodieren), sodass zwei Implementierungen sich bei den Blatt-Bytes nicht über eine unterschiedliche Ganzzahlbreite oder Schlüsselreihenfolge unterscheiden können. Alle Ganzzahlen sind u8 / u64 / i64; alle Prüfsummen sind 32-Byte-Byte-Strings (bstr[32]). Eine gespeicherte Arweave-Transaktions-ID ist ein rohes 32-Byte-SHA-256-Digest, dargestellt als bstr[32], niemals ein Base64url-Textstring (entspricht §3.3).

Das Blatt hat zwei Formen, die durch ein kind-Byte ausgewählt werden, weil der allgemeine Upload-Pfad byte-blind ist: POST /api/v1/upload behandelt absichtlich beide akzeptierten Payload-Formen als undurchsichtig und empfängt qub_id und unlock_at nur als nicht vertrauenswürdige Client-Aussagen. Auf dem Standard-Privatpfad werden body_hash, drand_round, created_at und drand_chain_version zusätzlich im §13-äußeren Wrapper verborgen, dessen Schlüssel der Worker niemals besitzt. Das Typsystem definiert auch eine bestätigte Form für einen Produzenten, der body_hash / drand_round selbst ableitet. Der aktuelle /seal-Pfad enthält diese Werte, ruft jedoch LogDO nicht auf, sodass die Produktion derzeit nur behauptete (0x02) Blätter aus erfolgreichen General-Upload-Anfügungen ausgibt. Die Trennung hält jeden festgeschriebenen Wert ehrlich, ohne vorzutäuschen, dass der bestätigte Produzent fest verdrahtet ist:

Schlüssel Enc. len Typ Präsenz Bedeutung
seq 4 u64 globalen 0-basierten Blattindex erforderlich; die Position, an die sich der Inklusionsbeweis verpflichtet.
kind 5 u8 erforderlich, 0x01 attestiert-fähig (definiert, derzeit nicht ausgesendet) oder 0x02 asserted (Client-Siegel / byte-blinder Upload).
ref 4 bstr[32] Leaf-Referenz-ID erforderlich. Bestätigt → Roh-qub_id. Behauptet → der geblendet ID SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 6 bstr[32] erforderlich Content-Adresse SHA3-256(stored_bytes) – die eine Content-Verbindung, die der Worker auf beiden Wegen immer ehrlich berechnen kann.
unlock_at 10 i64 erforderlich, kopiert (attestiert) oder behauptet (behauptet); validierte > 0, bevor es in das Blatt eintritt.
received_at 12 i64 Arbeiter-Wanduhr auf R2-Ack erforderlich. Nicht beweiskräftig (Operator-Asserted; §16.6). Anwesend zur Selbstbeschreibung, niemals als Beweis. Validierte > 0.
body_hash 10 bstr[32] kind=0x01 nur auf 0x02 ausgelassen – der Arbeiter fehlt sie gemäß §13.
drand_round 12 u64 kind=0x01 nur auf 0x02 weggelassen.

Ein kind=0x02 Blatt setzt absichtlich weder body_hash noch drand_round aus: Es bestätigt die Verpflichtung und Ordnung eines undurchsichtigen Chiffretextes an Inhaltsadresse chash und beansprucht qub_id und unlock_at – nicht seinen Klartext oder Rundtext. Die Klartext-/Rundebenen für einen behaupteten Qub stammen aus der bestehenden §11 .qub-Bundle-Verifikation, nicht aus dem Log (§16.11). drand_chain_version befindet sich nicht im Blatt (es befindet sich im Wrapper auf dem Standardpfad); die Kettengranularität lebt auf dem Anker (§16.7). Encoder-Disziplin: Lehne einen All-Zero-ref oder chash ab und lehne nicht-positive unlock_at / received_at ab, was dem outcome_at > 0 Sentinel Guard in cbor.rs entspricht.

16.2.1 Private-qub-Blendung

Das Log darf nicht zum Enumerationsorakel werden, das der §13-Außenhüller verhindert (§13.1). Für einen privaten (gewickelten) Qub committet das asserted Blatt die verblindet-Identifikatorin SHA3-256(qub_id ‖ log_blind_secret), wobei log_blind_secret ein servergehaltenes Geheimnis ist, und lässt body_hash weg. Eine dritte Partei kann ein solches Blatt nicht mit einem bestimmten qub_id verknüpfen; der Inhaber des Qub, der die Liefer-URL und damit qub_id hat, kann das Blind neu berechnen, um seine eigene Aufnahme zu bestätigen. Ein öffentlicher Qub (bereits aufzählbar, trägt bereits das Visibility: public Arweave-Tag gemäß §13.8) die Roh-qub_id. Dies ist der einzige Ort, an dem die eigenständige Überprüfbarkeit absichtlich einer lasttragenden Privatsphäre-Invariante weicht; die eigenständige Verbindung für private Quellen ist chash (§16.9).

log_blind_secret Obhut (gelöst — §16.15 Q4). Die Blinde schützt Blatt-Entverknüpfbarkeit, nicht die Klartextvertraulichkeit (der §13-Wrapper hält das unabhängig). Bei einem log_blind_secret Kompromiss berechnen für jedes qub_id, das der Angreifer bereits hält oder rekonstruieren kann (jedes Qub, dessen Bündel/URL er hat, plus etwaige Low-Entropie- oder öffentliche qub_id), das Blatt ref in einem Hash neu und verknüpft es – das ist eine direkte Verknüpfung einer bekannten Population, kein Brute-Force über einen unbekannten Raum. Klassifiziere log_blind_secret als Korrelations-/Sybil-Geheimnis im selben Cutody-Tier wie andere Servergeheimnisse und rotiere nur vorwärts (eine Rotation blendet zukünftige Blätter wieder; sie kann bereits verankerte Blätter nicht rückwirkend entkoppeln).

16.3 Blatt- und Knoten-Hashing

RFC 6962 §2.1 domänengetrenntes Hashing mit SHA-256 ersetzt durch 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änenpräfix-Bytes 0x02 (Eintragskette, §16.4) und 0x03 (STH-Hash, §16.6) sind reserviert und von diesen getrennt. Sie sind einzelne Bytes und können daher nicht mit den bestehenden 10-Byte-ASCII-Domänenseparatoren (QUB_ID_V2 usw.) kollidieren. Der Baum ist der RFC 6962 links-voll-unausgeglichen Baum (jeder Innenbereich ist bei der größten Zwei-Potenz, strikt kleiner als die Blattanzahl des Unterbaums), der es erlaubt, dass Inklusions- und Konsistenzbeweise einen Audit-Pfad-Algorithmus teilen. Die Referenzspecifikation führt expliziten Links/Rechts-Ableitungspseudocode und pinnt einen nicht-zwei-potenz-(5-Blatt)-Testvektor, sodass der Fall der rechten Kanten-Beförderung – den ein 4-Blatt-Vektor verbirgt – angewendet wird.

16.4 Hash-Chaining (intern)

Das LogDO pflegt eine interne Eintragskette nur zur Absturzkonsistenz. Es ist nie veröffentlicht und niemals auf Verifizierer ausgerichtet:

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

Die veröffentlichte, nur anhängige Autorität ist die kumulative Merkle-Wurzel + ihr Anker (§16.5–16.6), niemals die rohe Reihenfolge, in der der Operator zufällig Blätter bedient: Die Kette berechnet für jede bediente Ordnung neu, sodass nur die verankerten Wurzelstifte die kanonische Position einnimmt.

16.5 Kumulativer Merkle-Baum und Batching

Es gibt einen ständig wachsenden RFC 6962-Baum über alle Blätter in seq Reihenfolge – keine isolierten Pro-Batch-Bäume. (Eine Carry-Leaf-Chained per-Batch-Konstruktion wurde abgelehnt: Es handelt sich nicht um eine echte Präfix-Relation, daher sind ihre "Konsistenzbeweise" unzuverlässig.) Der kumulative Baum liefert echte RFC 9162-Konsistenzbeweise und lässt einen einzelnen aktuellen Anker die Inklusion für jeden älteren Qub beweisen.

Das LogDO Durable Object ist der Single Writer (blockConcurrencyWhile, spiegelnde QuotaDO / EntitlementDO) — das Anhängen an ein gemeinsames Protokoll ist lesen-modifizieren-schreiben im geteilten Zustand und MUSS daher durch einen DO, nicht durch KV gehen. Es ccacht die rechte Randgrenze des Baums (O(log n) Hashes), sodass das Schließen eines Batches O(batch) ist. Ein Batch ist die Menge von Blättern, die miteinander verankert sind; seine implementierten Trigger sind ein tree_size Fortschritt von mindestens LOG_BATCH_MAX_LEAVES (Standard 4096), ein Alter bis zur Ankerkadenz oder ein explizite administrative/cron-Force-Close. root_i ist der kumulative Merkle-Baum-Hash über Blätter 0 .. tree_size_i.

16.6 Beschilderter Baumkopf über Arweave Anchor

Die Arweave-Anker-Transaktion ist der signierte Baumkopf und ersetzt eine Operatorsignatur für den Baumkopf selbst: Der tägliche Anker benötigt keinen Qub-Schlüssel, weil der Arweave-Transaktions-owner die Signatur ist. Die Moat-These gilt – das unveränderliche Substrat, kein qub-gehaltenes Geheimnis, ist belastend für die verankerte Wurzel.

Das Log-Design verlangt einen warmen erfolgreichen Receipt-Key (§16.10), der in LogProfile gepinnt und von anchor_owner kreuzsigniert wird. Die aktuelle Implementierung hat diese Trust-Root-Bereitstellung noch nicht abgeschlossen: RECEIPT_SK optional ist, liefert ein abwesender/ungültiger Schlüssel sig_b64url: "", und der kompilierte LogProfile.receipt_pubkey ist leer. Ein solcher Empfang kann das angehängte Blatt beschreiben, ist aber nicht ein nicht widerlegbarer signierter Receipt. Die stärkere Design-Behauptung gilt nur, nachdem ein Verifier-Release den entsprechenden öffentlichen Schlüssel gepinnt und der Ankerbesitzer ihn kreuzsigniert. Eine Publikationsantwort ohne das vollständige Receipt-Tuple macht keine Log-Akzeptanz-Behauptung; eine mit leerer Signatur stellt einen Anhäng-Position-Anspruch, aber keine Signatur-Verifizierungsbehauptung.

Der SignedTreeHead ist kanonischer CBOR (Schlüssel nach codierter Länge): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (vor sth_hash; Genesis = 32 null Bytes), log_id:bstr[32], first_seq:u64, anchored_at:i64. Sein Hash ist sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

Angeheftete Vertrauenswurzel. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Ein konformer Prüfer MUSS anchor_tx.owner == LogProfile.anchor_owner verlangen, wobei anchor_owner (und der öffentliche Schlüssel des Empfangs) in qub_core als LogProfile eingebettet ist — zusammen mit den Quicknet-Konstanten, die bereits in DrandTimelockProvider::quicknet() enthalten sind — und mit der Prüfer-Binärdatei verteilt wird. Der Prüfer MUSS außerdem die Arweave-Transaktion Daten → tx_id Bindung lokal verifizieren, anstatt einer Gateway /raw/-Antwort zu vertrauen. Dies schließt das Problem der Doppelbuchung bei betrügerischen Wallets: „auf Arweave verankert“ ist bedeutungslos, bis der Prüfer festlegt, welche Wallet.

Rotation ist eine §15-Governance-Erweiterung, keine Wiederverwendung (gelöst — §16.15 Q3). Die Profiloberfläche von §15.2 führt derzeit nur sig_algs / drand-Chains / Wrapper-Versionen / Inhaltstypen auf, und die Triggerliste von §15.3 enthält keine dieser Punkte — LogProfile / anchor_owner ist noch nicht in der Oberfläche von §15 enthalten. Die Governance für Rotation muss daher entwickelt werden: §15.3 wird (siehe unten) erweitert, um den LogProfile-Trigger hinzuzufügen, und eine Rotation ist ein signierter LogProfile-Bump, der in einem Prüfer-Update ausgeliefert wird. Eine geplante Rotation trägt eine ausgehende → eingehende Kreuzsignatur; eine kompromissbedingte Rotation kann dies nicht (der ausgehende Schlüssel ist genau dann nicht vertrauenswürdig/nicht verfügbar) und fällt auf den §15-geregelten Bump zurück, wobei die Prüfung des vorherigen Ankerzweigs (siehe unten) den Schaden vorübergehend begrenzt.

Fenster der Doppelbuchung (erstklassiger Vertrauensparameter). Ein Blatt ist nur dann gegen Doppelbuchung resistent, wenn sein überdeckender Anker Arweave-bestätigt ist. Das Fenster ist received_at → anchor confirmation (Rhythmus + Arweave-Endgültigkeit, ohne Protokoll-Latenzgarantie). Vor der Bereitstellung der Vertrauenswurzel liefert die aktuelle Implementierung die operative Integrität von qub plus alle vorhandenen unsignierten Anhang-Metadaten; sie liefert nicht die geplante Non-Repudiation-Garantie. Drei Rechenschaftsartefakte definieren das abgeschlossene Design (das Zeugenmodell ist die Lösung aus §16.15 Q2):

  1. Seal receipt (provisioning-abhängig) — das SCT-Analogon wird zurückgegeben, wenn ein Upload-Log-Anhäng erfolgreich ist (§16.10). Es wird nur dann nicht widerlegbar, wenn sig_b64url nicht leer ist und die passende Public-Key/Anchor-Owner-Beziehung im Verifizor gepinnt ist. Der derzeit leere Produktionsprofil-Pin kann dieses Urteil nicht unterstützen. Diese Kontrolle gilt nicht für ein ausgelassenes Receipt-Tuple oder einen unsignierten Receipt.
  2. Veröffentlichte Monitor-Methodik + vorheriger Ketten-Walk — der Anker prev Kette ist Kopf→genese; eine Gabel (zwei Anker an einem size mit unterschiedlichen root oder einem gebrochenen prev) ist ein veröffentlichter Beweis für Fehlverhalten. Äquivokationserkennung ist eine erklärte operative Verpflichtung, keine stille Annahme.
  3. Zwei selbstveröffentlichte Köpfe — jeder neue Kopf {sth_hash, tree_size} wird in einem dedizierten, qub-eigenen öffentlichen, nur anhängbaren GitHub-Repository (dem tragenden, manipulationssicheren Selbstveröffentlichungsabschnitt) gepostet, wobei ein sozialer Beitrag nur als Best-Effort-Bestätigung dient. Eine fehlgeschlagene Veröffentlichung MUSS-Seite (nicht fehlerstill). Implementiert (Stufe 8) als publishHead Hook am Anker-Cron (workers/api/src/utils/heads-publish.ts): ein PUT zur Inhalts-API ohne sha ist nur anhängend (ein 422 bedeutet, dass der Kopf bereits veröffentlicht ist, niemals eine Überschreibung); Opt-in / deploy-gated auf PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO} und inert, bis das Repository bereitgestellt ist. Ein harter GitHub-Ausfall überschreibt über den health_alert-Kanal und stößt eine dauerhafte Fehlermetrik (m:tlog_publish_head_fail); der Arweave-Anker selbst rollt bei einem Veröffentlichungsfehler nie zurück. "Not fail silent" wird durch diese dauerhafte Metrik garantiert – auf der Ops MUSS Dashboard-Alert geschaltet werden –, selbst wenn die Best-Effort-E-Mail-Seite nicht geliefert werden kann. Zwei ehrliche Einschränkungen folgen von "anchor-on-advance" (der Cron veröffentlicht nur, wenn die Größe wächst): Ein vorübergehender GitHub-Fehler hinterlässt eine Lücke in der Publiced-Heads-Sequenz für diese Größe – begrenzt, nicht stumm (it paget), und da jeder Head einen Superset-Baum commit, überbrückt ein §16.9-Konsistenzproof die Lücke; entscheidend ist, dass dieser Konsistenznachweis aus dem autoritativen Arweave-verankerten Baum berechnet wird, nicht von der GitHub-Oberfläche, sodass eine GitHub-Lücke niemals die Verifizierbarkeit schwächt. Eine Aufhollücke, die Lücken bei veröffentlichten Köpfen füllt, ist eine aufgeschobene Verbesserung.

Ehrlichkeitsbindung (bindende Einschränkung). Da qub beide geplanten Posting-Oberflächen kontrolliert, ist dies selbst veröffentlicht, nicht unabhängig bezeugt. Keine Produkt-, Marketing- oder Rechtsoberfläche darf behaupten, dass das Protokoll „unabhängig bezeugt“ ist. Nachdem die Empfangs-/Profil-/Kopf-Gates bereitgestellt sind, ist die zulässige Aussage, dass Mehrdeutigkeit erkennbar ist und eine erfolgreich signierte Anlage eine unwiderlegbare Empfangsbestätigung hinterlässt. Davor ist diese Aussage nicht verfügbar. Ein echter unabhängiger Drittzeugen wird auf zukünftige §15 Governance-Erweiterung verschoben.

received_at wird vom Betreiber angegeben und keine Aussage darf sich darauf stützen — es wird niemals als Beweis oder zur Streitunterstützung auf irgendeiner Produkt-/Rechts-/API-/Beweisoberfläche angezeigt. Die Arweave-Ankerblockzeit T ist der einzige vertrauensfreie Zeitstempel (eine obere Grenze für „protokolliert von“). Jede Monitor-Konsistenzprüfung zu received_at MUSS mit T verglichen werden, nicht mit dem vom Betreiber kontrollierten anchored_at STH-Feld; eine solche Prüfung schützt nur vor einem ehrlichen Fehler der Betreiberuhr, nicht als Kontrollmechanismus gegen einen böswilligen Betreiber (§16.15 Q5).

16.7 Anker-Transaktionsformat und -Rhythmus

Die AnchorBundle ist der kanonische CBOR-Arweave-Transaktionskörper, geschrieben über den §16.8 Bundler: ver:u8, sth:bstr (kanonische SignedTreeHead-Bytes), prev_anchor:bstr (vorherige Anker-Tx-Id Rohbytes; beim Genesis weggelassen), chain_hash:tstr (die aktuell geltende drand-Kette — quicknet), und der Leaf-CBOR-Stream des Batches in seq-Reihenfolge, sodass der Anker autark ist: Ein Monitor leitet root vom Körper ohne jegliche qub-Abhängigkeit ab. (Wenn der Leaf-Stream bei hohem Volumen groß wird, könnte eine künftige Revision nur einen Leaf-Bereich per Referenz festschreiben; vermerkt, nicht in v1 übernommen.)

Arweave-Tags sind absichtlich durchsuchbar — das Protokoll soll gefunden werden, im Gegensatz zu privaten 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 sind unzuverlässige Hinweise; der CBOR-Körper ist die einzige Autorität.

Cadence: täglich standardmäßig, mit dem Volumen erneut überprüft (der Size-Trigger verkürzt die effektive Kadenz unter Last automatisch). Der aktuelle Hersteller implementiert keinen bezahlten Seal-Force-Anchor-Hook. Die Anchor Wallet ist dediziert und mit niedriger Geschwindigkeit, getrennt vom Upload-Wallet – sie MUSS sein eigene JWK sein (ein eigener Schlüssel, keine logische Rolle im Upload-Wallet), sodass ein Upload-Wallet-Kompromiss keine Anker schmieden kann – mit einem festen täglichen Anker-Transaktionsbudget. Die Verwahrungsposition wird klar formuliert: ein enge Hotkey mit enger Sicherung und niedrigem Saldo, nicht "kalt" – eine Wallet, die täglich automatisch signiert, darf nicht kalt sein, und die Spezifikation behauptet nichts anderes.

16.8 ANS-104 Bundler

Ein hausinterner ANS-104 DataItem-Encoder und Deep-Hash-Signer, etwa 300 Zeilen, nur Web-Krypto, null npm-Abhängigkeiten (beide Turbo-SDKs versagen am npm ci --ignore-scripts Supply-Chain-Gate). 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

Das Signieren erfolgt als Arweave-deepHash – ein rekursiver SHA-384-Digest (Arweaves Wire-Anforderung, crypto.subtle.digest("SHA-384")) über ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] – dann RSA-PSS über den tiefen Hash mit der Wallet-JWK über crypto.subtle; id = base64url(SHA-256(signature)). Das SHA-384 hier wird als Arweave-Wire-only Primitive unter Quarantäne gesetzt, niemals als qub-Trust-Primitiv (§15 zeichnet den Zaun auf; qub-Trust-Hashing ist durchgehend SHA3-256).

Der ANS-104-Codepfad bedient die deferred fallback/drain-Maschinerie und schreibt AnchorBundle DataItems. Der gewöhnliche Veröffentlichungspfad erzeugt zunächst eine exakt signierte Arweave-Transaktion und hält sein JSON in einem robusten Outbox; Direct Posting ist eine Latenzoptimierung, und der Drain-Pfad versucht dieselbe Transaktion erneut, bevor er seinen Bundler-Fallback anwendet. Signaturschema (gelöst — §16.15 Q8): v1 signiert mit RSA-PSS (Signaturtyp 1) verwendet den bestehenden Arweave-Wallet-JWK-Mechanismus (null neue, langlebige Schlüsselverwahrung, bedient die "ein weniger geheimer" These); Ed25519 wird auf den §15 PQ-Migrationspfad versetzt.

Der handgerollte tiefe Hash ist der Code mit dem höchsten Risiko und der geringsten natürlichen Abdeckung in W5, daher ist sein Gating nicht verhandelbar (§16.15 Q8):

  1. Die plattformübergreifende Einrichtung tlog_v1.json (Rust + TS, das §14.5 wrapper_v1.json Muster) umfasst Deep-Hash, DataItem-Bytes + ID, Blatt-Hashes, eine 5-Blatt-Wurzel + Audit-Pfad, einen STH-Hash, einen Einschlussnachweis und einen Konsistenznachweis — in beide Richtungen: signieren und verifizieren (die Verifizierungsrichtung ist wichtig, weil §16.6's lokale tx → tx_id-Prüfung den Deep-Hash in jeden eigenständigen Verifizierer zieht, nicht nur in den Writer).
  2. Eine einmalige Interop-Rundreise durch einen Referenz-ANS-104-Bundler, genutzt als statische Testdaten nur — niemals als npm-Laufzeitabhängigkeit (die Web-Crypto-only / keine-Installationsskripte-Haltung bleibt bestehen).
  3. Der Deep-Hash + RSA-PSS-Pfad muss die gleichen crypto.subtle-Primitiven durchlaufen, die auch in der Produktion verwendet werden, damit der interne Encoder byte-kompatibel ist.
  4. Ein fortlaufender Post-Bundle-Akzeptanzmonitor bestätigt, dass jedes Anchor / Fallback-DataItem tatsächlich Arweave-Akzeptanz erreicht, mit Alarm + Not-Aus-Schalter — da der Deep-Hash auch die Arweave-Unerreichbarkeits-Fallback-Warteschlange bedient, würde eine stille Regression diese Warteschlange während des genauen Ausfalls, den sie abdecken soll, mit vom Netzwerk abgelehnten Items füllen.

16.9 Einschluss- und Konsistenznachweise

Beide sind RFC 9162, SHA3-256, als kanonisches CBOR bereitgestellt.

InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (das exakte Blatt-CBOR — der Verifizierer berechnet leaf_hash selbst und vertraut niemals einem gelieferten 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. Eine einzige eindeutige Schlüssel-Liste, festgelegt durch Testvektor.

Eigenständige Prüfung (kein qub-Server, erweitert §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).

Proof-Serving-Speicher MUSS koordinatenschlüsselgebunden sein (gelöst — §16.15 Q7, Blockierungsvoraussetzung). Kalt-Leaf-Beweisgenerierung ist korrektheitsneutral nur, wenn das R2-Auditmaterial ein persistenter Merkle-Knotenspeicher ist, der durch absolute Baumkoordinaten-(level, index) geschlüsselt ist – nicht per batch Knotendeltas. Bei einem koordinatenschlüsselgebundenen Speicher ist jeder (leaf i, size N) Auditpfad eine Menge von O(log N) direkten R2 GETs mit keiner Recomputation über Batchgrenzen hinweg; bei einem batch-schlüsselgebundenen Speicher ist dies nicht, was die Speicherlayout-Lücke ist, die diese Auflösung schließt. Die Blattkörper sind ebenfalls durch seq inhaltsadressierbar. Ein W5-Testvektor MUSS ein Genesis-Ära kaltes Blatt gegen eine viel spätere Wurzel beweisen, indem nur R2 + Arweave mit gelöschtem LogDO-Speicher verwendet wird, sodass die Rückgewinnungssicherheitsbehauptung in §16.13 gestützt statt behauptet wird. Die O(log N) sequentiellen R2 GETs gehören **nur auf den asynchronen Beweisendpunkt – niemals auf den Seal Hot Path (§16.10) oder einen pro-tick Cron.

16.10 R2 – Erste Ack-Bestellung

Die implementierte POST /api/v1/upload Folge lautet:

  1. Vordere Hälft-Gates (Authentifizierung, Validierung, Idempotenz-Shard-Schlüssel) — unverändert.
  2. Erstellen Sie, taggen und signieren Sie die exakt einzelne Arweave-Transaktion. Dies leitet tx_id lokal ab, obwohl die Transaktionserstellung Belohnungs-/Anker-Metadaten von einem Gateway abrufen kann. Ein Vorbereitungsfehler führt dennoch dazu, dass die Anfrage vor der Bestätigung fehlschlägt.
  3. Synchron das ausgewählte Artefakt bei qub-cache/<tx_id> schreiben und die stabilen Erstellungs-Operationen/Outbox-Datensätze beibehalten. Dies sind die Haltbarkeits- und Wiederversuchsebene; Fehler vor der Abwicklung geben 503 zurück.
  4. Wenn LOG_DO konfiguriert ist, versucht synchron LogDO.append(leaf). Der einzelne Schreiber weist seq zu, erweitert die Eintragskette und aktualisiert die Grenze. Der anhängende RPC tut nur das; Batch-Schließen läuft außerhalb des Pfads auf dem Alarm. Ein Fehler bei einem Anhäng-Transport/einer Anwendung ist derzeit fail-soft: Die Antwort kann auch ohne log_seq, receipt oder anchor_status erfolgreich sein. Trotz eines Implementierungskommentars wird heute keine automatische spätere Log-Abstimmung verdrahtet.
  5. Geben Sie die Bestätigung zurück. Fügen Sie { log_seq, anchor_status: "pending", receipt } nur ein, wenn das Anhäng das vollständige erfolgreiche Tupel zurückgegeben hat. receipt.sig_b64url ist leer, wenn der Quittungsunterzeichner nicht verfügbar ist; Clients dürfen diesen Wert NICHT als signed oder nicht widerlegbar bezeichnen. Das Fehlen des Tupels bedeutet nur eine dauerhafte Veröffentlichung, keine Akzeptanz durch Transparenzprotokolle.
  6. Verwenden Sie eine aufgeschobene Aufgabe, um die exakt signierte Transaktion zu posten. Erfolg entfernt den Auskasten; Fehlschlag überlässt ihn für den begrenzten Drain Cron und darf die bereits bestätigte tx_id nicht ändern. Provisorische Metadaten und andere Best-Effort-Sidecars werden ebenfalls aufgeschoben.

Latenzgrenze. Der Anforderungspfad umfasst Front-End-Autoritäts-/Kontingentarbeiten, Transaktionsvorbereitung/-signierung, dauerhafte R2-Schreibvorgänge und (wenn konfiguriert) den LogDO-Versuch. < 300 ms erscheint im Design-Review als operatives Ziel, nicht als Protokollgarantie; der aktuelle Schritt der Transaktionsvorbereitung kann eine Gateway-Metadatenabfrage durchführen. Latenzalarmierungen und Startkontrollen sind operative Maßnahmen, keine Nachweise, die einem Prüfer zur Verfügung stehen.

16.11 Vertrauensmodell — der genaue Anspruch, nach Blatttyp abgegrenzt

Für kind=0x01 (beglaubigt): „Dieser Inhalt — Körper entspricht body_hash, identifiziert durch qub_id — wurde im append-only Log von qub an Position seq festgeschrieben und existierte spätestens bis Arweave Blockzeit T; er war bis drand Runde R = unlock_round(unlock_at) kryptographisch unlesbar.“ Dies ist das vollständige {tlock round binding + Merkle inclusion + anchored root}-Triple.

Für kind=0x02 (behauptet, Standard): „Ein opak verschlüsselter Text mit Inhalt-Adresse chash, beansprucht qub_id und unlock_at, wurde im append-only Log an Position seq festgeschrieben und existierte spätestens bis Arweave Blockzeit T.“ Die Runde- und Körper-Angaben werden durch die bestehende §11 .qub-Bundle-Verifizierung (qub_core::unlock) bereitgestellt, nicht durch das Log; was das Log über eine einzelne qub-Transaktion hinaus hinzufügt, ist manipulationssichere Reihenfolge, ein vertrauensloses zeitliches Obergrenzencommitment und Widerstand gegen Widersprüche.

Beide Ansprüche schließen gemäß §11 aus: Autorschaft ohne sig_alg ≥ 0x01, Absicht und Sub-Anker-Granularitätszeitpunkt. Keiner lässt einen Anspruch auf received_at stützen.

Anspruchsobergrenze (verbindliche Startbeschränkung — gelöst §16.15 Q1). Für ein behauptetes (kind=0x02) Blatt ist der oben abgegrenzte Anspruch die Obergrenze dessen, was irgendein Produkt, Marketing, die Bedingungen oder Proof-Rendering-Oberflächen behaupten dürfen. Keine Oberfläche darf aussagen oder implizieren, dass das Log den Inhalt oder die Freigaberunde eines byte-blinden Uploads beweist — das Log beweist Reihenfolge + ein vertrauensloses oberes Zeitlimit für ein opakes Chiffretext-Commitment. Inhalts- und Rundennachweise stammen ausschließlich aus der bestehenden §11 .qub-Bundle-Verifizierung, die log-unabhängig ist. Eine Veröffentlichung ohne erfolgreichen Append/Empfang hat überhaupt keinen Log-Anspruch.

16.12 Versionierung und W3-Koordination

Es gibt kein SealedQub-Draht-Bump und daher kein Protokollversions-Bump (§12.2): das Log ist ein Sidecar, das sich auf bestehende Felder und Bytes festlegt, daher geht es nicht in die §12.3-Protokollversionshistorie ein. W3s optionales drand_chain_version bleibt unberührt und bleibt das einzige optionale SealedQub-Feld. Das Log führt stattdessen seine eigenen unabhängigen Versionsräume ein — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — die die §12.5-Unabhängigkeit der Wrapper-Version widerspiegeln (der Wrapper trägt ein Versionsbyte, das unabhängig von der Protokollversion ist, und die Log-Versionen folgen derselben Trennung).

Der Nachweis wird standardmäßig abgerufen, mit einer optionalen Mitnahme. Ein Nachweis kann beim Versiegeln nicht existieren (der Anker wurde noch nicht geschrieben), daher bleibt das Seal-Time .qub-Bündel nachweisfrei. W7s Verifizierer ruft GET …/proof einmal ab oder rekonstruiert im vollständig Offline-Modus den Nachweis aus dem öffentlichen AnchorBundle über eine Arweave-Abfrage auf Log-Id. Das .qub-Bündel (W7) reserviert ein optionales inclusion_proof-Mitglied — beim Versiegeln nicht vorhanden, durch einen Post-Anker-Reexport für die Cold-Archivierung befüllt — gemäß dem gleichen Muster „optional, standardmäßig ausgelassen, additiv“ wie W3s drand_chain_version.

16.13 Aufbewahrung

Aufbewahrungsfenster für das offene Ende des LogDO, das R2-Nachweisbereitstellungs-Substrat, die Anker-Circuit-Breaker-Zähler und die Fallback-Warteschlange des Bundlers sind in docs/DATA-RETENTION.md spezifiziert. Prinzip: Der Hot-Speicher pro Eintrag des Logs (LogDO) kann nach dem Anker zurückgewonnen werden; das Audit-Material — der koordinatenbasierte Schlüssel (level, index) Merkle-Knoten-Speicher + die seq-adressierten Blattkörper (§16.9) + die Arweave-Anker — ist permanent. Das Zurückgewinnen eines kalten Blatts aus dem DO macht einen ausgestellten Nachweis niemals ungültig, weil ein Nachweis gegen den permanenten R2-Knotenspeicher und den Arweave-Anker gelöst wird, nicht gegen das DO (und der §16.9-Wiped-DO-Testvektor beweist dies).

16.14 Testvektoren

W5 liefert das plattformübergreifende Fixture tlog_v1.json (§16.8) sowie bearbeitete Vektoren: ein kind=0x01- und ein kind=0x02-Blatt → leaf_hash; die kumulative Wurzel aus 5 Blättern; ein Einschlussnachweis; ein Konsistenznachweis; ein AnchorBundle; und eine DataItem-ID. Diese existieren neben den §14.5-Outer-Wrapper-Vektoren und werden sowohl von der Rust- (qub-core) als auch von der TypeScript- (Worker) Implementierung verwendet.

16.15 Prüfentscheidungen (W5 — abgeschlossen)

Die externe W5-Überprüfung (ein adversarieller Design-Durchgang + Owner-Freigabe) ist abgeschlossen. Jede unten aufgeführte Entscheidung ist festgelegt und im obigen §16-Text reflektiert; die verbindlichen Startbedingungen werden am Ende erneut aufgeführt. Die Implementierung kann unter ihnen fortgesetzt werden.

  1. Standardpfad (kind=0x02) Blatthonestität — GELÖST. Versenden Sie die Art der Zwei-Blatt-Split wie angegeben: kind=0x02 verpflichtet weder body_hash noch drand_round. Kein *_body_hash-Feld auf dem byte-blinden Pfad (es wäre das am besten lesbare falsche „verifiziert“-Signal für Integratoren und ist ein Komfort, den §11 bereits aus dem Bündel bereitstellt). Fordern Sie nicht den Server-Siegel für log-beglaubigte Qubs an (das würde Klartext durch den Worker erzwingen und den Krypto-Schredder-Graben zerstören). Jede selbstbeschreibende Kurzschlussfunktion gehört in das .qub-Bündel / Beweisumschlag als vom Verifizierer neu berechnetes Feld, niemals in ein Blatt-Feld. Eigentümerbestätigtes Anspruchslimit: §16.11.

  2. Doppeldeutigkeit / Auslassungsverantwortung — ENTWURF GELÖST, BEREITSTELLUNG UNVOLLSTÄNDIG. Das Design erfordert, dass der Siegel-Empfangsschlüssel in LogProfile fixiert und durch anchor_owner gegengezeichnet wird, plus Überwachungsmethodik, Vor-Ketten-Durchgang und doppelte selbstveröffentlichte Heads. Das kompilierte Profil und die Bereitstellungshooks sind noch Platzhalter/optional wie in §16.6 beschrieben, sodass der stärkere erkennbare + quittierte Anspruch erst nach Abschluss dieser Schritte aktuell ist. Es darf niemals als unabhängig bezeugt vermarktet werden. Ein echter Drittzeugen wird auf einen §15-Governance-Update verschoben.

  3. Fixierte Anker-Eigentümer-Vertrauenswurzel + Rotation — GELÖST. Übernehmen Sie die LogProfile-Fixierung (§16.6); der Verifizierer prüft anchor_tx.owner == anchor_owner und verifiziert die TX-Daten → TX_ID-Bindung lokal. Rotations-Governance ist eine §15-Erweiterung zum Aufbau (§15.3 Auslöser hinzugefügt), keine Wiederverwendung; geplante Rotationen gegengezeichnet, kompromissgetriebene Rotationen greifen auf das §15-Update mit Fork-Check zur Schadensbegrenzung zurück.

  4. Private-Qub-Blattblendung — GELÖST. Blenden für private Qubs beibehalten (ref = SHA3-256(qub_id ‖ log_blind_secret)), rohes qub_id für öffentliche Qubs (bereits §16.2.1), chash als selbstständige Bindung. log_blind_secret ist ein Korrelations-/Sybil-Grad-Geheimnis, nur nach vorne rotieren (§16.2.1).

  5. received_at — GELÖST. Im Blatt behalten, verpflichtet, aber ausdrücklich nicht als Beweismittel; niemals als Beweis oder Streitbestätigung auf irgendeiner Oberfläche angezeigt. Jede Monitor-Integritätsprüfung vergleicht gegen die Arweave-Blockzeit T, nicht den Betreiber-kontrollierten anchored_at (§16.6).

  6. Gestufte nachweisbare Zeitplanung — ENTWURFSLÖSUNG, NICHT AKTUELLE ROUTING. Das geprüfte Design weist Ankerblock-Zeitpläne der gebündelten Stufe zu und exakte Stundenbeweise T3-Paid zu, ohne numerisches SLA für Erstere. Die aktuellen Routen haben diese kommerzielle Unterscheidung nicht umgesetzt: Sie planen eine einzelne Transaktion für jede akzeptierte Veröffentlichung, und die Protokollabdeckung bleibt bedingt, wie in §16.1/§16.10 angegeben. Die Produktbeschreibung muss die Implementierung beschreiben, nicht diese zukünftige Stufenaufteilung.

  7. Kumulative Baum auf Arbeitern — GELÖST. Einzelner kumulativer RFC 9162-Baum + frontier-gecachter Single-Writer LogDO (komfortabler Spielraum gegenüber der ~1k Schreibvorgänge/sec DO-Grenze; Merkle-von-Shard-Wurzeln Sharding bis knapp davor aufschieben). Der koordinaten-schlüssellose (level, index) R2 Knoten-Speicher + abgewischter DO Cold-Leaf-Testvektor sind implementiert (§16.9). < 300 ms bleibt ein Design-/Betriebsziel, kein Protokollversprechen (§16.10).

  8. ANS-104 Signaturschema + Deep-Hash — GELÖST. RSA-PSS (Signaturtyp 1, unter Verwendung des dedizierten Anchor-Wallet JWK); Ed25519 auf den §15 PQ-Pfad verschoben. Der handgefertigte SHA-384 Deep-Hash ist abhängig vom Cross-Impl Fixture in beide Richtungen, einem statischen Referenz-Bundler-Interop-Check, dem Shared-crypto.subtle Round-Trip und dem Post-Bundle Arweave-Akzeptanz-Monitor (§16.8).

Bindende Startbedingungen (in Implementierung + Produkt-/Rechtsprüfung übernehmen):


17. Portables Verifizierungsbündel (.qub)

Status. Dieser Abschnitt ist implementiert (W7 / UP-C2): qub_core::export erzeugt und parst das Bündel, und tools/qub-verify ist eine öffentliche, eigenständige CLI, die es offline verifiziert. §11 und §16.9 bezeichnen „das .qub-Bündel" bereits als die Einheit, die ein eigenständiger Verifizierer verarbeitet; dieser Abschnitt spezifiziert seine Bytes und den Verifizierungsablauf. Es ist strikt additiv—das Bündel verpackt bestehende Eingaben aus §11 und ändert kein On-Chain-Wire-Format.

17.1 Zweck

§11 stellt fest, dass jede dritte Partei das kryptografische Artefakt eines qubs ohne Mitwirkung von qub verifizieren kann. Das .qub-Bündel macht diese Verifizierung portabel und offline: Es verpackt das versiegelte CBOR und die drand-Rundensignatur, die es entsperrt, in ein einziges eigenständiges Artefakt, sodass Empfänger:innen Inhaltsintegrität, Rundenbindung und etwaige Urheberschaftssignaturen ganz ohne Netzwerkaufruf verifizieren können (kein Speicherabruf, keine Live-Anfrage an drand, keine qub-API). Ein Bündel allein beweist nicht, wann sein Chiffretext erstellt wurde; eine unabhängig verifizierte Speichertransaktion oder ein verankerter Log-Beweis liefert diese getrennte Existenzaussage (§11, §17.5).

17.2 Bündelformat

Ein QubBundle ist handgeschriebenes kanonisches CBOR unter dem §3.1-Profil (definite Länge, keine Tags, keine Floats, kürzeste Ganzzahlen, NFC-Text, optionale Felder bei Abwesenheit weggelassen, Schlüssel zuerst aufsteigend nach codierter Bytelänge und dann byteweise geordnet). Die drei 15 Zeichen langen Schlüssel stehen in der Reihenfolge d < i < s. Eine rohe .qub-Datei besteht exakt aus diesen Bytes; für URL- oder Copy-and-paste-Transport sind dieselben Bytes base64url-codiert (ohne Padding).

Schlüssel Cod. Länge Typ Vorhandensein Bedeutung
version 8 u8 erforderlich Version des Bündelformats (0x01).
sealed_at 10 i64 optional Von der erstellenden Person behauptete Versiegelungszeit (Unix-Sekunden); selbstbeschreibend, ohne Beweiskraft.
drand_round 12 u64 erforderlich Die Runde, an die der qub gebunden ist. Eine Projektion des eingebetteten versiegelten qubs.
arweave_tx_id 14 tstr erforderlich Die Transaktions-ID, unter der die versiegelten Bytes gespeichert wurden (Herkunftsverweis).
drand_chain_id 15 tstr erforderlich Die drand-Chain (Hex). Eine Projektion des eingebetteten versiegelten qubs.
drand_signature 16 bstr erforderlich Die drand-Beacon-Signatur für drand_round—der Wert, der den Chiffretext entsperrt.
inclusion_proof 16 bstr optional Der Merkle-Aufnahmebeweis des Transparenz-Logs aus §16, sobald das Log ausgeliefert ist (§17.5).
sealed_qub_cbor 16 bstr erforderlich Die inneren SealedQubCbor-Bytes (nach dem Unwrap aus §13), also die Verifizierungseingabe aus §11.

drand_round und drand_chain_id sind Komfortprojektionen von sealed_qub_cbor, die mitgeführt werden, damit Werkzeuge sie lesen können, ohne das innere CBOR zu parsen. Sie werden bei der Konstruktion abgeleitet und beim Decodieren gegen den geparsten versiegelten qub erneut geprüft; ein Bündel, dessen Feld auf oberster Ebene nicht mit seiner Nutzlast übereinstimmt, wird abgelehnt. Die Encoder-Disziplin entspricht dem übrigen Wire-Format: Leere drand_signature oder arweave_tx_id werden abgelehnt, und jedes Feld variabler Länge wird begrenzt.

17.3 Was die eingebettete drand-Signatur beweist

Das Bündel führt die drand-Signatur mit, statt vom Verifizierer ihren Abruf zu verlangen. Die Timelock-Entschlüsselung (tlock über der drand-Chain, §8) kann nur mit der echten Beacon-Signatur für die gebundene Runde gelingen—einem Wert, den die Chain erst nach Ablauf dieser Runde veröffentlicht und der unter dem öffentlichen Schlüssel der Chain eine gültige BLS-Signatur ist. Eine gefälschte oder falsche Signatur scheitert bei der BLS-Verifizierung oder IBE-/AEAD-Entschlüsselung. Ein entschlüsselbares Bündel beweist daher: Der Chiffretext ist an Runde R gebunden, und Runde R ist verstrichen. Der Verifizierer schreibt die Chain fest (DrandTimelockProvider::quicknet()) und wendet die Rundenbindungsprüfung aus §11 an, sodass ein Bündel keine Runde beanspruchen kann, an die sein Chiffretext nicht gebunden ist.

Dies ist ein Beweis der Freigabebedingung, kein Erstellungszeitstempel. Nach Ablauf von Runde R kann jede Person einen neuen Chiffretext für R erstellen und dessen bereits öffentliche Signatur einpacken. Daher DARF das Bündel allein NICHT als Beweis dafür bezeichnet werden, dass der Chiffretext oder Inhalt vor R, vor unlock_at oder vor irgendeinem Ereignis existierte.

17.4 Ablauf der Offline-Verifizierung

qub-verify <file.qub> führt das Standardverfahren aus §11 vollständig aus dem Bündel aus und steuert qub_core::unlock::unlock mit einem festgeschriebenen DrandTimelockProvider:

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

Die CLI beendet sich mit 0 (verifiziert), 1 (Verifizierung fehlgeschlagen—noch gesperrt, Body-Hash-Abweichung, gebrochene Runden-/Chain-Bindung oder eine Signatur, deren Verifizierung fehlschlägt) oder 2 (Verwendung / fehlerhaftes Bündel). Ein --json-Bericht enthält dieselben Urteile zur Automatisierung. Da das Bündel eigenständig ist, sind das Verifizierer-Crate (qub-core) und die CLI (qub-verify) die einzige Software, die eine dritte Partei benötigt; beide sind öffentlich und verwenden den bestehenden Verifizierungspfad des Protokolls wieder—keine maßgeschneiderte Kryptografie.

17.5 Beziehung zum Transparenz-Log

inclusion_proof ist ein optionaler Platz für den Merkle-Aufnahmebeweis aus §16. Die reine Bündelverifizierung (§17.4) ist für Integrität, Rundenbindung / Ablauf der Runde und optionale Urheberschaft vollständig, besitzt jedoch bewusst keine unabhängig mit Zeitstempel versehene Existenzaussage. Ein ausgefüllter, vollständig gegen seinen Anker verifizierter inclusion_proof fügt die blattartspezifische Verpflichtung und zeitliche Obergrenze aus §16.11 hinzu, ohne die Version des Bündelformats zu ändern. Ein abwesender Beweis bedeutet nur „kein Beweis enthalten"—nicht „ungültig" und nicht zwangsläufig „nicht verankert".

In der Referenzimplementierung ist der Platz nun typisiert: qub_core::export::QubBundle::inclusion_proof_typed() gibt ein Option<InclusionProof> zurück, das die vollständige Struktur aus §16.9 (Leaf, Audit-Pfad, verankerte Wurzel und AnchorRef) durch dasselbe undurchsichtige CBOR-Feld trägt—ohne Anhebung der Bündelformatversion. Die eigenständige CLI qub-verify verarbeitet es über ihren --anchor-Zweig und meldet—bis das Anker-Wallet bereitgestellt ist (§16, Status)—einen vorhandenen Beweis mit Platzhalter-Owner als nur aufgenommen statt vollständig gegen den Anker verifiziert.