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:
- Die Änderung eines beliebigen durch das Preimage gebundenen Felds—
version,content_type,created_at,unlock_at,outcome_at,drand_round, der rohenbody-Bytes (überbody_hash) odertitle(übertitle_hash)—erzeugt eine anderequb_id. - Die qub_id wird vor der Verschlüsselung berechnet. Sowohl QubEnvelope als auch SealedQub tragen dieselbe qub_id. Der Empfänger prüft nach der Entschlüsselung, dass sie übereinstimmen.
qub_idhängt nicht vonsender_label,reply_to, Signaturbytes oder öffentlichen Signierschlüsseln ab. In der aktuellen V2-Signaturkonstruktion werdensender_labelundreply_tojedoch immer dann direkt übersender_label_hashundreply_to_or_zero(§9.3) authentifiziert, wenn Signaturen vorhanden sind.- Eine Änderung des SealedQub-
title(bei sonst identischen Werten) ändert diequb_idübertitle_hash. Ein Gateway kann daher den im Countdown angezeigten Klartext-Titel nicht austauschen, ohne die qub-Identität zu invalidieren. - Eine Änderung des SealedQub-
outcome_at(bei sonst identischen Werten) ändert diequb_idüber das Preimage. Ein Gateway kann das im Countdown vor der Enthüllung angezeigte Verdict-Datum nicht austauschen, ohne die qub-Identität zu invalidieren. - Eine Änderung von
drand_round(bei sonst identischen Werten) ändert diequb_idüber das Preimage. Ein Gateway kann den Timelock-Ciphertext nicht an eine andere Runde rebinden, ohne die qub-Identität zu invalidieren; in Kombination mit der Stanza-Runden-Prüfung beim Entsperren (§8) ist das angezeigteunlock_atdie Runde, die die Entschlüsselung tatsächlich freigibt.
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:
- Nur HTTPS. Die Zeichenkette MUSS mit der Byte-Sequenz
https://beginnen. Jedes andere Schema —http,ftp,javascript,data,fileusw. — wird abgelehnt. - Längenbegrenzung. ≤ 2.048 Bytes (praktische Browser-URL-Grenze).
- NFC + Prüfung auf feindliche Codepunkte. Dieselbe Regel wie für
titleundreflection— Bidi-Override-, Zero-Width-, Tag-Block-, BOM-, C0- und C1-Codepunkte werden abgelehnt. Die Definition entspricht dem Rust-crate::handle::contains_hostile_text_codepointund dem TS-workers/api/src/utils/unicode.ts::isHostileCodepoint(im Gleichschritt halten). - Keine Leerzeichen, keine ASCII-Steuerzeichen. Leerzeichen / DEL / Bytes unter
0x20an beliebiger Stelle in der URL werden abgelehnt — schließt den\n/\t-Injektionsvektor, den die Bidi-Regel nicht abdeckt. - 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.
- Unter dem eingestellten V1-Preimage konnte eine Partei mit Schreibzugriff auf die gespeicherten Bytes
sender_label(„Alice" → „Mallory") tauschen oderreply_toumhängen — und nach der Runde neu verschlüsseln — ohne die Autorensignatur zu invalidieren, weil keines der beiden Felder im signierten Preimage enthalten war. V2 deckt beide ab, sodass jede Änderung an einem der beiden Felder die Verifizierung auf „fehlgeschlagen" umschlagen lässt. Da Verifizierer nun ausschließlich V2 akzeptieren, ist dieser Tausch für jede Signatur geschlossen: Eine Signatur, die keines der beiden Felder bindet (d. h. nur gegen V1 verifiziert), wird rundheraus abgelehnt, statt auf sie zurückgestuft zu werden. - Der
author_pubkeyinnerhalb des Envelopes bleibt der wahre Identitätsanker — Empfänger MÜSSEN die Anzeigeidentität ausauthor_pubkey(über die §9.5-Attestierungsschicht) ableiten, anstattsender_labelzu vertrauen.
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:
cosigner_pubkey: ML-DSA-65-Public-Key des Mitunterzeichners (Partei B).cosigner_signature: Signatur über dasselbesig_inputwie der Autor (§9.3).
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:
- Der Mitunterzeichner signiert dasselbe
sig_inputwie der Autor — beide Parteien verpflichten sich auf dieselbequb_id,body_hashundunlock_at(und, unter V2, denselbensender_label_hashundreply_to_or_zero). - Damit der Mitunterzeichner das V2-Preimage ohne Zugriff auf die rohen Envelope-Bytes rekonstruieren kann, erzwingt der Staging-Dienst zum Staging-Zeitpunkt, dass der
sender_labeleines Pakt-Envelopes gleichpact_terms.party_a.labelist und dassreply_toabwesend ist. Beides gilt für jeden Referenzclient-Pakt; verletzende Envelopes werden beim Staging abgelehnt. - Die Ableitung der
qub_id(§4.1) umfasst KEINE Mitunterzeichner-Felder. Das Hinzufügen eines Mitunterzeichners zu einem bestehenden Envelope ändert diequb_idnicht. - Ein Pakt kann nur vom Autor signiert sein (einseitige Verpflichtung), nur vom Mitunterzeichner (ungewöhnlich) oder von beiden (vollständiger bilateraler Beweis).
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
- Überschriften:
#bis####(kein#####oder######) - Hervorhebung: fett (
**), kursiv (*), durchgestrichen (~~) - Listen: geordnet (
1.) und ungeordnet (-,*) - Blockzitate (
>) - Code: Inline-Spans (```) und eingezäunte Blöcke (`````)
- Horizontale Linien (
---) - Zeilenumbrüche (zwei nachfolgende Leerzeichen oder Leerzeile)
- Absätze
10.2 Verbotene Elemente
| Element | Behandlung |
|---|---|
Rohes HTML (<div>, <script>, etc.) |
Vollständig entfernt. Kein HTML wird durchgereicht. |
Bilder () |
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:
- Markdown mit
pulldown-cmark(oder gleichwertig) parsen. - Den AST durchlaufen und jeden Knoten verwerfen, der nicht in der Allowlist (§10.1) enthalten ist.
- Für Link-Knoten: Die URL als sichtbaren Text ausgeben, nicht als anklickbares
<a>-Element. - 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. - Aus der typisierten IR auf die Zielsicht-Ebene rendern (z. B. reaktive View-Komponenten, DOM-Knoten). Keine HTML-Stringverkettung oder
innerHTMLan 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
- Maximale gerenderte Überschriftentiefe:
####(H4).#####und tiefer werden als Fettschrift gerendert. - Keine Begrenzung der Absatzanzahl (die Body-Größenlimits in §6 sind die Beschränkung).
- Eingezäunte Codeblöcke: Keine Syntaxhervorhebung im MVP. Gerendert als monospace-vorformatierter Text.
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>.
- PATCH: Genauigkeits- oder redaktionelle Korrektur, die konforme Bytes oder erforderliches Verhalten nicht ändert.
- MINOR: Rückwärtskompatible normative Ergänzung, neuer Registry-Eintrag oder neues unabhängig versioniertes Sidecar-Format.
- MAJOR: Inkompatible normative Änderung, einschließlich einer neuen erforderlichen Wire-Interpretation.
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.
- Empfänger MÜSSEN unbekannte Hauptversionen mit einem klaren Fehler ablehnen.
- Innerhalb einer bekannten Hauptversion MÜSSEN Decoder unbekannte Map-Schlüssel ablehnen (§3.1) — Schemaevolution erfolgt durch die Einführung einer neuen
version, nicht durch das Hinzufügen von Schlüsseln, die bestehende Decoder überspringen würden. (Frühere Revisionen dieser Spezifikation erlaubten es, unbekannte optionale Felder zu tolerieren; diese Klausel ist zurückgezogen — sie machteencode(decode(x))nicht-injektiv und öffnete einen Vektor für verborgene signierte Inhalte in Pakt-Nutzlasten.) - Inhaltstypen (
content_type) und Signaturverfahren (sig_alg) sind versionsgebunden: Neue Werte dürfen nur zusammen mit einer neuen Protokollversion oder einem expliziten Registry-Update eingeführt werden.
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:
- Enumerationsresistenz bei privater Zustellung.
OuterWrapperbleibt erkennbar strukturiertes CBOR — es ist nicht buchstäblich von Zufallsbytes ununterscheidbar — doch sein Ciphertext-Feld verbirgt die erkennbare innereSealedQub-Form. Die dokumentierte Harvester-Strategie „per GraphQL nach nackten qub-förmigen Uploads fragen und sie mit öffentlichen drand-Signaturen massenhaft entschlüsseln“ endet ohne K nicht in Klartext. - Crypto-Shredding-Datenschutzhaltung für den standardmäßigen privaten Browserfluss. qub.social kann diese gespeicherten Artefakte nicht aus seinen standardmäßigen serverseitigen Daten entschlüsseln. Explizite Wiederherstellung, öffentliche Zustellung und vertrauenswürdige serverseitige Versiegelung haben andere offengelegte Vertrauensgrenzen.
- Zweistufige Vertraulichkeitsleiter. Standard = linkgesteuerter Zugriff (dieser Abschnitt). Empfängerverschlüsselte private qubs (eine reservierte Phase-2-Funktion, noch nicht spezifiziert) bilden als zweite Stufe darauf.
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.
versionMUSS für v1.0-Wrapper-Bytes gleich0x01sein.qub_idMUSS dem Feldqub_iddes nach dem Unwrap wiederhergestellten SealedQubs entsprechen. Sowohl die Referenzfunktionwrap_sealed_qubals auchunwrap_sealed_qubparsen das innere CBOR und erzwingen diese Gleichheit direkt; die AAD-Bindung sorgt davon unabhängig dafür, dass eine nachträgliche Manipulation der äußerenqub_idbei der Authentifizierung fehlschlägt.nonceMUSS 96 Bit (12 Bytes) lang sein, frisch von einem CSPRNG für jeden Wrap-Vorgang generiert. Die Wiederverwendung einer Nonce unter demselben Schlüssel ermöglicht AEAD-Nonce-Reuse-Angriffe, die den Klartext wiederherstellen; Erzeuger MÜSSEN (key,nonce)-Paare als Einmalverwendung behandeln.ciphertextist die AES-256-GCM-Ausgabe: Ciphertext-Bytes konkateniert mit dem 16-Byte-Authentifizierungs-Tag. Genauciphertext.len() == SealedQubCbor.len() + 16.
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:
- WASM-Ersteller:
getrandom(WebCrypto unter demwasm_js-Backend). - Aufrufer der serverseitigen Versiegelungs-API: dessen lokaler CSPRNG; der Aufrufer stellt
Kbereit und behält ihn alswrapper_key_b64url. Der Worker verwendetKim Speicher für den Wrapper, DARF ihn aber NICHT persistieren. Dadurch kann ein idempotenter Wiederholungsversuch eine redigierte Antwort anhand der vom Aufrufer zurückbehaltenen Berechtigung wiederherstellen, statt auf ein einmaliges, serverseitig erzeugtes Geheimnis angewiesen zu sein.
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
- Die Autorensignatur (§9) bleibt unverändert: Signaturen werden innerhalb des inneren
QubEnvelopeberechnet und nach Unwrap → tlock-Decrypt → CBOR-Parse wiederhergestellt. - Die Verschlüsselung mit dem öffentlichen Schlüssel des Empfängers (das reservierte Feld
recipient_pubkey) ist eine künftige Funktion, die sich vom heutigen privaten, schlüsselbasiert zugriffsgesteuerten Wrapper-Modus unterscheidet. - Der aktuelle serverseitige Pakt-Mitunterzeichnungsfluss erzeugt öffentliches/unverpacktes
SealedQubCbormit Sichtbarkeit0x01; er kann das nur im Browser gewährleistete K-Geheimhaltungsmodell nicht erfüllen, da die endgültige Versiegelung nach der serververmittelten Mitunterzeichnung erfolgt. Ein künftiger Erzeuger privater Pakte kann denselben Wrapper verwenden, der gegenüber dem inneren Inhaltstyp byteblind ist.
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:
- Keine Aufzählungsimmunität. Öffentliche qubs verzichten konstruktionsbedingt auf die Aufzählungsimmunitäts-Eigenschaft aus §13.1. Der Referenz-Upload-Dienst prägt ihnen (und nur ihnen) einen
Visibility: public-Tag im dauerhaften Speicher auf, sodass sie absichtlich auffindbar sind; private qubs tragen keinen solchen Tag und behalten ihre Byte-Ununterscheidbarkeit. - Klartext-Titel zum Versiegelungszeitpunkt offengelegt. Das
title-Feld aus §3.2 ist Klartext innerhalb vonSealedQubCbor. Unter dem Wrapper ist es verborgen, bis ein EmpfängerKliefert; ohne den Wrapper ist es vom Moment des Uploads an — vor der Entsperrung — im dauerhaften Speicher für jeden lesbar. Konforme Ersteller-Apps MÜSSEN dies zum Versiegelungszeitpunkt offenlegen. - Die Erkennung ist strukturell und wird gegengeprüft. Ein konformer Viewer bzw. Embed unterscheidet die beiden gespeicherten Formen durch Parsen: Bytes, die als
OuterWrapperparsen, nehmen den Unwrap-mit-K-Pfad; Bytes, die als blankesSealedQubCborparsen, werden direkt akzeptiert. Der wiederhergestellte innere Wert MUSS dazu passen (0x00für verpackt/privat,0x01für unverpackt/öffentlich).qub_idbindet die Sichtbarkeit nicht, doch die kanonischenSealedQub-Bytes enthalten sie, sodass die öffentlichen und privaten inneren Kodierungen nicht byte-identisch sind.
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:
- Rust:
crates/qub-core/tests/wrapper_vectors.rs(cargo test -p qub-core --test wrapper_vectors). - TypeScript:
workers/api/src/crypto/__tests__/wrapper.test.ts(npm test).
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:
- Signatur: ML-DSA-65 (
sig_alg = 0x01; öffentlicher Schlüssel mit 1.952 Bytes, Signatur mit 3.309 Bytes) und unsigniert (sig_alg = 0x00). Die Codebasis reserviert0x02für Ed25519, doch Protokoll v1 aktiviert es nicht; ein v1-Verifizierer MUSS jedensig_alg-Wert außerhalb von{0x00, 0x01}ablehnen. - Timelock: ausschließlich drand quicknet — der Chain-Hash, der öffentliche Schlüssel, der Genesis-Zeitpunkt und die Periode sind feste Netzwerkparameter, die von der Referenzimplementierung
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) undconfig/drand-endpoints.jsongetragen werden. - Äußerer Wrapper: ausschließlich AES-256-GCM v1 (§13).
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:
- Ein zweites
sig_alg-Byte (Ed25519-Aktivierung, ML-DSA-87 oder ein beliebiger neuer Eintrag in der Registry aus §9). - Eine zweite drand-Chain im Produktiveinsatz.
- Eine zweite äußere Wrapper-Version.
- Eine Rotation des Vertrauensankers des Transparenz-Logs—der Adresse
LogProfile.anchor_owneroder des festgeschriebenen öffentlichen Empfangsschlüssels (§16.6). DasLogProfileergänzt die Profiloberfläche aus §15.2 als kontrolliertes Primitiv: Eine Rotation ist eine signierteLogProfile-Anhebung, die in einem Verifizierer-Update ausgeliefert wird (geplante Rotationen signieren vom ausgehenden zum eingehenden Schlüssel über Kreuz; kompromittierungsbedingte Rotationen können dies nicht und stützen sich auf diese Anhebung, wobei die Prev-Anchor-Fork-Prüfung den zwischenzeitlichen Schaden begrenzt). Die Versionsräume des Transparenz-Logs (LOG_VERSION,ANCHOR_FORMAT) entwickeln sich als unabhängige Geschwister, genau wie die Wrapper-Version aus §12.5 unabhängig von der Protokollversion ist.
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-WriterLogDO+ koordinatenbasiertes R2-Knotenspeicher, der/uploadLog-Append-Versuch, die täglichen Anchor- + Bündler-Drain-Crons, dieGET /api/v1/qub/:tx_id/proof(Inklusions-) undGET /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/uploadist immer R2-dauerhaft, aber nur protokollgedeckt, wennLOG_DOkonfiguriert ist und das Inline-Append erfolgreich ist; erst dann liefert seine Antwortlog_seq,receiptundanchor_status. IstRECEIPT_SKabwesend oder ungültig, ist dassig_b64urldieser 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_ownerist noch der[0xAB; 32]-Platzhalter); (b) der Quittungs-Signaturschlüssel und der passende Public-Key-Pin (RECEIPT_SKist optional undLogProfile.receipt_pubkeyderzeit 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 amSealedQub/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):
- 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_b64urlnicht 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. - Veröffentlichte Monitor-Methodik + vorheriger Ketten-Walk — der Anker
prevKette ist Kopf→genese; eine Gabel (zwei Anker an einemsizemit unterschiedlichenrootoder einem gebrochenenprev) ist ein veröffentlichter Beweis für Fehlverhalten. Äquivokationserkennung ist eine erklärte operative Verpflichtung, keine stille Annahme. - 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) alspublishHeadHook am Anker-Cron (workers/api/src/utils/heads-publish.ts): einPUTzur Inhalts-API ohneshaist nur anhängend (ein422bedeutet, dass der Kopf bereits veröffentlicht ist, niemals eine Überschreibung); Opt-in / deploy-gated aufPUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}und inert, bis das Repository bereitgestellt ist. Ein harter GitHub-Ausfall überschreibt über denhealth_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):
- Die plattformübergreifende Einrichtung
tlog_v1.json(Rust + TS, das §14.5wrapper_v1.jsonMuster) 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). - 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).
- 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. - 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:
- Vordere Hälft-Gates (Authentifizierung, Validierung, Idempotenz-Shard-Schlüssel) — unverändert.
- Erstellen Sie, taggen und signieren Sie die exakt einzelne Arweave-Transaktion. Dies leitet
tx_idlokal 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. - 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. - Wenn
LOG_DOkonfiguriert ist, versucht synchronLogDO.append(leaf). Der einzelne Schreiber weistseqzu, 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 ohnelog_seq,receiptoderanchor_statuserfolgreich sein. Trotz eines Implementierungskommentars wird heute keine automatische spätere Log-Abstimmung verdrahtet. - 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_b64urlist 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. - 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_idnicht ä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.
Standardpfad (
kind=0x02) Blatthonestität — GELÖST. Versenden Sie die Art der Zwei-Blatt-Split wie angegeben:kind=0x02verpflichtet wederbody_hashnochdrand_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.Doppeldeutigkeit / Auslassungsverantwortung — ENTWURF GELÖST, BEREITSTELLUNG UNVOLLSTÄNDIG. Das Design erfordert, dass der Siegel-Empfangsschlüssel in
LogProfilefixiert und durchanchor_ownergegengezeichnet 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.Fixierte Anker-Eigentümer-Vertrauenswurzel + Rotation — GELÖST. Übernehmen Sie die
LogProfile-Fixierung (§16.6); der Verifizierer prüftanchor_tx.owner == anchor_ownerund 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.Private-Qub-Blattblendung — GELÖST. Blenden für private Qubs beibehalten (
ref = SHA3-256(qub_id ‖ log_blind_secret)), rohesqub_idfür öffentliche Qubs (bereits §16.2.1),chashals selbstständige Bindung.log_blind_secretist ein Korrelations-/Sybil-Grad-Geheimnis, nur nach vorne rotieren (§16.2.1).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-BlockzeitT, nicht den Betreiber-kontrolliertenanchored_at(§16.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.
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 msbleibt ein Design-/Betriebsziel, kein Protokollversprechen (§16.10).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.subtleRound-Trip und dem Post-Bundle Arweave-Akzeptanz-Monitor (§16.8).
Bindende Startbedingungen (in Implementierung + Produkt-/Rechtsprüfung übernehmen):
- Anspruchsobergrenze (Q1/Q6). Keine Oberfläche darf sagen das Protokoll beweist den Inhalt eines behaupteten Blattes oder die Freischaltrunde; der erlaubte Anspruch für ein erfolgreich verankertes
kind=0x02-Blatt ist geordnet, manipulationssicher, mit vertrauenslosem Obergrenzen-Verpflichtungszeitpunkt. Eine Antwort ohne Beleg-Tupel hat keinen Protokollanspruch. Keine Zeitstempelkopie trägt eine numerische Latenzgarantie. - Zeugen-Ehrlichkeit (Q2). Marktliche Zweideutigkeit als erkennbar + belegbar, niemals unabhängig bezeugt.
- Beleg- + Anker-Schlüssel (Q2/Q8). Bevor die Ansprüche auf Nichtabstreitbarkeit/verankerte Verifikation verschickt werden, den öffentlichen Belegschlüssel bereitstellen und kompilieren, ihn mit dem bereitgestellten Anker-Eigentümer kreuzsignieren und das Anker-Wallet als eigenes JWK führen, getrennt vom Upload-Wallet.
- Deep-Hash-Gate (Q8). Kein Anker oder T3-Transaktion wird verschickt, bevor die beidseitige Fixierung + Interoperabilitätsprüfung bestanden ist; der Annahme-Monitor meldet bei Fehler.
- Speichervoraussetzung (Q7). Koordinatenschlüssel-gebundener Knotenspeicher + geloeschter-DO Kaltblatt-Vektor sind Voraussetzungen für die Garantie "Wiedergewinnung macht den Beweis niemals ungültig".
17. Portables Verifizierungsbündel (.qub)
Status. Dieser Abschnitt ist implementiert (W7 / UP-C2):
qub_core::exporterzeugt und parst das Bündel, undtools/qub-verifyist 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.