qub-Protokollspezifikation
qub ist ein Protokoll für kryptografische temporale Verpflichtungen: ein System, das Worte auf ein zukünftiges Datum versiegelt und beweist, sobald dieses Datum eintritt, exakt was gesagt wurde und wann.
Drei Primitive ermöglichen dies. drand ist ein dezentrales Zufallsbeacon — der Enthüllungszeitpunkt wird durch Physik erzwungen, nicht durch das Wohlwollen einer Partei. Dauerhafter öffentlicher Speicher ist ein manipulationssicherer öffentlicher Speicher — keine Partei kann einen qub bearbeiten oder löschen, sobald er versiegelt wurde. ML-DSA-65 ist eine post-quantensichere digitale Signatur — jeder qub ist an ein Schlüsselpaar gebunden, dessen Geheimnis das Gerät des Autors nie verlässt.
Zusammen erzeugen diese Primitive eine Aussage, die zeitversiegelt, manipulationssicher und zurechenbar ist — 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 |
|---|---|
| Version | 1.0 (Protokollversion 0x01, äußere Wrapper-Version 0x01) |
| Datum | 2026-05-01 |
| Status | Entwurf |
| Geprüft bis | 2026-05-01 |
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, // 0x01 = public (only value in MVP)
content_type: u8, // 0x01 = text (only value in MVP)
plaintext: Vec<u8>, // UTF-8 qub body
sender_label: Option<String>, // Decorative display name; not authenticated
status: DraftStatus, // Composing | Sealed | Uploaded | Failed
}
2.2 QubEnvelope (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>, // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
sender_label: Option<String>, // Decorative; not authenticated in MVP
reply_to: Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
body: Vec<u8>, // Content payload (UTF-8 for text, CBOR for pact)
body_hash: [u8; 32], // SHA3-256(body) (see §4.2)
sig_alg: u8, // Signature algorithm (see §9.2)
author_signature: Option<Vec<u8>>, // Set when sig_alg != 0x00
author_pubkey: Option<Vec<u8>>, // Set when sig_alg != 0x00
cosigner_pubkey: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
cosigner_signature: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}
Basislinie (unsignierter Text-qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, alle Option-Felder abwesend.
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). In den dauerhaften Speicher hochgeladen. Dies ist das On-Chain-Artefakt.
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x01 = public; v1 viewers reject other values
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — surfaced on the verdict-watch CTA
// before reveal; mirrors QubEnvelope.outcome_at;
// bound to qub_id via the §4.1 preimage.
drand_chain_id: String, // drand chain hash (hex string)
drand_round: u64, // Target drand round number
tlock_ciphertext: Vec<u8>, // tlock-encrypted QubEnvelope CBOR bytes
recipient_pubkey: Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
// but not interpreted by the v1 reference viewer
title: Option<String>, // Plaintext title surfaced on the viewer
// countdown before reveal. Bound to qub_id
// via title_hash (§4.1). 1..=100 NFC code
// points, no control characters.
}
2.4 RevealedQub (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>, // V1.1 — übernommen aus QubEnvelope.outcome_at / SealedQub.outcome_at; steuert den Verdikt-Beobachtungsblock auf der Enthüllungsseite (verdict-uplift-plan §5.1)
drand_chain_id: String,
drand_round: u64,
sender_label: Option<String>,
title: Option<String>, // Carried forward from SealedQub.title
reply_to: Option<[u8; 32]>,
body: Vec<u8>,
body_hash: [u8; 32],
body_hash_verified: bool,
author_signature: Option<Vec<u8>>,
author_pubkey: Option<Vec<u8>>,
signature_verified: Option<bool>,
cosigner_pubkey: Option<Vec<u8>>,
cosigner_signature: Option<Vec<u8>>,
cosigner_verified: Option<bool>,
}
3. 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 (V1.1 verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
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 (V1.1 verdict mechanic)
"visibility" (11 encoded bytes)
"drand_round" (12 encoded bytes)
"drand_chain_id" (15 encoded bytes)
"recipient_pubkey" (17 encoded bytes) ← only if present
"tlock_ciphertext" (17 encoded bytes)
PactTerms (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: V1.1 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 Verdict-Mechanik, die dieses Feld motiviert.
Codierung von drand_round: V1.2 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 Ciphertext nicht an eine andere (z. B. bereits vergangene) Runde rebinden, als das angezeigte unlock_at impliziert. Das Entsperrverfahren (§8) verifiziert zusätzlich, dass die in der tlock-Ciphertext-Stanza eingebackene 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 Felds im QubEnvelope (Body, Zeitstempel, Content-Typ, Version) erzeugt eine andere qub_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_id hängt nicht von
sender_label,author_signatureoderauthor_pubkeyab. Dies bedeutet, dass derselbe Inhalt, zur selben Zeit versiegelt, unabhängig davon, wer signiert, dieselbe qub_id ergibt. - 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 = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| 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 |
Die ceil()-Operation wählt die erste drand-Runde, deren Enthüllungszeitpunkt ≥ unlock_at ist. Dies stellt sicher, dass der qub nicht vor dem gewählten Entsperrzeitpunkt entschlüsselbar wird.
Grenzfall: Wenn (unlock_at - chain_genesis_time) exakt durch chain_period_seconds teilbar ist, ist das Ergebnis genau diese Runde — der qub wird präzise zum Enthüllungszeitpunkt dieser Runde entsperrt.
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() |
Upload in den dauerhaften Speicher, Viewer-Fetch |
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), value: String (≤ 2,000) } // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }
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.
3. Compute body_hash = SHA3-256(body).
4. Set created_at = current Unix seconds UTC.
5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
(Computed here, before qub_id, because drand_round is bound into the qub_id
preimage — §4.1, V1.2.)
6. Compute qub_id (see §4.1), folding in drand_round from step 5.
7. Construct QubEnvelope with all fields.
8. Serialise QubEnvelope using canonical CBOR → bytes B.
Assert: serialised output matches canonical profile (§3).
9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C, and matching qub_id, version,
unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
never the bare SealedQubCbor. K leaves the device only as the URL
fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
signs and uploads to permanent storage. The service is byte-blind to the inner
SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
when a short code is allocated). Browsers do not transmit URL fragments
to servers, so K is never observed by qub.social or any storage gateway.
Speicher-Tag-Schicht (out-of-band). Der qub-Upload-Dienst hängt einen bewusst kleinen Satz von Speicher-Transaktions-Tags zusammen mit der gewrappten Nutzlast 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 (durch Allowlist validierte Compose-Intention — z. B. quote, reply, commitment), 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. Der äußere Wrapper (§13) schützt den inneren Body vor Ciphertext-Korrelation — wodurch verhindert wird, dass ein Harvester qub-förmige Uploads erkennt und nach Veröffentlichung ihrer drand-Runde massenhaft entschlüsselt.
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 path AND
K = base64url_decode(fragment) from the URL fragment. If the fragment
is absent or malformed → display "this URL is missing its decryption
key" and stop; the viewer MUST NOT contact the storage gateway
without K, since fetching wrapped bytes the viewer cannot decrypt
serves no purpose and only leaks the access attempt.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
`version` byte is `0x01`, and compute SealedQubCbor =
unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
swapped nonce) → display "this URL's decryption key does not match
the stored qub" and stop. Authentication failures are
indistinguishable to the viewer per §13.5.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check (V1.2). Recompute expected_round =
ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
Reject unless SealedQub.drand_round == expected_round AND the round baked
into the tlock ciphertext stanza (read via the age/tlock header, no signature
required) == expected_round. The stanza round is the one that actually gates
decryption; without this check a malicious creator could bind the ciphertext
to an already-past round while displaying a future countdown, so anyone
reading the stored bytes could decrypt before unlock_at. Implementations with
no chain identity (test mocks) skip this check.
7. Once current time ≥ SealedQub.unlock_at:
a. Fetch drand round signature for SealedQub.drand_round from drand network.
b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
8. Parse B → QubEnvelope.
9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
Fail → integrity error.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact). Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.
9. 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 |
|---|---|---|---|
0x00 |
Keine Signatur (unsigniert) | — | — |
0x01 |
ML-DSA-65 (FIPS 204) | 1.952 Bytes | 3.309 Bytes |
Empfänger MÜSSEN unbekannte sig_alg-Werte ablehnen.
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 (V1.2) |
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 kann einen öffentlichen qub ohne Mitwirkung von qub verifizieren. Das Verifizierungsverfahren:
1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.
Was die Verifizierung beweist:
| Beweis | Was er feststellt |
|---|---|
| Verpflichtung | Der Ciphertext existierte zum Zeitpunkt des Speicher-Blockzeitstempels. |
| Integrität | Der Klartext-Body entspricht dem festgelegten Hash und wurde nicht verändert. |
| Timing | Der Inhalt war unlesbar bis zur drand-Runde, die dem gewählten Entsperrzeitpunkt entspricht (vorbehaltlich der Sicherheitsannahmen von tlock und drand). |
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 | Der qub beweist Inhalt und Timing, nicht das, was der Ersteller subjektiv meinte. |
| Vor-Ereignis-Timing | Die Speicher-Blockaufnahme kann gegenüber dem tatsächlichen Upload um Minuten verzögert sein. Der Verpflichtungszeitstempel ist die Blockzeit, nicht der Moment, in dem der Benutzer auf „versiegeln" geklickt hat. |
12. Versionierung
12.1 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.2 Versionsverlauf
| Version | Wert | Beschreibung |
|---|---|---|
| v1 | 0x01 |
Öffentliche Text-qubs (content_type 0x01), bilaterale Pakt-Vereinbarungen (0x03, structured/v1-Schema, ML-DSA-65 Autor + Mitunterzeichner), tlock, SHA3-256 |
12.3 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.4 Ä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 |
v1-Standard |
| — | 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.
Der äußere Verschlüsselungs-Wrapper schließt diesen Kanal, indem er eine zusätzliche symmetrische AEAD-Schicht zwischen das kanonische SealedQubCbor und die in den dauerhaften Speicher hochgeladenen Bytes setzt. Der 256-Bit-Schlüssel K lebt 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. Jeder qub im dauerhaften Speicher ist daher ein opaker Ciphertext, dessen Klartext ohne die vom Ersteller gewählte Freigabe-URL unwiederherstellbar ist.
Nettoeffekt:
- Aufzählungsimmunität standardmäßig. Gewrappte Bytes im dauerhaften Speicher sind byteweise nicht von beliebigem Ciphertext zu unterscheiden. Eine Harvester-Strategie, „qub-förmige Uploads per GraphQL abzufragen und mit öffentlichen drand-Signaturen massenhaft zu entschlüsseln", endet nicht in Klartext.
- Crypto-Shredding-Datenschutzhaltung. qub.social kann seinen eigenen Korpus buchstäblich nicht entschlüsseln. Vorladungen erreichen Ciphertext, nicht Klartext.
- 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
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes ← uploaded to permanent storage (§7 step 15)
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.4
qub_id: [u8; 32], // copied from inner SealedQub; AEAD AAD
nonce: [u8; 12], // 96-bit AEAD nonce
ciphertext: Vec<u8>, // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}
Feldinvarianten.
versionMUSS für v1.0-Wrapper-Bytes gleich0x01sein.qub_idMUSS dem Feldqub_iddes nach dem Unwrap wiederhergestellten SealedQubs entsprechen. Der Unwrap-Schritt erzwingt dies nicht direkt (die AEAD-AAD-Bindung macht byte-Level-Manipulation unmöglich), aber die Entsperrebene prüft die Beziehung transitiv: Wenn ein Ersteller einSealedQubCboreinwickelt, dessen internequb_idnicht mit der Wrapper-qub_idübereinstimmt, schlägt §8 Schritt 11 fehl.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 Bytes von qub B aus dem dauerhaften Speicher mischen | AAD-Mismatch → 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
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.4
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
return P // P is the inner SealedQubCbor
Kollaps 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. - Empfängerverschlüsselte private qubs (eine reservierte Phase-2-Funktion, noch nicht spezifiziert) komponieren als zweite Vertraulichkeitsstufe auf diesem Wrapper; beide Stufen können gleichzeitig aktiv sein.
- Pakte (§6, content_type
0x03) werden exakt wie Text-qubs gewrappt; der Wrapper ist gegenüber dem inneren Inhaltstyp byte-blind.
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, in welchem Fall das kanonische SealedQubCbor direkt in den dauerhaften Speicher geschrieben wird, ohne OuterWrapper-Schicht und ohne Schlüssel K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
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, der die arweave_tx_id hat, entschlüsseln — es ist kein URL-Fragment erforderlich, weil es kein K gibt. Dies ist der bewusste Trade für Oberflächen, die der Server ansteuern muss: Enthüllungs-Benachrichtigungs-E-Mails, Drittanbieter-Embeds und reichhaltigere SEO nach der Enthüllung benötigen allesamt einen Link, der ohne ein Geheimnis funktioniert, das der Server nie hält (§13.6).
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. Ein konformer Viewer bzw. Embed unterscheidet die beiden Formen durch Parsen: Bytes, die als
OuterWrapperparsen, nehmen den Unwrap-mit-K-Pfad; Bytes, die als blankesSealedQubCborparsen, werden direkt akzeptiert. Es ist kein Wire-Flag erforderlich, undqub_idbindet die Sichtbarkeit nicht — derselbe Inhalt ist auf derSealedQub-Ebene byte-identisch, ob öffentlich oder privat versiegelt.
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 = 4695445 (= (1736294400 - 1595431050) / 30, drand mainnet params §14.2)
body = "Hello, future." (UTF-8, 14 bytes)
title = absent
Intermediate:
body_hash = SHA3-256("Hello, future.")
= 76ab8b3f843c6ed4f2d0fd75b9f457b4
ad49dd4450f9c22723ae430e3af3211d
title_hash = [0u8; 32] (title absent — §4.2.1 sentinel)
Domain separator (10 bytes):
[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
Preimage (108 bytes — V1.2):
domain_separator || // 10 bytes
0x01 || // version
0x01 || // content_type
0x0000000067748580 || // created_at as i64 big-endian (1735689600)
0x00000000677DC000 || // unlock_at as i64 big-endian (1736294400)
0x0000000000000000 || // outcome_at_or_zero (outcome_at absent)
0x000000000047A595 || // drand_round as u64 big-endian (4695445)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 3a9fcb31b750d985c262fada6d4f777f
d6a28be831d941d85c131f5a4bbaf8a4
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 Preimage-Layouts (vor dem Launch — kein Live-qub hing von diesen ab): die 92-Byte-V1.0-qub_id lautete 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; die 100-Byte-V1.1-qub_id (nach Einbeziehung von outcome_at_or_zero) lautete b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 bezieht drand_round ein und hebt den Domain-Separator auf QUB_ID_V2 an.
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
ceil(4675285.0) = 4675285
drand_round = 4675285
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 Fälle:
| Fall | Abdeckung |
|---|---|
basic-text-public |
Kleinste realistische SealedQub-Form; keine optionalen Felder. Etabliert die kanonische Wrapper-Form für einen v1.0-typischen qub. |
with-recipient-pubkey |
SealedQub mit gesetztem recipient_pubkey (Pfad Phase 2). Anderer innerer CBOR-Schlüsselsatz, andere qub_id. |
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 Registry aus §9.2 definiert keine weiteren Werte; ein v1-Verifizierer MUSS jedensig_alg-Wert außerhalb von{0x00, 0x01}ablehnen. Ein zukünftiger Ed25519-Eintrag wird antizipiert (§15.3), ist aber in v1 nicht zugewiesen. - 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 Primitiv fest. Das Wire-Format legt keine Agilitätsoberfläche offen.
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.
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. Transparenz-Log und Haltbarkeitsstufen (Design — Prüfung abgeschlossen)
Status. Dieser Abschnitt ist eine Designspezifikation. Die Wire-Formate, das Hashing und das Vertrauensmodell unten sind normativ für die Implementierung, aber es ist noch kein Transparenz-Log-Code ausgeliefert. Die externe W5-Prüfung ist abgeschlossen: §16.15 verzeichnet die getroffenen Entscheidungen und die daraus hervorgegangenen bindenden Launch-Einschränkungen. Die Implementierung darf unter diesen Einschränkungen fortgesetzt werden. §16 bleibt im selben Sinne zukunftsgerichtet wie §15 — er fixiert das Ziel, damit die Implementierung gegen ein festgelegtes Design landet, anstatt das Vertrauensmodell im Code-Review neu herzuleiten. Er ist strikt additiv — jeder bestehende qub behält seine individuelle Arweave-Transaktion, und es gibt keine Änderung am
SealedQub- /QubEnvelope-Wire-Format.
16.1 Begründung und Haltbarkeitsstufen
Heute ruhen sowohl die Haltbarkeit eines qubs als auch seine zeitliche Verpflichtung auf einer einzigen Arweave-Transaktion pro qub (§11). Das koppelt die Versiegelungslatenz an die Arweave-Finalität, macht den Upload pro qub zu einer produktbezogenen Kostengrenze (ARWEAVE_DAILY_CEILING) und liefert keine manipulationssichere Reihenfolge über qubs hinweg. Das Transparenz-Log fügt unterhalb und um diese einzelne Stufe herum zwei Schichten hinzu:
| Stufe | Name | Garantie | Wann |
|---|---|---|---|
| T1 | Synchrones R2-First-Ack | Haltbarkeitsuntergrenze — versiegelte Bytes werden in dauerhaften Speicher geschrieben, bevor die Versiegelung zurückkehrt (< 300 ms p95). |
Jeder qub, synchron (§16.10). |
| T2 | Gebündelte Transparenz-Log-Aufnahme | Universelle Append-only-, manipulationssichere Festlegung + Gesamtreihenfolge, in Arweave verankert. | Jeder qub, aufgeschoben + gebündelt (§16.5–16.7). |
| T3 | Arweave-Permanenz pro qub | Eine individuelle Arweave-Transaktion für den qub. | Bezahltes Upsell und die Fallback-Lösung bei Arweave-Nichtverfügbarkeit (§16.8). |
T2 macht Arweave pro qub zu einer Wahl (T3) statt zum einzigen Haltbarkeitspfad. ARWEAVE_DAILY_CEILING wird als Produktgrenze außer Kraft gesetzt und nur noch zu einem Schutzschalter (Circuit Breaker) auf dem dedizierten Anker-Wallet herabgestuft (§16.7); Benutzerversiegelungen werden niemals abgelehnt, weil sie es überschreiten.
Haltbarkeitsehrlichkeit (entschieden — §16.15 Q6). Die Haltbarkeit regrediert nicht: Der T1-R2-Schreibvorgang ist synchron und write-once, sodass ein Free-Tier-qub, der kein T3 erworben hat, in dem Moment vollständig haltbar ist, in dem die Versiegelung zurückkehrt. Was vergröbert, ist der beweisbare Obergrenzen-Festlegungszeitpunkt: Für einen Free-qub wird er zur Anker-Blockzeit statt zur Blockzeit einer Transaktion pro qub. Bei geringem Volumen — dem realistischen Zustand zu Launch-Beginn und in Schwachlastzeiten — ist die volle tägliche Taktung der typische Boden, nicht ein seltener Randfall. Die Produktdarstellung ist daher eine Obergrenze ohne zugesicherte numerische Latenz — „jetzt versiegelt und haltbar; ein unabhängiger öffentlicher Zeitstempel wird beim nächsten Log-Anker hinzugefügt (typischerweise täglich)" — und der Beweis einer Festlegung auf die genaue Stunde ist eine bezahlte T3-Eigenschaft, offengelegt an der Stufenvergleichs-Oberfläche und in den Bedingungen (§16.11, §16.15 Q6). Jede Zeitgrenze ist ausschließlich ein internes SLO, niemals ein vermarktetes SLA.
16.2 LogLeaf-Struktur (zwei festgelegte Formen)
Ein Log-Eintrag ist ein LogLeaf, codiert als handgeschriebenes kanonisches CBOR unter dem §3.1-Profil (definite Länge, keine Tags, keine Floats, kürzestmögliche Ganzzahlen, NFC-Text, optionale Felder weggelassen, wenn nicht vorhanden, Schlüssel sortiert nach codierter Bytelänge aufsteigend, dann bytweise). Der kanonische §3.1-parse → re-encode → compare-Schutz wird auf dem Encode-Pfad vor dem Hashing angewendet (nicht nur beim Decodieren), sodass zwei Implementierungen sich nicht über die Leaf-Bytes durch einen Unterschied in der Ganzzahlbreite oder der Schlüsselreihenfolge uneins sein können. Alle Ganzzahlen sind u8 / u64 / i64; alle Digests sind 32-Byte-Byte-Strings (bstr[32]). Eine gespeicherte Arweave-Transaktions-id ist ein roher 32-Byte-SHA-256-Digest, getragen als bstr[32], niemals ein base64url-Textstring (entspricht §3.3).
Das Leaf hat zwei Formen, ausgewählt durch ein kind-Byte, weil der Worker auf dem Standard-Upload-Pfad byte-blind ist: POST /api/v1/upload erhält nur qub_id und unlock_at als nicht vertrauenswürdige Client-Behauptungen — body_hash, drand_round, created_at und drand_chain_version sind alle innerhalb des äußeren §13-Wrappers versiegelt, dessen Schlüssel der Worker niemals hält. Nur der Server-Versiegelungs-Pfad (POST /api/v1/seal) leitet body_hash / drand_round aus dem Klartext ab. Eine einzelne Leaf-Form, die body_hash + drand_round trägt, würde daher für die Mehrheit der realen qubs Werte festlegen, die der Betreiber nie verifiziert hat. Die Aufteilung hält jeden festgelegten Wert ehrlich:
| Schlüssel | Cod. Länge | Typ | Vorhandensein | Bedeutung |
|---|---|---|---|---|
seq |
4 | u64 |
erforderlich | Globaler 0-basierter Leaf-Index; die Position, auf die sich der Aufnahmebeweis festlegt. |
kind |
5 | u8 |
erforderlich | 0x01 attestiert (Server-Versiegelung) oder 0x02 behauptet (Client-Versiegelung / byte-blinder Upload). |
ref |
4 | bstr[32] |
erforderlich | Leaf-Referenz-id. Attestiert → rohe qub_id. Behauptet → die geblindete id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
6 | bstr[32] |
erforderlich | Inhaltsadresse SHA3-256(stored_bytes) — die eine inhaltliche Bindung, die der Worker auf beiden Pfaden stets ehrlich berechnen kann. |
unlock_at |
10 | i64 |
erforderlich | Kopiert (attestiert) oder behauptet (behauptet); validiert > 0, bevor es in das Leaf eingeht. |
received_at |
12 | i64 |
erforderlich | Worker-Wanduhrzeit beim R2-Ack. Nicht-beweiskräftig (betreiber-behauptet; §16.6). Vorhanden zur Selbstbeschreibung, niemals ein Beweis. Validiert > 0. |
body_hash |
10 | bstr[32] |
nur kind=0x01 |
Weggelassen bei 0x02 — dem Worker fehlt es unter §13. |
drand_round |
12 | u64 |
nur kind=0x01 |
Weggelassen bei 0x02. |
Ein kind=0x02-Leaf legt bewusst weder body_hash noch drand_round fest: Es attestiert die Festlegung und Reihenfolge eines opaken Ciphertexts an der Inhaltsadresse chash, der qub_id und unlock_at beansprucht — nicht seinen Klartext oder seine Runde. Die Klartext-/Runden-Legs für einen behaupteten qub stammen aus der bestehenden §11-.qub-Bündel-Verifizierung, nicht aus dem Log (§16.11). drand_chain_version ist nicht im Leaf (es ist auf dem Standard-Pfad innerhalb des Wrappers); die Chain-Granularität liegt auf dem Anker (§16.7). Encoder-Disziplin: ein durchgängig nulliges ref oder chash ablehnen und nicht-positive unlock_at / received_at ablehnen, analog zum outcome_at > 0-Sentinel-Schutz in cbor.rs.
16.2.1 Blinding privater qubs
Das Log darf nicht zum Aufzählungsorakel werden, das der äußere §13-Wrapper zu verhindern existiert (§13.1). Für einen privaten (gewrappten) qub legt das asserted-Leaf den geblindeten Bezeichner SHA3-256(qub_id ‖ log_blind_secret) fest, wobei log_blind_secret ein serverseitig gehaltenes Geheimnis ist, und lässt body_hash weg. Eine dritte Partei kann ein solches Leaf nicht an eine bestimmte qub_id binden; der Inhaber des qubs, der die Liefer-URL und damit qub_id besitzt, kann das Blinding neu berechnen, um seine eigene Aufnahme zu bestätigen. Ein öffentlicher qub (bereits aufzählbar, bereits den Visibility: public-Arweave-Tag gemäß §13.8 tragend) legt die rohe qub_id fest. Dies ist die eine Stelle, an der die eigenständige Verifizierbarkeit bewusst einer tragenden Datenschutzinvariante weicht; die eigenständige Bindung für private qubs ist chash (§16.9).
log_blind_secret-Verwahrung (entschieden — §16.15 Q4). Das Blinding schützt die Unverknüpfbarkeit der Leaves, nicht die Vertraulichkeit des Klartexts (das hält der §13-Wrapper unabhängig). Bei einer log_blind_secret-Kompromittierung berechnet der Angreifer für jede qub_id, die er bereits hält oder rekonstruieren kann (jeder qub, dessen Bündel/URL er hat, plus jede qub_id mit geringer Entropie oder jede öffentliche qub_id), das Leaf-ref in einem Hash neu und verknüpft es — dies ist eine direkte Verknüpfung einer bekannten Population, kein Brute-Force über einen unbekannten Raum. Klassifizieren Sie log_blind_secret als ein Geheimnis der Korrelations-/Sybil-Klasse in derselben Verwahrungsstufe wie andere Servergeheimnisse und rotieren Sie nur vorwärts (eine Rotation blindet zukünftige Leaves neu; sie kann bereits verankerte Leaves nicht rückwirkend entkoppeln).
16.3 Leaf- und Knoten-Hashing
Domänengetrenntes Hashing gemäß RFC 6962 §2.1, wobei SHA-256 durch SHA3-256 ersetzt ist:
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
Die Domänen-Präfixbytes 0x02 (Eintragskette, §16.4) und 0x03 (STH-Hash, §16.6) sind reserviert und disjunkt von diesen. Sie sind Einzelbytes und können daher nicht mit den bestehenden 10-Byte-ASCII-Domänentrennern (QUB_ID_V2 usw.) kollidieren. Der Baum ist der linksvolle unbalancierte RFC-6962-Baum (jede innere Teilung an der größten Zweierpotenz strikt kleiner als die Leaf-Anzahl des Teilbaums), was es Aufnahme- und Konsistenzbeweisen erlaubt, einen Audit-Pfad-Algorithmus zu teilen. Die Referenzspezifikation trägt expliziten Links-/Rechts-Ableitungs-Pseudocode und fixiert einen Nicht-Zweierpotenz-Testvektor (5 Leaves), sodass der Rechtskanten-Promotion-Fall — den ein 4-Leaf-Vektor verbirgt — geübt wird.
16.4 Hash-Verkettung (intern)
Das LogDO unterhält eine interne Eintragskette ausschließlich zur Absturzkonsistenz. Sie wird niemals veröffentlicht und ist niemals verifizierer-seitig:
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 Append-only-Autorität ist die kumulative Merkle-Wurzel + ihr Anker (§16.5–16.6), niemals die rohe Reihenfolge, in der der Betreiber zufällig Leaves ausliefert: Die Kette berechnet sich für jede ausgelieferte Reihenfolge neu, sodass nur die verankerte Wurzel die kanonische Position fixiert.
16.5 Kumulativer Merkle-Baum und Bündelung
Es gibt einen einzigen, stetig wachsenden RFC-6962-Baum über alle Leaves in seq-Reihenfolge — keine isolierten Bäume pro Bündel. (Eine carry-leaf-verkettete Konstruktion pro Bündel wurde verworfen: Sie ist keine echte Präfixbeziehung, sodass ihre „Konsistenzbeweise" unsolide sind.) Der kumulative Baum liefert echte RFC-9162-Konsistenzbeweise und erlaubt es einem einzelnen jüngeren Anker, die Aufnahme für jeden älteren qub zu beweisen.
Das Durable Object LogDO ist der einzige Schreiber (blockConcurrencyWhile, analog zu QuotaDO / EntitlementDO) — das Anhängen an ein gemeinsames Log ist Read-Modify-Write auf gemeinsamem Zustand und MUSS daher durch ein DO laufen, niemals durch KV. Es cached die Rechtskanten-Frontier des Baums (O(log n) Hashes), sodass das Schließen eines Bündels O(batch) ist. Ein Bündel ist die Menge der gemeinsam verankerten Leaves; seine Auslöser sind konfigurierbar, nicht protokoll-eingefroren: ein tree_size-Vorrücken um mindestens LOG_BATCH_MAX_LEAVES (Standard 4096), oder ein Alter, das die Anker-Taktung erreicht, oder ein erzwungener Flush, wenn eine bezahlte T3-Versiegelung eintrifft. root_i ist der kumulative Merkle-Tree-Hash über die Leaves 0 .. tree_size_i.
16.6 Signed Tree Head via Arweave-Anker
Die Arweave-Anker-Transaktion ist der Signed Tree Head und ersetzt eine Betreibersignatur für den Tree Head selbst: Der tägliche Anker benötigt keinen qub-Schlüssel, weil der Arweave-tx-owner die Signatur ist. Die Moat-These hält — das unveränderliche Substrat, nicht ein qub-gehaltenes Geheimnis, ist tragend für die verankerte Wurzel.
Es gibt im Design genau einen warmen qub-Signierschlüssel, und er ist gepinnt: der Quittungsschlüssel pro Versiegelung (§16.10). Sein öffentlicher Schlüssel ist in LogProfile festgelegt (mit dem Verifizierer verteilt) und von anchor_owner quer-signiert, sodass ein Verifizierer eine Quittung gegen dieselbe gepinnte Wurzel validiert wie den Anker. Dies ist die Auflösung von §16.15 Q2 — ein ungepinnter, betreiber-rotierbarer Quittungsschlüssel wäre abstreitbar (der Betreiber könnte leugnen, dass der Schlüssel seiner war), was den Rechenschaftswert der Quittung gegen den Betreiber-Adversär, dessen Abschreckung die Quittung existiert, zunichtemachen würde. Also: qub hält keinen ungepinnten Log-Signierschlüssel; der Quittungsschlüssel ist gepinnt und anchor_owner-quer-signiert.
Der SignedTreeHead ist kanonisches CBOR (Schlüssel nach codierter Länge): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (vorheriger sth_hash; Genesis = 32 Nullbytes), log_id:bstr[32], first_seq:u64, anchored_at:i64. Sein Hash ist sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Gepinnte Vertrauenswurzel. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Ein konformer Verifizierer MUSS anchor_tx.owner == LogProfile.anchor_owner verlangen, wobei anchor_owner (und der öffentliche Schlüssel des Quittungsschlüssels) als LogProfile in qub_core eingebacken ist — neben den bereits in DrandTimelockProvider::quicknet() vorhandenen quicknet-Konstanten — und mit dem Verifizierer-Binary verteilt wird. Der Verifizierer MUSS außerdem die Arweave-tx-Daten-→-tx_id-Bindung lokal verifizieren, anstatt einer /raw/-Antwort eines Gateways zu vertrauen. Dies schließt das Äquivokationsloch eines unautorisierten Wallets: „auf Arweave verankert" ist bedeutungslos, bis der Verifizierer welches Wallet pinnt.
Rotation ist eine §15-Governance-Erweiterung, keine Wiederverwendung (entschieden — §16.15 Q3). Die Profiloberfläche von §15.2 zählt derzeit nur sig_algs / drand-Chains / Wrapper-Versionen / Inhaltstypen auf, und die Auslöserliste von §15.3 enthält keinen davon — LogProfile / anchor_owner ist noch nicht in der Oberfläche von §15. Die Rotations-Governance muss daher gebaut werden: §15.3 wird (unten) erweitert, um den LogProfile-Auslöser hinzuzufügen, und eine Rotation ist ein signierter LogProfile-Bump, ausgeliefert in einem Verifizierer-Update. Eine geplante Rotation trägt eine Quer-Signatur ausgehend → eingehend; eine kompromittierungsgetriebene Rotation kann das nicht (der ausgehende Schlüssel ist genau dann nicht vertrauenswürdig/nicht verfügbar) und fällt auf den §15-gesteuerten Bump zurück, wobei die Vorgänger-Anker-Fork-Prüfung (unten) den Schaden in der Zwischenzeit begrenzt.
Äquivokationsfenster (erstklassiger Vertrauensparameter). Ein Leaf ist erst dann äquivokationsresistent, sobald sein abdeckender Anker Arweave-bestätigt ist. Das Fenster ist received_at → Anker-Bestätigung (≤ Taktung + Arweave-Finalität). Innerhalb davon sind die einzigen Garantien die gepinnte Versiegelungsquittung (§16.10) und qubs operative Integrität. Drei Rechenschafts-Artefakte machen dies ehrlich statt vage (das Witness-Modell ist die Auflösung von §16.15 Q2):
- Gepinnte signierte Versiegelungsquittung — das SCT-Analogon, das in der Upload-Antwort zurückgegeben wird (§16.10), signiert vom gepinnten,
anchor_owner-quer-signierten Quittungsschlüssel. Ein Leaf, das vor seinem Anker fallengelassen wird, hinterlässt dem Geschädigten eine nicht abstreitbare Quittung zum Veröffentlichen, was das Loch der stillen Auslassung schließt. - Veröffentlichte Monitor-Methodik + Vorgänger-Ketten-Walk — die Anker-
prev-Kette wird Kopf → Genesis durchlaufen; ein Fork (zwei Anker bei einemsizemit unterschiedlicherroot, oder ein gebrochenesprev) ist veröffentlichbarer Beweis für Fehlverhalten. Die Äquivokationserkennung ist eine erklärte operative Zusage, keine stille Annahme. - Doppelt selbstveröffentlichte Heads — jeder neue Head
{sth_hash, tree_size}wird in ein dediziertes, qub-eigenes öffentliches, Append-only-GitHub-Repository gepostet (das tragende manipulationssichere Selbstveröffentlichungs-Leg), mit einem Social-Post nur als bestmögliche Bestätigung. Ein fehlgeschlagenes Posten MUSS paging auslösen (nicht still fehlschlagen).
Ehrlichkeitsgrenze (bindende Einschränkung). Weil qub beide Posting-Oberflächen kontrolliert, ist dies selbstveröffentlicht, nicht unabhängig bezeugt. Keine Produkt-, Marketing- oder Rechtsoberfläche darf behaupten, das Log sei „unabhängig bezeugt"; die zulässige Aussage ist, dass Äquivokation erkennbar ist und eine nicht abstreitbare Quittung hinterlässt. Ein echter unabhängiger Drittpartei-Witness ist auf einen zukünftigen §15-Governance-Bump aufgeschoben.
received_at ist betreiber-behauptet, und keine Aussage darf sich darauf stützen — es wird niemals als Beweis oder als Streit-Bestätigung auf irgendeiner Produkt- / Rechts- / API- / Beweisdarstellungs-Oberfläche aufgeführt. Die Arweave-Anker-Blockzeit T ist der einzige vertrauenslose Zeitstempel (eine Obergrenze für „protokolliert bis"). Jede Monitor-Plausibilitätsprüfung von received_at MUSS gegen T vergleichen, nicht gegen das betreiber-kontrollierte anchored_at-STH-Feld; eine solche Prüfung ist ausschließlich ein Schutz gegen den Uhren-Bug eines ehrlichen Betreibers, nicht eine Rechenschaftskontrolle gegen einen böswilligen Betreiber (§16.15 Q5).
16.7 Anker-Transaktionsformat und Taktung
Das AnchorBundle ist der kanonisch-CBOR-Arweave-Transaktions-Body, geschrieben über den §16.8-Bundler: ver:u8, sth:bstr (kanonische SignedTreeHead-Bytes), prev_anchor:bstr (rohe Bytes der vorherigen Anker-tx-id; bei Genesis weggelassen), chain_hash:tstr (die geltende drand-Chain — quicknet) und der Leaf-CBOR-Strom des Bündels in seq-Reihenfolge, sodass der Anker eigenständig ist: Ein Monitor leitet root aus dem Body mit null qub-Abhängigkeit neu ab. (Wenn der Leaf-Strom bei hohem Volumen groß wird, kann eine zukünftige Revision nur einen Leaf-Bereich per Referenz festlegen; vermerkt, in v1 nicht übernommen.)
Arweave-Tags sind absichtlich aufzählbar — das Log ist dafür gedacht, gefunden zu werden, anders als private qubs: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tags sind nicht vertrauenswürdige Hinweise; der CBOR-Body ist die alleinige Autorität.
Taktung: standardmäßig täglich, mit dem Volumen überdacht (der Größen-Auslöser verkürzt unter Last die effektive Taktung automatisch); eine bezahlte T3-Versiegelung erzwingt einen Anker, sodass zahlende Kunden niemals einen Tag warten. Das Anker-Wallet ist dediziert und niedrig-volumig, getrennt vom Upload-Wallet — es MUSS sein eigenes JWK sein (ein eigenständiger Schlüssel, keine logische Rolle auf dem Upload-Wallet), sodass eine Upload-Wallet-Kompromittierung keine Anker fälschen kann — mit einem harten Anker-Transaktionsbudget pro Tag (dem herabgestuften ARWEAVE_DAILY_CEILING). Die Verwahrungshaltung wird offen ausgesprochen: ein eng abgegrenzter Hot-Key mit engem Schutzschalter und niedrigem Guthaben, nicht „kalt" — ein Wallet, das täglich automatisch signiert, kann nicht kalt sein, und die Spezifikation gibt das auch nicht vor.
16.8 ANS-104-Bundler
Ein hauseigener ANS-104-DataItem-Encoder und Deep-Hash-Signierer, etwa 300 Zeilen, nur Web Crypto, null npm-Abhängigkeiten (beide Turbo-SDKs scheitern 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 ist 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 Deep-Hash mit dem Wallet-JWK via crypto.subtle; id = base64url(SHA-256(signature)). Das SHA-384 hier ist als reines Arweave-Wire-Primitiv abgeschottet, niemals ein qub-Vertrauensprimitiv (§15 verzeichnet den Zaun; qubs Vertrauens-Hashing ist durchgängig SHA3-256).
Ein Codepfad bedient drei Konsumenten: bezahlte T3-Permanenz pro qub, die Fallback-Lösung bei Arweave-Nichtverfügbarkeit (das DataItem in die Warteschlange stellen, das R2-First-Ack ungeachtet dessen zurückgeben — dies schließt die aktuelle ARWEAVE_UNAVAILABLE-503-Sackgasse) und das Schreiben des AnchorBundle. Signaturverfahren (entschieden — §16.15 Q8): v1 signiert mit RSA-PSS (Signaturtyp 1) unter Wiederverwendung des bestehenden Arweave-Wallet-JWK-Mechanismus (null neue langlebige Schlüsselverwahrung, im Dienst der „ein Geheimnis weniger"-These); Ed25519 ist auf den §15-PQ-Migrationspfad aufgeschoben.
Der handgerollte Deep-Hash ist der risikoreichste Code mit der geringsten natürlichen Abdeckung in W5, sodass sein Gating nicht verhandelbar ist (§16.15 Q8):
- Die sprachübergreifende Fixture
tlog_v1.json(Rust + TS, das §14.5-wrapper_v1.json-Muster) deckt Deep-Hash, DataItem-Bytes + id, Leaf-Hashes, eine 5-Leaf-Wurzel + Audit-Pfad, einen STH-Hash, einen Aufnahmebeweis und einen Konsistenzbeweis ab — in beiden Richtungen, Signieren und Verifizieren (die Verifizierungsrichtung ist relevant, weil die lokale tx-→-tx_id-Prüfung aus §16.6 den Deep-Hash in jeden eigenständigen Verifizierer zieht, nicht nur in den Schreiber). - Ein einmaliger Interop-Round-Trip durch einen Referenz-ANS-104-Bundler, konsumiert nur als statische Testdaten — niemals als npm-Laufzeitabhängigkeit (die Nur-Web-Crypto- / Keine-Install-Scripts-Haltung bleibt bestehen).
- Der Deep-Hash-+-RSA-PSS-Pfad muss über dieselben
crypto.subtle-Primitive Round-Trippen, die die Produktion nutzt, sodass der hauseigene Encoder byte-kompatibel ist. - Ein laufender Post-Bündel-Akzeptanz-Monitor bestätigt, dass jedes Anker- / Fallback-DataItem tatsächlich Arweave-Akzeptanz erreicht, mit einem Alarm + Schutzschalter — denn der Deep-Hash bedient auch die Fallback-Warteschlange bei Arweave-Nichtverfügbarkeit, sodass eine stille Regression diese Warteschlange während genau jenes Ausfalls, den sie abdecken soll, mit netzwerk-abgelehnten Items füllen würde.
16.9 Aufnahme- und Konsistenzbeweise
Beide sind RFC 9162, SHA3-256, ausgeliefert als kanonisches CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (das exakte Leaf-CBOR — der Verifizierer berechnet leaf_hash selbst neu 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üsselliste, per Testvektor fixiert.
Eigenständige Verifizierung (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).
Der beweis-ausliefernde Speicher MUSS koordinatenbasiert geschlüsselt sein (entschieden — §16.15 Q7, blockierende Vorbedingung). Die Beweiserzeugung für kalte Leaves ist nur dann korrektheitsneutral, wenn das R2-Audit-Material ein persistenter Merkle-Knoten-Speicher ist, geschlüsselt nach absoluter Baum-Koordinate (level, index) — nicht nach Knoten-Deltas pro batch. Mit einem koordinatenbasiert geschlüsselten Speicher ist jeder (leaf i, size N)-Audit-Pfad eine Menge von O(log N) direkten R2-GETs ohne Neuberechnung über Bündelgrenzen hinweg; mit einem batch-basiert geschlüsselten Speicher ist er es nicht, was die Lücke im Speicher-Layout ist, die diese Entscheidung schließt. Die Leaf-Bodies sind ebenfalls inhaltsadressierbar nach seq. Ein W5-Testvektor MUSS ein Genesis-Ära-Kalt-Leaf gegen eine viel spätere Wurzel ausschließlich mit R2 + Arweave bei gelöschtem LogDO-Speicher beweisen, sodass die Reclamation-Sicherheits-Aussage in §16.13 untermauert statt behauptet ist. Die O(log N) sequenziellen R2-GETs gehören ausschließlich auf den asynchronen Beweis-Endpunkt — niemals auf den Versiegelungs-Hot-Path (§16.10) oder einen Per-Tick-Cron.
16.10 R2-First-Ack-Reihenfolge
Die POST /api/v1/upload-Sequenz wird zu:
- Front-Half-Gates (Auth, Validierung, Idempotenz-Shard-Key) — unverändert.
- Synchron
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)— die Haltbarkeitsuntergrenze; schließt außerdem W1s Precache-Race (vormals einctx.waitUntilnach dem Arweave-Submit). - Synchron
await LogDO.append(leaf)— ein In-Colo-DO-RPC; der einzige Schreiber weistseqzu, erweitert die Eintragskette und aktualisiert die Frontier. (RMW auf gemeinsamem Zustand → DO, niemals KV.) Dasappend-RPC tut ausschließlich das — dieO(batch)-Merkle-Bündel-Schließarbeit läuft außerhalb dieses RPC auf dem LogDO-Alarm, oder dieappend-p95 spitzt sich bei jederLOG_BATCH_MAX_LEAVES-ten Versiegelung auf. - Das Ack jetzt zurückgeben — mit der Versiegelungsquittung (signiert vom gepinnten Quittungsschlüssel, §16.6) und
{ tx_id, log_seq, anchor_status: "pending" }. Das mehrsekündige Arweave-Fan-out wird vom kritischen Pfad entfernt. - Ein
ctx.waitUntilstellt die aufgeschobene Arbeit in die Warteschlange: den Arweave-Submit pro qub (nun bestmöglich / bezahlt; bei Fehlschlag wird er in die Bundler-Fallback-Warteschlange geleitet, statt dem Benutzer ein 503 zu geben) plus die bestehenden provisorischen Meta-Schreibvorgänge. Bündel-Schließung und Verankerung laufen unabhängig vom LogDO-Alarm und vom täglichen Anker-Cron. Keinctx.waitUntilinnerhalb einer Schleife; der bestehende Idempotenz-Shard-Key bleibt erhalten.
Latenzbudget (entschieden — §16.15 Q7). Das < 300 ms-p95-Ziel ist ein gemessenes Launch-Gate, keine Annahme. Der ehrliche kritische Pfad ist die Front-Half-KV-Reads + ein R2-PUT + zwei serialisierte Durable Objects — der bestehende QuotaDO-Versiegelungs-Kontingent-Abzug und das neue LogDO-Append — sodass das Budget zwei In-Colo-DO-Round-Trips berücksichtigen muss, nicht einen. Liefern Sie einen LogDO-Latenzalarm aus, der den von QuotaDO spiegelt, und behandeln Sie eine p95-Regression als Release-Blocker.
16.11 Vertrauensmodell — die präzise Aussage, gestaffelt nach Leaf-Art
Für kind=0x01 (attestiert): „Dieser Inhalt — Body, der zu body_hash passt, identifiziert durch qub_id — wurde an Position seq in qubs Append-only-Log festgelegt und existierte nicht später als die Arweave-Blockzeit T; er war kryptografisch unlesbar bis zur drand-Runde R = unlock_round(unlock_at)." Dies ist das vollständige Tripel {tlock-Runden-Bindung + Merkle-Aufnahme + verankerte Wurzel}.
Für kind=0x02 (behauptet, der Standard): „Ein opaker Ciphertext mit Inhaltsadresse chash, der qub_id und unlock_at beansprucht, wurde an Position seq im Append-only-Log festgelegt und existierte nicht später als die Arweave-Blockzeit T." Die Runden- und Body-Legs werden von der bestehenden §11-.qub-Bündel-Verifizierung (qub_core::unlock) geliefert, nicht vom Log; was das Log über eine bloße Transaktion pro qub hinzufügt, ist manipulationssichere Reihenfolge, ein vertrauensloser Obergrenzen-Festlegungszeitpunkt und Äquivokationsresistenz.
Beide Aussagen schließen, gemäß §11, aus: Urheberschaft ohne sig_alg ≥ 0x01, Absicht und Timing unterhalb der Anker-Granularität. Keine von beiden lässt eine Aussage sich auf received_at stützen.
Aussagen-Obergrenze (bindende Launch-Einschränkung — entschieden §16.15 Q1). Für Free- / Standard-qubs (kind=0x02) ist die oben gestaffelte kind=0x02-Aussage die Obergrenze dessen, was irgendeine Produkt-, Marketing-, Bedingungs- oder Beweisdarstellungs-Oberfläche behaupten darf. Keine Oberfläche darf aussagen oder implizieren, dass das Log den Inhalt oder die Entsperrrunde eines Standard-qubs beweist — das Log beweist Reihenfolge + einen vertrauenslosen Obergrenzen-Festlegungszeitpunkt eines opaken Ciphertexts. Inhalts- und Rundenbeweis stammen ausschließlich aus der bestehenden §11-.qub-Bündel-Verifizierung, die log-unabhängig ist. Dies ist ein harter Launch-Blocker für die Texte, keine stilistische Präferenz; es ist die Entscheidung, die den byte-blinden Standard-Pfad ehrlich hält.
16.12 Versionierung und W3-Koordination
Es gibt keinen SealedQub-Wire-Bump und daher keinen Protokollversions-Bump (§12.1): Das Log ist ein Sidecar, der sich auf bestehende Felder und Bytes festlegt, sodass es nicht in den §12.2-Protokollversionsverlauf eingeht. 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 — analog zur §12.4-Wrapper-Versionsunabhängigkeit (der Wrapper trägt ein Versionsbyte unabhängig von der Protokollversion, und die Log-Versionen folgen derselben Trennung).
Die Beweis-Lieferung erfolgt standardmäßig per Fetch, mit einem optionalen Beilieger. Ein Beweis kann zum Versiegelungszeitpunkt nicht existieren (der Anker ist noch nicht geschrieben), sodass das versiegelungszeitliche .qub-Bündel beweisfrei bleibt. W7s Verifizierer ruft GET …/proof einmal ab, oder rekonstruiert im vollständig offline-Modus den Beweis aus dem öffentlichen AnchorBundle via einer Arweave-Abfrage auf Log-Id. Das .qub-Bündel (W7) reserviert ein optionales inclusion_proof-Mitglied — zum Versiegelungszeitpunkt abwesend, durch einen Nach-Anker-Re-Export für die Kaltarchivierung befüllt — nach demselben „optional, standardmäßig weggelassen, additiv"-Muster wie W3s drand_chain_version.
16.13 Aufbewahrung
Die Aufbewahrungsfenster für den offenen LogDO-Tail, das R2-Beweisauslieferungs-Substrat, die Anker-Schutzschalter-Zähler und die Bundler-Fallback-Warteschlange sind in docs/DATA-RETENTION.md spezifiziert. Prinzip: Der heiße Per-Eintrag-Speicher des Logs (LogDO) ist nach der Verankerung rückgewinnbar; sein Audit-Material — der koordinatenbasiert geschlüsselte (level, index)-Merkle-Knoten-Speicher + die seq-adressierten Leaf-Bodies (§16.9) + die Arweave-Anker — ist permanent. Das Rückgewinnen eines kalten Leaves aus dem DO invalidiert niemals einen ausgestellten Beweis, weil ein Beweis sich gegen jenen permanenten R2-Knoten-Speicher und den Arweave-Anker auflöst, nicht gegen das DO (und der §16.9-Testvektor mit gelöschtem DO beweist es).
16.14 Testvektoren
W5 liefert die sprachübergreifende Fixture tlog_v1.json (§16.8) plus ausgearbeitete Vektoren aus: ein kind=0x01- und ein kind=0x02-Leaf → leaf_hash; die kumulative 5-Leaf-Wurzel; einen Aufnahmebeweis; einen Konsistenzbeweis; ein AnchorBundle; und eine DataItem-id. Diese liegen neben den §14.5-Außenwrapper-Vektoren und werden sowohl von der Rust- (qub-core) als auch von der TypeScript- (Worker) Implementierung geübt.
16.15 Prüfungsentscheidungen (W5 — entschieden)
Die externe W5-Prüfung (ein adversarialer Design-Durchgang + Owner-Freigabe) ist abgeschlossen. Jede Entscheidung unten ist getroffen und im §16-Text oben reflektiert; die bindenden Launch-Einschränkungen sind am Ende erneut aufgeführt. Die Implementierung darf unter ihnen fortgesetzt werden.
- Leaf-Ehrlichkeit des Standard-Pfads (
kind=0x02) — ENTSCHIEDEN. Die Zwei-Leaf-Art-Aufteilung wie spezifiziert ausliefern:kind=0x02legt wederbody_hashnochdrand_roundfest. Kein*_body_hash-Feld auf dem byte-blinden Pfad (es wäre das am leichtesten lesbare falsche „verifiziert"-Signal für Integratoren und ist eine Annehmlichkeit, die §11 bereits aus dem Bündel liefert). Nicht Server-Versiegelung für log-attestierte qubs verlangen (das würde Klartext durch den Worker zwingen und den Crypto-Shredding-Moat zerstören). Jeder selbstbeschreibende Short-Circuit gehört in das.qub-Bündel / die Beweis-Hülle als verifizierer-neuberechnetes Feld, niemals als Leaf-Feld. Owner-bestätigte Aussagen-Obergrenze: §16.11. - Äquivokations- / Auslassungs-Rechenschaft — ENTSCHIEDEN. Der Versiegelungsquittungsschlüssel ist in
LogProfilegepinnt +anchor_owner-quer-signiert (schließt den vorherigen „kein Signierschlüssel"-Widerspruch; §16.6). Launch-Witness-Modell: gepinnte Quittung + Monitor-Methodik + Vorgänger-Ketten-Walk + doppelt selbstveröffentlichte Heads (qub-eigenes öffentliches GitHub-Repo, Social bestmöglich), vermarktet als erkennbar + quittiert, niemals unabhängig bezeugt. Ein echter Drittpartei-Witness ist auf einen §15-Governance-Bump aufgeschoben. - Gepinnte anchor-owner-Vertrauenswurzel + Rotation — ENTSCHIEDEN. Den
LogProfile-Pin übernehmen (§16.6); der Verifizierer prüftanchor_tx.owner == anchor_ownerund verifiziert die tx-Daten-→-tx_id-Bindung lokal. Die Rotations-Governance ist eine §15-Erweiterung zum Bauen (§15.3-Auslöser hinzugefügt), keine Wiederverwendung; geplante Rotationen quer-signieren, kompromittierungsgetriebene Rotationen fallen auf den §15-Bump zurück, wobei die Fork-Prüfung den Schaden begrenzt. - Blinding privater qubs — ENTSCHIEDEN. Das Blinding für private qubs beibehalten (
ref = SHA3-256(qub_id ‖ log_blind_secret)), rohequb_idfür öffentliche qubs (bereits §16.2.1),chashals eigenständige Bindung.log_blind_secretist ein Geheimnis der Korrelations-/Sybil-Klasse, nur vorwärts rotieren (§16.2.1). received_at— ENTSCHIEDEN. Es im Leaf behalten, festgelegt, aber explizit nicht-beweiskräftig; niemals als Beweis oder Streit-Bestätigung auf irgendeiner Oberfläche aufgeführt. Jede Monitor-Plausibilitätsprüfung vergleicht gegen die Arweave-BlockzeitT, nicht gegen das betreiber-kontrollierteanchored_at(§16.6).- Beweisbares Timing im Free-Tier — ENTSCHIEDEN (Owner-Freigabe). Die Haltbarkeit regrediert nicht; nur der beweisbare Obergrenzen-Festlegungszeitpunkt vergröbert sich zur Anker-Blockzeit. Free-Tier-Texte verwenden kein numerisches SLA („…beim nächsten Log-Anker hinzugefügt, typischerweise täglich"); der Beweis der genauen Stunde ist eine bezahlte T3-Eigenschaft, offengelegt an der Stufenvergleichs-Oberfläche + in den Bedingungen (§16.1).
- Kumulativer Baum auf Workers — ENTSCHIEDEN. Einzelner kumulativer RFC-9162-Baum + frontier-cachendes Einzel-Schreiber-LogDO (komfortabler Spielraum gegenüber der DO-Decke von ~1k Writes/Sek.; Merkle-of-Shard-Roots-Sharding aufschieben, bis man sich ihr nähert). Blockierende Vorbedingung: koordinatenbasiert geschlüsselter
(level, index)-R2-Knoten-Speicher + der Kalt-Leaf-Testvektor mit gelöschtem DO (§16.9);< 300 msist ein gemessenes Launch-Gate über zwei serialisierte DOs (§16.10). - ANS-104-Signaturverfahren + Deep-Hash — ENTSCHIEDEN. RSA-PSS (sig-Typ 1, unter Wiederverwendung des dedizierten Anker-Wallet-JWK); Ed25519 auf den §15-PQ-Pfad aufgeschoben. Der handgerollte SHA-384-Deep-Hash ist gegated über die Beide-Richtungen-cross-impl-Fixture, eine Nur-statisch-Referenz-Bundler-Interop-Prüfung, den geteilten
crypto.subtle-Round-Trip und den Post-Bündel-Arweave-Akzeptanz-Monitor (§16.8).
Bindende Launch-Einschränkungen (in Implementierung + Produkt-/Rechtsprüfung übernehmen):
- Aussagen-Obergrenze (Q1/Q6). Keine Oberfläche darf sagen, das Log beweise den Inhalt oder die Entsperrrunde eines Standard-qubs; die zulässige Aussage ist geordnet, manipulationssicher, mit einem vertrauenslosen Obergrenzen-Festlegungszeitpunkt. Free-Tier-Zeitstempel-Texte tragen keine numerische Latenz; der Beweis der genauen Stunde ist ausschließlich bezahltes T3.
- Witness-Ehrlichkeit (Q2). Äquivokation als erkennbar + quittiert vermarkten, niemals als unabhängig bezeugt.
- Quittungs- + Anker-Schlüssel (Q2/Q8). Der Quittungsschlüssel ist gepinnt + quer-signiert; das Anker-Wallet ist sein eigenes JWK, verschieden vom Upload-Wallet.
- Deep-Hash-Gate (Q8). Kein Anker oder T3-tx wird ausgeliefert, bis die Beide-Richtungen-Fixture + die Interop-Prüfung bestehen; der Akzeptanz-Monitor löst bei Fehlschlag paging aus.
- Speicher-Vorbedingung (Q7). Koordinatenbasiert geschlüsselter Knoten-Speicher + Kalt-Leaf-Vektor mit gelöschtem DO sind Voraussetzungen für die Garantie „Rückgewinnung invalidiert niemals einen Beweis".