qub Protokolspecifikation
qub er en protokol til kryptografiske tidsmæssige forpligtelser: et system til at forsegle ord til en fremtidig dato og senere verificere præcis, hvad der blev forseglet, hvilken drand-runde der styrede udgivelsen, og — når en lagringstransaktion eller et bevis fra transparensloggen er tilgængeligt — en uafhængigt tidsstemplet øvre grænse for, hvornår ciphertext blev forpligtet.
Tre primitiver får det til at fungere. drand er et decentraliseret tilfældigheds-beacon — afsløringsdatoen håndhæves kryptografisk frem for af qub's velvilje. Holdbart lager plus en transparenslog, der kun kan tilføjes til, bevarer forseglede bytes og forankrer samlede forpligtelser i permanent offentligt lager; den betalte T3-sti skriver også en individuel transaktion til permanent lager. ML-DSA-65 er en post-kvante digital signatur — når forfatterskab er aktiveret, bindes qub'en til et nøglepar, hvis hemmelighed aldrig forlader forfatterens enhed.
Tilsammen skaber disse primitiver en erklæring, der er tidslåst og med påviselig manipulation, valgfrit kan tilskrives og kan tidsstemples uafhængigt — en kvittering, hvis værdi vokser i takt med, at verdens evne til at fabrikere fortiden forbedres.
Resten af dette dokument er den normative specifikation, der kræves til interoperable implementeringer.
qub-protokolspecifikation
| Felt | Værdi |
|---|---|
| Dokumentudgivelse | 1.0.0 (protocol-v1.0.0) |
| Wire-protokol | 0x01 |
| Ydre indpakning | 0x01 |
| Ikrafttrædelsesdato | 2026-09-23 |
| Status | Aktuel |
| Gennemgået til og med | 2026-09-23 |
Dette dokument er den normative protokolspecifikation for qub-systemet til tidsmæssige forpligtelser. Det definerer datastrukturer, serialiseringsregler, udledningsformler og verifikationsprocedurer, der kræves til interoperable implementeringer.
Anvendelsesområde: protokollaget er bevidst sprogneutralt — qub-bodyen er uigennemsigtig klartekst / markdown / pact-bytes, og lokalitetsbevidst rendering er læserens ansvar (qub.social-webappen, <qub-embed>-iframen, MCP-klienter osv.).
1. Notation og konventioner
| Notation | Betydning |
|---|---|
u8, u64, i64 |
Heltal uden/med fortegn af angivet bitbredde |
[u8; N] |
Byte-array med fast længde på N bytes |
Vec<u8> |
Byte-array med variabel længde |
Option<T> |
Værdi af typen T, eller fraværende |
String |
UTF-8-tekststreng, NFC-normaliseret |
| ` | |
SHA3-256(x) |
NIST SHA3-256-hash af bytestrengen x (FIPS 202) |
ceil(x) |
Loftfunktion: mindste heltal ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | Mest betydende byte først |
Alle heltal i preimage-konstruktioner kodes som big-endian-byte-arrays med fast bredde (i64 → 8 bytes, u8 → 1 byte), medmindre andet er angivet.
Alle tidsstempler er Unix-sekunder i UTC.
2. Datastrukturer
2.1 ComposeQub (skaberens in-memory-tilstand)
Ikke serialiseret til CBOR. Ikke skrevet til permanent lager. Lokalt i skaberens app.
ComposeQub {
draft_id: [u8; 16], // Random, generated locally
created_at: i64, // Unix seconds UTC
unlock_at: Option<i64>, // Unix seconds UTC; None while composing
visibility: u8, // 0x00 = private; 0x01 = public
content_type: u8, // 0x01 text; 0x03 pact; 0x04 verdict
plaintext: Vec<u8>, // Raw body bytes (UTF-8 for text)
sender_label: Option<String>, // Display name; V2-signed when authorship is enabled
title: Option<String>, // Plaintext countdown title; bound via title_hash
reply_to: Option<[u8; 32]>,// Parent qub_id; V2-signed when authorship is enabled
outcome_at: Option<i64>, // Optional future judgment time; bound to qub_id
status: DraftStatus, // Composing | Sealed | Uploaded | Failed
}
2.2 QubEnvelope (dekrypteret payload)
Serialiseret med kanonisk CBOR (§3). Krypteret inde i SealedQub. Dette er strukturen, der beviser indholdsintegritet efter dekryptering.
QubEnvelope {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Derived (see §4.1)
content_type: u8, // Content type registry (see §6)
created_at: i64, // Unix seconds UTC
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // When reality renders judgment; bound to qub_id
sender_label: Option<String>, // Not in qub_id; V2-signed when authorship is enabled
reply_to: Option<[u8; 32]>,// Parent qub_id; not in qub_id; V2-signed when present
body: Vec<u8>, // UTF-8 text or canonical CBOR pact/verdict body
body_hash: [u8; 32], // SHA3-256(body) (see §4.2)
sig_alg: u8, // Signature algorithm (see §9.2)
author_signature: Option<Vec<u8>>, // Set when sig_alg != 0x00
author_pubkey: Option<Vec<u8>>, // Set when sig_alg != 0x00
cosigner_pubkey: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
cosigner_signature: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}
Baseline (usigneret tekst-qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; signatur- og medunderskriverfelter er fraværende. Andre valgfrie metadatafelter kan være til stede.
Andre v1-konfigurationer: content_type = 0x03 (pact-body, se §6.1); sig_alg = 0x01 (ML-DSA-65) med author_signature og author_pubkey til stede (se §9.3); cosigner_pubkey og cosigner_signature til stede sammen for medunderskrevne pagter (se §9.7); reply_to sat til den overordnede qubs qub_id for svarkæde-qubs (se §9.3 for konsekvenserne af signaturomfanget).
2.3 SealedQub (kanonisk wire-format)
Serialiseret med kanonisk CBOR (§3). Dette er det indre wire-artefakt: offentlig levering lagrer disse bytes uden wrapper, mens privat levering wrapper dem i OuterWrapper før lagring (§13).
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x00 = private/wrapped; 0x01 = public/bare
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // Surfaced on the verdict-watch CTA
// before reveal; mirrors QubEnvelope.outcome_at;
// bound to qub_id via the §4.1 preimage.
drand_chain_id: String, // drand chain hash (hex string)
drand_round: u64, // Target drand round number
drand_chain_version: Option<u8>, // W3 — chain-migration version. Absent / 0 = quicknet
// (the only chain today). Lets a future chain swap
// be expressed on the wire without a breaking format
// change. NOT part of the §4.1 qub_id preimage, so its
// addition never alters an existing qub's identity.
tlock_ciphertext: Vec<u8>, // tlock-encrypted QubEnvelope CBOR bytes
recipient_pubkey: Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
// but not interpreted by the v1 reference viewer
title: Option<String>, // Plaintext title surfaced on the viewer
// countdown before reveal. Bound to qub_id
// via title_hash (§4.1). 1..=100 NFC code
// points, no hostile/control code points.
}
2.4 RevealedQub (læserens applikationstilstand)
Ikke serialiseret til CBOR. Lokalt i læserens app. Konstrueret efter succesfuld dekryptering og verifikation.
RevealedQub {
qub_id: [u8; 32],
arweave_tx_id: String,
visibility: u8,
content_type: u8,
created_at: i64,
unlock_at: i64,
outcome_at: Option<i64>, // Carried from both wire layers; drives the verdict-watch block
drand_chain_id: String,
drand_round: u64,
sender_label: Option<String>,
title: Option<String>, // Carried forward from SealedQub.title
reply_to: Option<[u8; 32]>,
body: Vec<u8>,
body_hash: [u8; 32],
body_hash_verified: bool,
author_signature: Option<Vec<u8>>,
author_pubkey: Option<Vec<u8>>,
signature_verified: Option<bool>,
cosigner_pubkey: Option<Vec<u8>>,
cosigner_signature: Option<Vec<u8>>,
cosigner_verified: Option<bool>,
}
3. Kanonisk CBOR-profil
Al serialisering af SealedQub og QubEnvelope MUST overholde denne profil. To implementeringer, der får den samme logiske struktur, MUST producere identiske bytes.
3.1 Kodningsregler
| Regel | Specifikation |
|---|---|
| Standard | RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements) |
| Map-nøglerækkefølge | Sorteret efter kodet bytelængde først (kortere før længere), derefter leksikografisk (byte for byte for kodninger med samme længde) |
| Heltalskodning | Korteste form: 0–23 i startbyte; 24–255 i 2 bytes; 256–65535 i 3 bytes; osv. |
| Længdekodning | Kun definerede længder. Ingen arrays, maps, byte-strenge eller tekststrenge af ubestemt længde (additional info = 31 er forbudt). |
| Tags | Ingen CBOR-tags (major type 6 er forbudt). |
| Flydende komma | Ingen floats (major type 7-værdierne 0xF9–0xFB er forbudt). |
| Tekststrenge | UTF-8-kodede, NFC-normaliserede (Unicode Normalization Form C). |
| Byte-strenge | Rå bytes. Ingen base64-kodning på CBOR-laget. |
| Duplikerede nøgler | Afvis med fejl. Parsere MUST NOT stiltiende acceptere duplikerede map-nøgler. |
| Ukendte nøgler | Afvis med fejl. Parsere MUST NOT tolerere map-nøgler uden for typens kanoniske nøglesæt — to forskellige kanoniske byte-strenge må aldrig afkodes til samme værdi (encode(decode(x)) == x), og for signerede payloads ville en ekstra nøgle være skjult indhold, som begge signaturer forpligter sig til. Skemaudvikling sker gennem version, aldrig gennem ekstra nøgler. |
| Simple værdier | Kun true (0xF5), false (0xF4) og null (0xF6) er tilladt. |
| Valgfrie felter | Fraværende valgfrie felter udelades helt fra CBOR-mappet (kodes ikke som null). Tilstedeværende valgfrie felter inkluderes i sorteret nøglerækkefølge. |
3.2 Verificerede kanoniske nøglerækkefølger
Disse nøglerækkefølger er normative. Implementeringer MUST udsende nøgler i præcis denne rækkefølge. Debug-assertions SHOULD verificere rækkefølgen i ikke-release-builds.
QubEnvelope (version 0x01, usigneret, alle valgfrie felter fraværende):
"body" (5 encoded bytes)
"qub_id" (7 encoded bytes)
"sig_alg" (8 encoded bytes)
"version" (8 encoded bytes)
"reply_to" (9 encoded bytes) ← only if present (reply chains)
"body_hash" (10 encoded bytes)
"unlock_at" (10 encoded bytes)
"created_at" (11 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
Udledning af QubEnvelope-nøglerækkefølge: hver nøgle er en CBOR-tekststreng. Kodet længde = 1-byte-header + strenglængde (for strenge under 24 bytes). Sortér først efter samlet kodet længde, derefter leksikografisk for nøgler med samme længde.
SealedQub (version 0x01, offentlig, ingen modtager):
"title" (6 encoded bytes) ← only if present
"qub_id" (7 encoded bytes)
"version" (8 encoded bytes)
"unlock_at" (10 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (verdict mechanic)
"visibility" (11 encoded bytes)
"drand_round" (12 encoded bytes)
"drand_chain_id" (15 encoded bytes)
"recipient_pubkey" (17 encoded bytes) ← only if present
"tlock_ciphertext" (17 encoded bytes)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)
PactTerms (pact-body, content_type 0x03):
"notes" (6 encoded bytes) ← only if present
"terms" (6 encoded bytes)
"title" (6 encoded bytes)
"party_a" (8 encoded bytes)
"party_b" (8 encoded bytes)
"pact_version" (13 encoded bytes)
PactTerm (række i terms-arrayet):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier (party_a / party_b-map):
"label" (6 encoded bytes)
"contact" (8 encoded bytes) ← only if present
3.3 Byte-kodningsreference
| Type | CBOR-kodning | Eksempel |
|---|---|---|
| SHA3-256-hash (32 bytes) | 0x58 0x20 + 32 bytes |
body_hash, qub_id |
| Tidsstempler (i64) | Major type 0 (positiv) eller 1 (negativ), korteste kodning | Unix-sekunder |
| Version (u8, værdi 1) | 0x01 (enkelt byte) |
|
| Content type (u8, værdi 1) | 0x01 (enkelt byte) |
|
| sig_alg (u8, værdi 0) | 0x00 (enkelt byte) |
|
| ML-DSA-65-signatur (3.309 bytes) | 0x59 0x0C 0xED + 3.309 bytes |
author_signature, cosigner_signature |
| ML-DSA-65 offentlig nøgle (1.952 bytes) | 0x59 0x07 0xA0 + 1.952 bytes |
author_pubkey, cosigner_pubkey |
4. Normative udledninger
4.1 qub_id
qub_id identificerer entydigt en qub og binder QubEnvelope til SealedQub. Den udledes deterministisk fra envelope-indholdet.
qub_id = SHA3-256(
"QUB_ID_V2" || // domain separator: ASCII bytes [0x51 0x55 0x42 0x5F 0x49 0x44 0x5F 0x56 0x32] (9 bytes) + 0x00 padding (1 byte) = 10 bytes
version || // u8 (1 byte)
content_type || // u8 (1 byte)
created_at || // i64 big-endian (8 bytes)
unlock_at || // i64 big-endian (8 bytes)
outcome_at_or_zero || // i64 big-endian (8 bytes; 0 when outcome_at is absent)
drand_round || // u64 big-endian (8 bytes)
body_hash || // [u8; 32] (32 bytes)
title_hash // [u8; 32] (32 bytes; absent-sentinel = [0u8; 32])
)
// Total preimage: 108 bytes → 32-byte output
Kodning af domæneseparator: Strengen "QUB_ID_V2" er 9 ASCII-bytes. En enkelt 0x00-padding-byte tilføjes for at nå 10 bytes til justering. Implementeringer MUST bruge præcis disse 10 bytes: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
Kodning af outcome_at: En implementeringsrevision før udgivelsen udvidede preimage'et fra 92 til 100 bytes for at folde det valgfrie outcome_at-felt ind i bindingen. Fraværende outcome_at kodes som 8 nul-bytes; protokollens validatorer afviser outcome_at <= 0 overalt, så denne sentinel ikke kan kollidere med en legitim værdi. Se §3.2 (wire-format) og den in-tree tasks/verdict-uplift-plan.md for den verdict-mekanik, der motiverer dette felt.
Kodning af drand_round: En senere implementeringsrevision før udgivelsen udvidede preimage'et fra 100 til 108 bytes for at folde drand_round (den ønskede drand-runde, §4.3) ind i bindingen og ændrede domæneseparatoren til QUB_ID_V2. Dette binder timelock-runden ind i qub-identiteten: en gateway kan ikke ombinde ciphertext til en anden (f.eks. allerede passeret) runde end den, som det viste unlock_at indebærer. Oplåsningsproceduren (§8) verificerer derudover, at den runde, der er indbagt i tlock-ciphertext-stanzaen, matcher unlock_round(unlock_at), så det viste oplåsningstidspunkt påviseligt er den runde, der styrer dekrypteringen.
Egenskaber:
- Ændring af et felt, der bindes af preimage'et —
version,content_type,created_at,unlock_at,outcome_at,drand_round, de råbody-bytes (gennembody_hash) ellertitle(gennemtitle_hash) — producerer et andetqub_id. - qub_id beregnes før kryptering. Både QubEnvelope og SealedQub bærer samme qub_id. Læseren verificerer, at de matcher efter dekryptering.
qub_idafhænger ikke afsender_label,reply_to, signaturbytes eller offentlige signeringsnøgler. Under den nuværende V2-signeringskonstruktion autentificeressender_labelogreply_todog direkte afsender_label_hashogreply_to_or_zero(§9.3), når signaturer er til stede.- Ændring af SealedQub-
title(med alt andet fast) ændrerqub_idviatitle_hash. En gateway kan derfor ikke udskifte den klartekst-titel, der vises på nedtællingen, uden at ugyldiggøre qub-identiteten. - Ændring af SealedQub-
outcome_at(med alt andet fast) ændrerqub_idvia preimage'et. En gateway kan ikke udskifte den verdict-on-dato før afsløring, der vises på nedtællingen, uden at ugyldiggøre qub-identiteten. - Ændring af
drand_round(med alt andet fast) ændrerqub_idvia preimage'et. En gateway kan ikke ombinde timelock-ciphertext'en til en anden runde uden at ugyldiggøre qub-identiteten; kombineret med §8-kontrollen af stanza-runden ved oplåsning er det visteunlock_atden runde, der faktisk gater dekrypteringen.
4.2 body_hash
body_hash = SHA3-256(body)
Hvor body er den rå Vec<u8>-indholds-payload. For tekst-qubs er dette den UTF-8-kodede qub-body.
4.2.1 title_hash
title_hash = SHA3-256(NFC(title).utf8_bytes) if title is present
title_hash = [0u8; 32] if title is absent
Hvor title er den valgfrie klartekst-titel, der vises på læserens nedtælling før afsløring (se §3.2). NFC-normalisering køres på hash-tidspunktet, så digesten er stabil på tværs af visuelt ækvivalente kodepunktsekvenser. Sentinel-værdien med alle nuller er reserveret til det fraværende tilfælde; en tom streng afvises ved den kanoniske CBOR-grænse som en ikke-kanonisk kodning af "fraværende" (den kanoniske kodning udelader feltet helt).
4.3 Mapping fra oplåsning til runde
drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
| Parameter | Kilde | Eksempel |
|---|---|---|
unlock_at |
Brugervalgte Unix-sekunder UTC | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
drand chain info (genesis_time) |
1595431050 |
chain_period_seconds |
drand chain info (period) |
30 |
Dette er referencekortlægningen for tlock (drands CurrentRound). drand udgiver runde N på chain_genesis_time + (N - 1) * chain_period_seconds, så formlen vælger den runde, der er aktuel ved unlock_at — den runde, hvis signatur er den første, en læser, der ankommer ved unlock_at, kan bruge.
Justeringsegenskab (det tilfælde, der betyder noget i praksis): Når (unlock_at - chain_genesis_time) er præcis deleligt med chain_period_seconds, udgives den valgte rundes signatur præcis ved unlock_at, aldrig før. Det gælder altid for referencedistributionen: quicknets genesis-tid (1692803367) er delelig med dens periode på 3 sekunder, og referenceapps fastgør oplåsningstider til hele minutter. Ved et ikke-justeret unlock_at udgives den valgte rundes signatur strengt mindre end én periode før unlock_at — forpligtelsens tidsmæssige præcision er én beacon-periode.
Ældre kortlægning før udgivelsen og tolerance ved oplåsning: Den oprindelige kortlægning var ceil((unlock_at - chain_genesis_time) / chain_period_seconds), som — i det periodejusterede tilfælde ovenfor — valgte den runde, der blev udgivet en hel periode før unlock_at, så ciphertext kunne dekrypteres præcis én periode for tidligt. De to kortlægninger adskiller sig med præcis +1, når forskellen er delelig med perioden, og er ellers ens. Da drand_round er foldet ind i det uforanderlige qub_id-preimage (§4.1), kan artefakter, der er forseglet med den ældre kortlægning, ikke genudledes; verifikatorer, der udfører rundekrydskontrollen i §8 trin 6a, MUST derfor acceptere en lagret drand_round, der enten er lig med den udledte runde eller den udledte runde minus én (og MUST kræve, at tlock-stanzaens runde er præcis lig med den lagrede runde). Tolerancen udvider den tidligst mulige styresignatur med højst én periode. Tjenesten til faseinddeling af pagter anvender samme tolerance, når den genudleder en faseinddelt pagts qub_id (ved faseinddeling og ved medunderskrift): Hvis den aktuelle kortlægnings runde ikke genskaber det forpligtede qub_id, og forskellen er delelig med perioden, prøver den igen med runden minus én og forsegler den færdiggjorte pagt til den runde, som qub_id faktisk binder — aldrig blindt til den genberegnede runde, da artefaktet ellers permanent ikke ville kunne udledes.
Validering: unlock_at MUST være i fremtiden på forseglingstidspunktet. unlock_at MUST NOT være mere end 10 år fra created_at (for at begrænse risikoen ved langtidshorisont-drand-afhængighed; UI'et SHOULD advare for oplåsningsdatoer ud over 2 år).
5. Wire-format-newtypes
Wire-format-newtypes giver compile-time-sikkerhed mod at forveksle CBOR-bytes med JSON, rå klartekst eller andre byte-kodninger.
| Type | Indeholder | Produceret af | Forbrugt af |
|---|---|---|---|
SealedQubCbor |
Kanonisk CBOR af SealedQub | serialize_sealed_qub() |
Indre wire-artefakt; lagres uden wrapper ved offentlig levering eller wrappes ved privat levering og gendannes derefter af læseren |
QubEnvelopeCbor |
Kanonisk CBOR af QubEnvelope | serialize_qub_envelope() |
tlock encrypt-input, tlock decrypt-output |
5.1 Konstruktionsregler
// Production code — only through CBOR serialisers:
let sealed = SealedQubCbor::from_encoded(cbor_bytes);
// There is deliberately NO From<Vec<u8>> implementation.
// You cannot accidentally wrap arbitrary bytes in a wire format type.
// Accessing raw bytes:
let bytes: &[u8] = sealed.as_bytes();
let bytes: Vec<u8> = sealed.into_bytes();
5.2 Validering ved konstruktion
from_encoded() SHOULD validere, at input begynder med en gyldig CBOR-map-header. Fuld strukturel validering sker ved parse-tid, ikke konstruktionstid, for at undgå dobbelt-parsing.
6. Content-Type-register
| Værdi | Type | Maks. body-størrelse | Noter |
|---|---|---|---|
0x00 |
Reserveret (ugyldig) | — | MUST NOT bruges |
0x01 |
Almindelig tekst (UTF-8, begrænset Markdown) | 50 KB betalt / 10 KB gratis | Se §10 for rendering-regler. Opdelingen gratis / betalt håndhæves af upload-tjenesten; det hårde protokol-loft er 50 KB. |
0x02 |
Reserveret (fremtidig) | — | Allokeret til en fremtidig content type; ikke gyldig i v1. Læsere MUST afvise pr. reglen nedenfor. |
0x03 |
Pagt (bilateral aftale, CBOR-body) | 100 KB | Body er kanonisk CBOR PactTerms (§6.1). Medunderskriver-signering pr. §9.7. |
0x04 |
Dom (skaberens selvbedømmelse, CBOR-body) | 8 KB | Body er kanonisk CBOR VerdictBody (§6.2). Udsendes kun af system-siden verdict-intent. Den overordnede relation ligger på Parent-Tx-Id-Arweave-tagget, ikke på body'en. Se verdict-uplift-plan §3.4. |
Læsere MUST afvise ukendte content types med en tydelig brugersynlig fejl. Læsere MUST NOT forsøge at rendere ukendte typer som tekst.
6.1 Pact-body (content_type = 0x03)
En pact-body er den kanoniske CBOR-kodning af en PactTerms-værdi:
PactTerms {
pact_version: u8, // 0x01 for structured/v1
title: String, // ≤ 200 bytes, NFC
terms: Vec<PactTerm>, // ≤ 20 rows
party_a: PartyIdentifier, // initiator
party_b: PartyIdentifier, // counter-signer
notes: Option<String>, // ≤ 5,000 bytes, NFC; absent key if none
}
PactTerm { key: String (≤ 100 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }
Kanoniske CBOR-nøglerækkefølger for alle tre maps findes i §3.2. Den samlede serialiserede pact-CBOR MUST NOT overstige 100 KB (matcher §6).
Skema-diskriminator. Den første række i terms for en structured/v1-pagt MUST være { key: "pact_schema", value: "structured/v1" }. Rækker uden denne markør er "custom"-pagter og modtager ingen struktureret validering eller skema-bevidst rendering.
Indefrosne anerkendelsespladser. structured/v1-pagter bærer præcis fire anerkendelsesrækker under disse nøgler:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value for hver er én af otte indefrosne engelske strenge, valgt af (role, kind)-parret, hvor role ∈ { seller, buyer, provider, client } og kind ∈ { standard, capacity }. Selve strengene er normative protokoldata — begge parters ML-DSA-65-signaturer forpligter sig til de præcise bytes via body_hash. De er IKKE lokaliseret; den signerede body er sprogneutral. Enhver ordlydsændring kræver en ny skemaversion (structured/v2).
De otte strenge, deres opslag (acknowledgement_for(role, kind)) og rationalet for hver er fastlåst af referenceimplementeringen. Konforme implementeringer MUST udsende byte-identiske anerkendelsesværdier; golden-fixture SHA3-256-body-hash-tests, der dækker alle fire role-kombinationer, fanger enhver drift.
Læsernes visningsrækkefølge. Anerkendelsesstrengene indeholder formuleringer som "described above", der forudsætter, at beskrivelses-/omfangsrækkerne rendres før anerkendelserne. Læsere MUST rendre terms-arrayet i CBOR-rækkefølge; omorganisering bryder prosa-semantikken.
Modpartens kontakt. Når Party B's contact er en gyldig e-mailadresse, sender qub-upload-tjenesten automatisk en gennemgang/medunderskrivnings-invitation pr. e-mail på stage-tidspunktet og binder den endelige medunderskrift til verifikation af samme adresse (§9.7). Pagter, hvis Party B-kontakt er fraværende, kan stadig medunderskrives, men kun gennem en out-of-band-kanal — tjenesten afviser medunderskrivningsanmodninger, der ikke kan producere en matchende 15-minutters e-mail-verifikationsmarkør.
6.2 Verdict-body (content_type = 0x04)
En verdict-body er den kanoniske CBOR-kodning af en VerdictBody-værdi:
VerdictBody {
verdict_version: u8, // 0x01 for structured/v1
outcome: u8, // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
reflection: Option<String>, // ≤ 2,000 bytes NFC; "what changed, what did you learn"
evidence_url: Option<String>, // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}
Kanonisk CBOR-nøglerækkefølge:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
Den samlede serialiserede verdict-CBOR MUST NOT overstige 8 KB (matcher registret ovenfor).
Outcome-enum. Wire-byten er intent-neutral; de fire kategorier Right / Partial / Wrong / Unfalsifiable dækker resultatrummet for ethvert verdict-bærende intent. Per-intent-etiketter ("Ramte plet" / "Holdt det" / "Leveret" / "Bekræftet" for Right osv.) er en læser-side renderingssag, der opløses mod den overordnede qubs intent — wire-formatet forbliver sprog- og intent-neutralt. Værdier uden for 1..=4 MUST afvises ved dekodning.
Overordnet linkning. En verdict-qub bærer IKKE den overordnede reference i sin body. Den overordnede qubs Arweave-transaktions-id udsendes som Parent-Tx-Id-storage-tagget ved upload (§7 storage-tag-laget). Det holder body'en som en selvstændig signeret erklæring om selvbedømmelse; revisionskæden ("ret om hvad?") etableres via Arweave-tag-opslaget.
Sikkerhed for evidence-URL (normativ). Når evidence_url er til stede, MUST validatorer (compose-siden, wire-siden, Worker-edge) håndhæve:
- Kun HTTPS. Strengen MUST starte med byte-sekvensen
https://. Ethvert andet skema —http,ftp,javascript,data,fileosv. — afvises. - Længdeloft. ≤ 2.048 bytes (browserens praktiske URL-grænse).
- NFC + fjendtligt-kodepunkt-tjek. Samme regel som
titleogreflection— bidi-override- / nul-bredde- / tag-block- / BOM- / C0- / C1-kodepunkter afvises. Definitionen matcher Rustcrate::handle::contains_hostile_text_codepointog TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(hold dem synkroniseret). - Ingen mellemrum, ingen ASCII-kontroller. Mellemrum / DEL / sub-
0x20-bytes hvor som helst i URL'en afvises — lukker\n/\t-injektionsvektoren, som bidi-reglen ikke dækker. - Ikke-tomt hostsegment. Alt mellem
https://og første/,?eller#MUST være ikke-tomt.
Ingen server-side fetching. Workeren MUST NOT proxy, fetche eller forhåndsvise URL'en. Protokollen gemmer en streng; rendering sker læser-side med rel="nofollow noopener noreferrer" target="_blank" og en synlig vært vist sammen med linkteksten.
Reflektion. Valgfri skaber-skrevet refleksionstekst ("hvad ændrede sig, hvad lærte du"). Samme NFC + fjendtligt-kodepunkt-validering som title. Tomt / kun-mellemrum-input falder sammen til fraværende ved konstruktion.
Skemaversion. v1 understøtter kun verdict_version = 0x01. Fremtidige skemarevisioner bumper denne byte og lander sammen med en ny protokolversion pr. §12.
7. Forseglingsprotokol
Den komplette forseglingssekvens. Hvert trin er normativt.
1. User composes plaintext and metadata in ComposeQub.
2. Validate:
a. body is non-empty.
b. body size ≤ max for content_type and user tier (see §6).
c. unlock_at is in the future.
d. unlock_at ≤ created_at + 10 years.
e. content_type is a known, supported value.
f. visibility is 0x00 (private) or 0x01 (public).
3. Compute body_hash = SHA3-256(body).
4. Set created_at = current Unix seconds UTC.
5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
compute drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
(§4.3). (Computed here, before qub_id, because drand_round is bound into the
qub_id preimage—§4.1.)
6. Compute qub_id (see §4.1), folding in drand_round from step 5.
7. Construct QubEnvelope with all fields.
8. Serialise QubEnvelope using canonical CBOR → bytes B.
Assert: serialised output matches canonical profile (§3).
9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C and matching qub_id, version,
visibility, unlock_at, drand_chain_id, and drand_round.
11. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12. Select the delivery shape from visibility:
a. Private (0x00): generate K = 32 random bytes and N = 12 random bytes
using a CSPRNG. Compute W = wrap_sealed_qub(SealedQubCbor,
qub_id=qub_id, key=K, nonce=N) per §13. Upload payload = W.
b. Public (0x01): upload payload = bare SealedQubCbor; do not generate K.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit the selected upload payload to the qub upload service. For a private
browser seal, the service is byte-blind to the inner SealedQubCbor and never
receives K. The Builder `/api/v1/seal` route is an explicit exception: it
receives plaintext and caller-supplied K in memory, then persists neither.
16. Receive arweave_tx_id from the service. For private delivery, construct
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or the equivalent short-code
path). For public delivery, omit the fragment. Browsers do not transmit URL
fragments to servers, so K from the browser-seal path is not observed by
qub.social or any storage gateway.
Lager-tag-laget (out-of-band). qub-upload-tjenesten tilknytter et bevidst lille sæt lager-transaktionstags ved siden af den valgte upload-payload. Content-Type=application/octet-stream er normativt påkrævet. Referencetjenesten tilknytter desuden tre valgfrie tags, når skaberen vælger at vise dem: Intent (allowlist-valideret komponeringshensigt—announcement, thesis, prediction, letter, secret, commitment, proof eller den systemudsendte verdict), Author (skaberens §9.3-pubkey-fingeraftryk som 64-tegns lowercase hex) og Parent-Tx-Id (det overordnede qubs lager-transaktions-id for svarkæder, 43-tegns base64url).
Author-tagget er opt-in pr. qub: reference-skaber-appen tilknytter det kun, når brugeren eksplicit aktiverer offentlig tilskrivning på forseglingstidspunktet. Når kontakten er slået fra — standarden — skrives intet Author-tag, og qub'en er utilskrevet på kæden: intet i permanent lager forbinder uploaden til en skabers handle, e-mail eller andre qubs. Når kontakten er slået til, opløses Author-fingeraftrykket til skaberens valgte @handle via §9.5-attesteringskæden. Svarkæde-relationer og Intent er ikke-identificerende. Ved privat levering krypterer den ydre wrapper (§13) det genkendelige indre SealedQub-artefakt, så høst af lagrede wrappers og adgang til offentlige drand-signaturer stadig ikke er nok til at gendanne body'en uden K; lager-tags forbliver bevidst offentlige metadata.
Referencetjenesten tilknytter bevidst IKKE App-Name-, App-Version- eller Type-tags: ethvert sådant enkeltværdifilter ville returnere hele qub-korpusset til en GraphQL-forespørgsel, hvilket er uforeneligt med wrapperens body-only-fortrolighedsomfang.
En konform verifikator MUST NOT være afhængig af noget lager-tag til §11-tredjeparts-verifikation; body-hashet / qub_id / signatur forpligter kun til den indre CBOR, aldrig til tag-sættet.
8. Oplåsningsprotokol
Den komplette oplåsningssekvens. Hvert trin er normativt.
1. Viewer opens delivery URL. Extract arweave_tx_id from the path and retain
the optional URL fragment. Do not assume a missing fragment is an error:
public/bare delivery intentionally has no K.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch the stored bytes (with multi-gateway fallback).
3a. Resolve the delivery shape structurally:
a. If the bytes parse as OuterWrapper, require a well-formed 32-byte K in
the URL fragment, require wrapper version 0x01, and unwrap per §13.
Any missing/malformed K or AEAD failure is a terminal error.
b. Otherwise require the bytes to parse as bare SealedQubCbor; no K is
required. If neither shape parses, report an integrity error.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01), visibility is known, and the
delivery shape matches it (wrapped = 0x00 private; bare = 0x01 public).
Reject any mismatch or unknown value.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check. Recompute expected_round from
SealedQub.unlock_at per §4.3. Reject unless SealedQub.drand_round ==
expected_round OR SealedQub.drand_round == expected_round - 1 (the
legacy pre-release mapping—see §4.3), AND the round baked into the tlock
ciphertext stanza (read via the age/tlock header, no signature required)
== SealedQub.drand_round exactly. The stanza round is the one that
actually gates decryption; without this check a malicious creator could
bind the ciphertext to an already-past round while displaying a future
countdown, so anyone reading the stored bytes could decrypt before
unlock_at. Implementations with no chain identity (test mocks) skip this
check.
7. Once current time ≥ SealedQub.unlock_at:
a. Fetch drand round signature for SealedQub.drand_round from drand network.
b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
8. Parse B → QubEnvelope.
9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
Fail → integrity error.
12a. Verify: QubEnvelope.outcome_at == SealedQub.outcome_at (both absent, or
both present and equal). Fail → integrity error.
12b. Content re-derivation. Recompute qub_id per §4.1 from the decrypted
fields — (QubEnvelope.version, content_type, created_at, unlock_at,
outcome_at, SealedQub.drand_round, QubEnvelope.body_hash,
title_hash(SealedQub.title)) — and verify it equals SealedQub.qub_id.
Fail → integrity error. The pairwise checks in steps 10-12a only prove
the two layers agree with EACH OTHER; a forger who rewrites a bound
field consistently on both surfaces (a pre-reveal title swap, or a
post-round body swap with a recomputed body_hash re-encrypted to the
same round under the same qub_id) passes them all. Only re-deriving
the identity from content closes this.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact), 0x04 (verdict).
Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using the appropriate renderer (see §10 for text and §6 for pact/verdict).
17. Construct RevealedQub for display.
9. Forfatter-signering
9.1 Rationale
qubs opbevares i permanent lager. Forfatter-signaturer skal forblive uforfalskelige på ubestemt tid, hvilket er grunden til, at v1.0 bruger den post-kvante ML-DSA-65-ordning (FIPS 204) frem for en klassisk ordning, hvis sikkerhed kan degradere inden for qub'ens permanente levetid.
9.2 Algoritmeregister
sig_alg |
Ordning | Nøglestørrelse | Signaturstørrelse | Status |
|---|---|---|---|---|
0x00 |
Ingen signatur (usigneret) | — | — | Aktiv |
0x01 |
ML-DSA-65 (FIPS 204) | 1.952 bytes | 3.309 bytes | Aktiv |
0x02 |
Ed25519 | 32 bytes | 64 bytes | Reserveret konstant; ikke understøttet i protokol v1 |
Protokol-v1-læsere MUST afvise enhver værdi uden for {0x00, 0x01}, herunder den reserverede værdi 0x02. Reservationen forhindrer utilsigtet genbrug; den er ikke en aktivering. Aktivering kræver den styrede ændring, der beskrives i §15.
9.3 Konstruktion af signeret preimage
Der har eksisteret to preimage-versioner. Alle signaturer MUST bruge V2, og verifikatorer MUST kun acceptere V2. Den ældre V1-preimage (dokumenteret nedenfor til historisk reference) blev accepteret som en fallback udelukkende til verifikation under V2-migreringen; den fallback er nu udfaset, og en signatur, der kun bruger V1, afvises nu.
V2 (aktuel — produceret af al ny forfatter-signering og af begge signaturer i pagtens staging- og medunderskrivningsflow):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V2" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 || // u8 (1 byte): MUST be 0x00 in v1.x
sender_label_hash || // [u8; 32]: SHA3-256(NFC(sender_label)),
// or 32 zero bytes when absent
reply_to_or_zero // [u8; 32]: parent qub_id, or 32 zero
// bytes when absent
)
// Total preimage: 155 bytes → 32-byte hash
signature = Sign(author_secret_key, sig_input)
sender_label_hash følger samme fraværs-sentinel-konvention som title_hash (§4.2.1): 32 nul-bytes er ikke et gyldigt SHA3-256-output, så "fraværende" aldrig kan kollidere med en tilstedeværende etiket. Alle felter har fast bredde, så preimagen er entydig uden længdepræfikser.
V1 (ældre — UDFASET; produceres ikke længere og accepteres ikke længere ved verifikation):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V1" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 // u8 (1 byte): MUST be 0x00 in v1.0
)
// Total preimage: 91 bytes → 32-byte hash
V1-preimagen udelod sender_label og reply_to. Den blev accepteret som en fallback udelukkende til verifikation under migreringen til V2; den fallback er siden blevet udfaset — verifikatorer MUST kun acceptere V2-preimagen. Definitionen bevares her til historisk reference og for at forklare domæneseparatoren nedenfor. En signatur, der kun verificerer mod V1, MUST behandles som en verifikationsfejl.
Domæneseparatorer: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" er hver 17 ASCII-bytes ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Ingen padding. Den afvigende separator domæneadskiller de to konstruktioner, så en signatur over den ene preimage aldrig kan verificere som den anden.
org_id_present-byte: byten efter unlock_at MUST være 0x00. Referenceimplementeringen eksponerer denne som konstanten ORG_ID_PRESENT_INDIVIDUAL = 0x00 i crates/qub-core/src/signing.rs; læsere, der rekonstruerer sig_input til verifikation, MUST udsende samme byte.
Signaturomfang — hvad der er og ikke er dækket. V2-sig_input forpligter sig direkte til version, qub_id, body_hash, unlock_at, sender_label og reply_to (plus den faste domæneseparator og org_id_present-byten). qub_id er selv udledt af version, content_type, created_at, unlock_at, outcome_at, drand_round og body_hash via §4.1-preimagen, så enhver ændring til disse felter producerer et andet qub_id og ugyldiggør signaturen transitivt. Den autentificerede overflade er derfor:
| Felt | Autentificeret af signatur | Hvordan |
|---|---|---|
version |
✓ | Direkte input til sig_input |
qub_id |
✓ | Direkte input |
body_hash |
✓ | Direkte input |
unlock_at |
✓ | Direkte input |
sender_label |
✓ | Direkte input via sender_label_hash (V2-preimage — den eneste accepterede form) |
reply_to |
✓ | Direkte input via reply_to_or_zero (V2-preimage — den eneste accepterede form) |
content_type |
✓ | Transitivt, via qub_id-preimage |
created_at |
✓ | Transitivt, via qub_id-preimage |
outcome_at |
✓ | Transitivt, via qub_id-preimage |
drand_round |
✓ | Transitivt, via qub_id-preimage |
body |
✓ | Transitivt, via body_hash = SHA3-256(body) |
author_pubkey |
— (implicit) | Nøglen, der verificerede signaturen, er forfatteren pr. definition |
cosigner_pubkey / cosigner_signature |
— | Uafhængigt signeret over samme sig_input (se §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | Ydre SealedQub-felter, ikke inde i envelopen — dækket af deres egne strukturelle invarianter (runde-/kæde-konsistens), men ikke af forfattersignaturen. (drand_round er nu bundet transitivt via qub_id-preimage'et — se ovenfor.) |
Hvorfor V2 er den eneste accepterede preimage.
- Under den udfasede V1-preimage kunne en part med skriveadgang til de lagrede bytes udskifte
sender_label("Alice" → "Mallory") eller givereply_toen ny forælder — og genkryptere efter runden — uden at ugyldiggøre forfattersignaturen, fordi ingen af felterne var i den signerede preimage. V2 dækker begge, så enhver ændring til et af felterne vender verifikationen til "mislykket". Fordi verifikatorer nu kun accepterer V2, er denne udskiftning lukket for enhver signatur: en signatur, der ikke binder nogen af felterne (dvs. kun verificerer mod V1), afvises direkte i stedet for at blive accepteret via nedgradering. author_pubkeyinde i envelopen forbliver det sande identitetsanker — læsere MUST udlede vis-identiteten fraauthor_pubkey(via §9.5-attesteringslaget) i stedet for at stole påsender_label.
Implementeringer, der viser sender_label eller reply_to til slutbrugere, MUST fremhæve den autentificerede identitet (pubkey-fingeraftryk, attestering) som det primære identitetssignal, ikke etiketten.
9.4 Verifikationsprocedure
1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
only accepted form — the legacy V1 fallback is retired (§9.3), so a
signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."
Signaturverifikation er den dyreste operation (især ML-DSA-65). Den SHOULD udføres, efter alle billigere kontroller (hash, qub_id, unlock_at) er bestået.
9.5 Identitetsattesteringer
Identitetsattesteringer — mappingen af author_pubkey til menneskeligt genkendelige identitetspåstande som et qub-handle, en e-mailadresse, et socialt handle eller en passkey-legitimation — er en læser-side progressiv forbedring og er ikke påkrævet til signaturverifikation. Læsere, der opløser attesteringer til en vis-identitet, MUST anvende præcedensen:
handle > email > social > fingerprint
Fingeraftryks-fallbacken er lowercase-hex af SHA3-256(author_pubkey); den er altid tilgængelig for enhver signeret qub. Læsere MAY forkorte den til visning — reference-læseren renderer qub: efterfulgt af de første og sidste fire bytes (qub:<8 hex>…<8 hex>).
En konform verifikator kan gennemføre hver kontrol i §9.4 uden at kontakte qub-API'en, uden noget netværk ud over permanent lager og drand og uden noget server-side opslag. Attesteringsopløsning er et separat best-effort-trin, der kun udføres, efter signaturverifikation er lykkedes.
9.6 Størrelsesindvirkning
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| Signatur | 64 bytes | 3.309 bytes |
| Offentlig nøgle | 32 bytes | 1.952 bytes |
| I alt pr. qub | 96 bytes | 5.261 bytes |
| Lager-omkostningsdelta (ved ~$5/MB) | ~$0,0005 | ~$0,026 |
For en tekst-qub på 500–2.000 bytes tredobler ML-DSA-65 nogenlunde den lagrede størrelse. Den absolutte omkostning er ubetydelig.
9.7 Verifikation af medunderskriver (pact bilaterale aftaler)
For bilaterale aftaler (content_type = 0x03) beviser et andet signaturlag, at begge parter har samtykket til de samme vilkår.
Envelope-felter:
cosigner_pubkey: ML-DSA-65 offentlig nøgle for medunderskriveren (Party B).cosigner_signature: Signatur over sammesig_inputsom forfatteren (§9.3).
Begge felter MUST være til stede sammen eller begge fraværende. Hvis præcis ét er til stede, MUST læsere rapportere en integritetsfejl.
Verifikationsprocedure:
1. If cosigner_pubkey absent and cosigner_signature absent → no cosigner. Done.
2. If exactly one is present → integrity error.
3. Verify cosigner_pubkey != author_pubkey (prevent self-cosigning).
Fail → display "cosigner pubkey must differ from author."
4. Reconstruct sig_input using the same formula as §9.3 (V2 only — the
legacy V1 fallback is retired; all pact clients produce V2 signatures).
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."
Egenskaber:
- Medunderskriveren signerer det identiske
sig_inputsom forfatteren — begge parter forpligter sig til sammequb_id,body_hashogunlock_at(og, under V2, sammesender_label_hashogreply_to_or_zero). - For at lade medunderskriveren rekonstruere V2-preimagen uden adgang til de rå envelope-bytes håndhæver staging-tjenesten på stage-tidspunktet, at en pagt-envelopes
sender_labeler lig medpact_terms.party_a.label, og atreply_toer fraværende. Begge gælder for enhver reference-klient-pagt; envelopes, der overtræder dette, afvises ved staging. qub_id-udledning (§4.1) inkluderer IKKE medunderskriverfelter. At tilføje en medunderskriver til en eksisterende envelope ændrer ikkequb_id.- En pagt kan være kun forfattersigneret (énsidet forpligtelse), kun medunderskriver (usædvanligt) eller begge (fuldt bilateralt bevis).
E-mail-bindings-gate (operationel). Når en staget pagt bærer en Party B-e-mailkontakt (§6.1), MUST qub-upload-tjenesten afvise medunderskrivningsanmodningen, medmindre der eksisterer en kortvarig e-mail-verifikationsmarkør, der matcher både staging-id'et og den normaliserede e-mail-hash af denne kontakt. Markøren skrives af /api/v1/auth/verify, når magic-link-tokenet bærer et staging_id, og den verificerede adresse matcher SHA-256(normalise_email(party_b.contact)) — hvor normalise_email(addr) bevarer local-part-store/små-bogstaver og kun gør domænedelen lowercase (pr. RFC 5321 §2.3.11), og SHA-256 her er NIST FIPS 180-4-hashen (distinkt fra SHA3-256, der bruges i §4-udledninger) — og udløber 900 sekunder (15 minutter) efter udstedelse. Dette er en operationel anti-impersonation-gate, IKKE en del af det on-chain qub-bevis — en tredjeparts-verifikator, der genafspiller §11, behøver kun permanent lager og drand, uden noget server-side opslag. Markøren findes kun server-side og er aldrig en del af den signerede body.
Størrelsesindvirkning (ML-DSA-65 forfatter + medunderskriver):
| Komponent | Størrelse |
|---|---|
| Forfattersignatur | 3.309 bytes |
| Forfatterens offentlige nøgle | 1.952 bytes |
| Medunderskriversignatur | 3.309 bytes |
| Medunderskriverens offentlige nøgle | 1.952 bytes |
| Samlet krypto-overhead | 10.522 bytes |
| Lager-omkostningsdelta | ~$0,05 |
10. Markdown-rendering og sanering
Denne sektion er sikkerhedskritisk. Læseren renderer tekst-qubs (content_type = 0x01) ved hjælp af et begrænset Markdown-undersæt.
10.1 Tilladte elementer
- Overskrifter:
#til og med####(ingen#####eller######) - Fremhævning: fed (
**), kursiv (*), gennemstreget (~~) - Lister: ordnet (
1.) og uordnet (-,*) - Citatblokke (
>) - Kode: inline-spans (```) og fenced-blokke (`````)
- Vandrette streger (
---) - Linjeskift (to afsluttende mellemrum eller blank linje)
- Afsnit
10.2 Forbudte elementer
| Element | Håndtering |
|---|---|
Rå HTML (<div>, <script> osv.) |
Fjernes helt. Ingen HTML kommer igennem. |
Billeder () |
Fjernes. Billede-syntaks fjernes fra output. |
Links ([text](url)) |
URL'en gengives som synlig klartekst. Ikke auto-linket. Ikke klikbar uden eksplicit brugerhandling. |
| Farlige URL-skemaer | javascript:, data:, vbscript:, file: — fjernes. |
| Iframes, embeds, objects | Fjernes. |
| HTML-entiteter | Afkodes til vis-tegn, kun hvis de er sikre. |
10.3 Implementering
Implementeringer MUST bruge en streng allowlist-parser, ikke en blocklist. Den anbefalede tilgang:
- Parse Markdown med
pulldown-cmark(eller tilsvarende). - Gennemløb AST'et og kassér enhver knude, der ikke er i allowlisten (§10.1).
- For link-knuder: udsend URL'en som synlig tekst, ikke som et klikbart
<a>-element. - Konverter det filtrerede AST til en typed mellemrepræsentation (f.eks. en
MarkdownNode-enum med kun sikre varianter). Rå HTML er strukturelt ikke-repræsenterbar i denne IR. - Render fra den typed IR til mål-vislaget (f.eks. reaktive view-komponenter, DOM-knuder). Ingen HTML-strengsammenkædning eller
innerHTMLpå noget tidspunkt.
Blocklist-tilgange er skrøbelige, fordi nye Markdown-udvidelser eller parser-særheder kan introducere ufiltrerede elementer. Den typed-AST-tilgang gør XSS strukturelt umulig — der er ingen variant, der kan bære vilkårlig HTML.
10.4 Størrelses- og strukturgrænser
- Maksimal renderet overskriftsdybde:
####(H4).#####og dybere rendres som fed tekst. - Ingen grænse for antal afsnit (body-størrelsesgrænserne i §6 er begrænsningen).
- Fenced kodeblokke: ingen syntax-highlighting i MVP. Rendres som monospace-præformateret tekst.
11. Tredjeparts-verifikation
Enhver tredjepart, der har de lagrede bytes (og K for en privat/indpakket qub), kan verificere det kryptografiske artefakt uden qubs samarbejde. En uafhængigt tidsstemplet påstand om eksistens kræver desuden enten verificeret inklusion i permanent lager pr. qub eller et verificeret bevis fra §16-transparensloggen.
1. Obtain the stored bytes. For a private delivery, also obtain K from the
delivery-link fragment.
2. Resolve the delivery shape (§8 step 3a): unwrap OuterWrapper with K, or
accept bare SealedQubCbor. Require shape/visibility agreement.
3. Parse SealedQubCbor → SealedQub; validate protocol version, visibility,
content type, chain identity, and structural bounds.
4. Recompute expected_round from unlock_at (§4.3); require the stored round
(allowing the documented legacy minus-one case) and ciphertext-stanza round
to agree exactly as §8 step 6a specifies.
5. Obtain the drand signature for SealedQub.drand_round and verify its BLS
signature against the pinned chain public key.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify envelope/sealed equality for qub_id, unlock_at, and outcome_at.
10. Recompute qub_id from the decrypted fields, sealed drand_round, and title;
require it to equal the carried qub_id (§4.1).
11. If sig_alg != 0x00, verify the V2 author signature (§9.4). If cosigner
fields are present, verify their pairing, key separation, and signature
(§9.7).
12. For an existence-time claim, independently verify either:
a. the permanent-storage transaction's data-to-id binding, owner, block
inclusion, and block timestamp; or
b. a §16 inclusion proof through its pinned anchor and anchor block time.
13. Report each verified claim separately; do not collapse an absent storage
or anchor proof into a successful timing verdict.
Hvad verifikation beviser:
| Bevisinput | Hvad det fastslår |
|---|---|
| Gyldigt bundt / forseglet artefakt + drand-signatur | Den genskabte body matcher body_hash; de metadata, der er bundet i qub_id, er intakte; ciphertext er bundet til den angivne drand-runde; og runden er passeret. Dette fastslår ikke, hvornår ciphertext blev skabt. |
| Gyldig V2-forfatter-/medunderskriversignatur | Indehaveren eller indehaverne af de tilsvarende hemmelige nøgler autentificerede den signerede overflade i §9.3. |
| Uafhængigt verificeret lagringstransaktion pr. qub | Den nøjagtige lagrede ciphertext eksisterede senest på dens bloktidsstempel. |
| Gyldigt forankret bevis fra transparensloggen | Påstanden for den specifikke bladtype i §16.11, herunder en øvre grænse for forpligtelsestidspunktet fra forankringsblokken. |
Hvad verifikation IKKE beviser:
| Ikke-bevis | Hvorfor |
|---|---|
| Forfatterskab | sender_label er dekorativt. Uden sig_alg ≥ 0x01 kunne hvem som helst have forseglet dette indhold. |
| Hensigt | Artefaktet beviser bytes og kryptografiske relationer, ikke hvad skaberen subjektivt mente. |
Allerede eksisterende forpligtelse alene ud fra .qub |
En skaber kan samle et gyldigt bundt, efter den bundne runde er passeret. Den indlejrede drand-signatur beviser, at runden er passeret, ikke at ciphertext eksisterede før den. |
| Nøjagtigt tidspunkt for tryk på forseglingsknappen | Et tidsstempel fra en lager- eller forankringsblok er en uafhængigt verificerbar øvre grænse og kan ligge efter brugerens lokale handling. Påstande om sealed_at / received_at har ingen bevisværdi. |
Den implementerede transparenslog (§16) udvider verifikationen på tværs af qubs med manipulationspåviselig rækkefølge og et tillidsløst øvre forpligtelsestidspunkt (forankringsblokkens tidspunkt), afgrænset efter bladtype (§16.11). Den tilføjer ikke forfatterskab eller hensigt; for standardstien med byteblind upload beviser den ikke selv body_hash eller drand_round, som fortsat kommer fra artefaktkontrollerne.
12. Versionering og udgivelseskontrol
Dokumentudgivelser, den indre wire-protokol og den ydre indpakning er separate versionsrum. En afklaring, der kun vedrører dokumentet, ændrer derfor ikke stiltiende bytes, og en fremtidig wire-migrering kan ikke udgive sig for at være en redaktionel revision.
12.1 Dokumentudgivelsesversion
Denne specifikation bruger semantiske dokumentudgivelser (MAJOR.MINOR.PATCH) og et uforanderligt Git-tag med navnet protocol-v<release>.
- PATCH: Nøjagtigheds- eller redaktionel rettelse, der ikke ændrer konforme bytes eller påkrævet adfærd.
- MINOR: Bagudkompatibel normativ tilføjelse, ny registerpost eller nyt sidecar-format med uafhængig versionering.
- MAJOR: Inkompatibel normativ ændring, herunder en ny påkrævet fortolkning af wire-formatet.
Udgivelsesstatus er enten Udkast (endnu ikke normativ), Aktuel (det eneste anbefalede implementeringsmål) eller Erstattet (bevaret til historisk verifikation). Den uversionerede /protokol-rute viser den aktuelle udgivelse; udgivelsestagget bevarer dens nøjagtige kilde og alle lokaliteter, der blev udgivet med den. En ændring af status eller udgivelsesnummer kræver, at denne tabel og udgivelseshistorikken opdateres i den samme gennemgåede ændring.
| Dokumentudgivelse | Ikrafttrædelsesdato | Status | Wire-protokol | Indpakning | Kilde |
|---|---|---|---|---|---|
| 1.0.0 | 2026-09-23 | Aktuel | 0x01 |
0x01 |
protocol-v1.0.0 |
12.2 Protokolversion
version-feltet (u8) i både SealedQub og QubEnvelope identificerer den større protokolversion.
- Læsere MUST afvise ukendte større versioner med en tydelig fejl.
- Inden for en kendt større version MUST afkodere afvise ukendte map-nøgler (§3.1) — skemaudvikling sker ved at introducere en ny
version, ikke ved at tilføje nøgler, som eksisterende afkodere ville springe over. (Tidligere revisioner af denne specifikation tillod, at ukendte valgfrie felter blev tolereret; den klausul er trukket tilbage — den gjordeencode(decode(x))ikke-injektiv og åbnede en vektor for skjult signeret indhold på pact-payloads.) - Content types (
content_type) og signaturordninger (sig_alg) er version-gated: nye værdier kan kun introduceres sammen med en ny protokolversion eller eksplicit registeropdatering.
12.3 Protokolversionshistorik
| Version | Værdi | Beskrivelse |
|---|---|---|
| v1 | 0x01 |
Privat/indpakket og offentlig/bar levering; tekst- (0x01), pagt- (0x03) og doms-bodyer (0x04); ML-DSA-65 V2-signering med forfatter/medunderskriver; drand quicknet tlock; SHA3-256. |
12.4 Fremad-kompatibilitet
En v1-læser, der støder på en QubEnvelope med ukendte CBOR-map-nøgler (nøgler, der ikke er i §3.2-kanonisk rækkefølge), MUST afvise den med en afkodningsfejl (§3.1). Fremad-kompatibilitet hviler på version-feltet, ikke på nøgletolerance: fremtidige tilføjelser — selv mindre metadata — udgives under en ny version-værdi, som en v1-læser afviser med en tydelig "nyere protokol"-fejl i stedet for stiltiende at droppe indhold, som signaturerne forpligter sig til.
En v1-læser, der støder på sig_alg = 0x01 (ML-DSA-65), men som mangler ML-DSA-65-verifikationsunderstøttelse, SHOULD vise qub-indholdet med en "signatur til stede, men ikke verificerbar"-notits, ikke afvise qub'en helt. Reference-implementeringen i dag afviser enhver sig_alg-værdi ud over 0x00 og 0x01, fordi v1-registret ikke indeholder nogen anden gyldig algoritme — streng afvisning og soft-fail er observationelt identiske, indtil en tredje algoritme registreres. Soft-fail-adfærden ovenfor bliver bærende, så snart §9.2 optager en ny post, og reference-læseren vil blive opdateret til soft-fail på det tidspunkt.
12.5 Version af ydre indpakning
OuterWrapperen beskrevet i §13 bærer sin egen version-byte, uafhængig af SealedQub.version og QubEnvelope.version. De to versionsrum udvikler sig separat: en fremtidig post-kvante-sikker symmetrisk erstatning hæver wrapper-byten uden at røre den indre protokolversion, og en fremtidig protokollag-tilføjelse (f.eks. et nyt envelope-felt) hæver den indre version uden at røre wrapper-byten.
OUTER_WRAPPER_VERSION_* |
Værdi | Algoritme | Status |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM med 12-byte nonce, 16-byte authentication tag, AAD bundet til qub_id |
Aktiv til privat levering |
| — | 0x02–0xFF |
Reserveret | Fremtidig |
Læsere MUST afvise ukendte wrapper-versioner med en tydelig fejl. Protokollen holder bevidst wrapper-versionsrummet smalt, indtil en konkret migrationsdriver dukker op (f.eks. NIST-vejledning, der favoriserer en anden AEAD); en 0x02-plads vil blive allokeret i samme revision, der introducerer algoritmen.
13. Ydre krypteringswrapper
13.1 Rationale
Protokollagene (QubEnvelope → tlock → SealedQub) gør en forseglet qub tidslåst: bodyen er ulæselig indtil unlock_at, og drand-runde-signaturen er blevet udgivet. Efter oplåsning er runde-signaturen imidlertid offentlig, og den kanoniske CBOR-form af SealedQub er genkendelig, så en harvester, der indekserede permanent-lager-transaktioner, kunne masse-dekryptere hele qub-korpusset.
Ved privat levering lukker den ydre krypteringsindpakning den kanal ved at indsætte et yderligere symmetrisk AEAD-lag mellem den kanoniske SealedQubCbor og de lagrede bytes. I browserens forseglingssti lever 256-bit-nøglen K kun i leverings-URL'ens fragment og på brugerenheder; browsere sender ikke URL-fragmenter til servere, så qub.social, alle lagergateways og alle CDN'er foran dem er blinde over for K. Den lagrede repræsentation af en privat qub er derfor uigennemsigtig ciphertext, hvis klartekst ikke kan genskabes uden den URL, skaberen valgte at dele. Offentlig levering udelader bevidst dette lag (§13.8).
Nettoeffekt:
- Optællingsmodstand for privat levering.
OuterWrapperforbliver genkendelig struktureret CBOR—den er ikke bogstaveligt talt uskelnelig fra tilfældige bytes—men dens ciphertext-felt skjuler den genkendelige indreSealedQub-form. Den dokumenterede harvester-strategi om at "GraphQL-spørge efter bare qub-formede uploads og masse-dekryptere med offentlige drand-signaturer" ender ikke med klartekst uden K. - Crypto-shredding-privatlivsposition for browserens private standardflow. qub.social kan ikke dekryptere disse lagrede artefakter ud fra sine almindelige serverdata. Eksplicit genoprettelse, offentlig levering og betroet server-side-forsegling har andre, oplyste tillidsgrænser.
- To-trins-fortrolighedsstige. Standard = link-kontrolleret adgang (denne sektion). Modtager-krypterede private qubs (en reserveret Fase 2-funktion, endnu ikke specificeret) lægger sig ovenpå som det andet trin.
13.2 Lagdeling
plaintext body ← QubEnvelope.body (§2.2)
↓ canonical CBOR (§3)
envelope CBOR
↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
↓ canonical CBOR (§3)
SealedQubCbor bytes ← inner wire artifact
├─ public (visibility=0x01) ───────────────▶ stored directly (§13.8)
└─ private (visibility=0x00)
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12, this section)
OuterWrapper CBOR bytes ← stored private payload
Forsegling og oplåsning på protokollaget (§7, §8) er uændrede under wrapper-grænsen; wrapperen sættes på ved kaldstedet for seal() og fjernes ved kaldstedet for unlock().
13.3 OuterWrapper-datastruktur
struct OuterWrapper {
version: u8, // 0x01, see §12.5
qub_id: [u8; 32], // copied from inner SealedQub; AEAD AAD
nonce: [u8; 12], // 96-bit AEAD nonce
ciphertext: Vec<u8>, // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}
Feltinvarianter.
versionMUST være lig med0x01for v1.0 wrapper-bytes.qub_idMUST være lig medqub_id-feltet i den SealedQub, der genskabes efter unwrap. Både referencefunktionernewrap_sealed_qubogunwrap_sealed_qubparser den indre CBOR og håndhæver denne lighed direkte; AAD-bindingen får særskilt efterfølgende manipulation af wrapperens ydrequb_idtil at fejle autentificering.nonceMUST være 96 bits (12 bytes), frisk genereret af en CSPRNG ved hver wrap-operation. Genbrug af en nonce under samme nøgle tillader AEAD-nonce-reuse-angreb, der genskaber klarteksten; producenter MUST behandle (key,nonce)-par som engangs.ciphertexter AES-256-GCM-outputtet: ciphertext-bytes sammenkædet med den 16-byte authentication tag.ciphertext.len() == SealedQubCbor.len() + 16præcis.
CBOR-kodning. Kanonisk CBOR pr. §3, med samme nøgle-ordens-regel (sorteret efter kodet bytelængde stigende, derefter leksikografisk). De fire nøgler er:
| Nøgle | Kodede bytes | Rækkefølge |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
Den første byte af OuterWrapper-CBOR'en er derfor den definite-length-map-header for en 4-entry-map (0xA4).
13.4 AAD-binding til qub_id
Wrapperen binder qub_id som AEAD additional authenticated data. Dette er det bærende strukturelle forsvar mod tre angrebsklasser:
| Angreb | Forsvar |
|---|---|
Flytte ciphertext under et andet qub_id-felt i wrapperen |
AAD-mismatch → AEAD-autentificering fejler |
| Blande URL-fragmentet af qub A med de lagrede bytes fra qub B | Forkert nøgle (og uafhængigt bundet AAD) → AEAD-autentificering fejler |
Manipulere qub_id-feltet af wrapperen efter upload |
AAD-mismatch → AEAD-autentificering fejler |
At bære qub_id i wrapperens klartekst svækker ikke optællings-immuniteten på meningsfuld måde — qub_id er selv en SHA3-256-hash af §4.1-preimagen uden gendannelig preimage fra digesten, og en optæller, der allerede har høstet wrapper-bytene, lærer intet af det synlige qub_id, som de ikke kunne udlede af selve uploadens eksistens.
13.5 Wrap- og unwrap-algoritmer
wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
require K.len() == 32 and N.len() == 12 and Q.len() == 32
I := canonical_cbor_decode(S) as SealedQub
require I.qub_id == Q // reject mismatched caller AAD
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.5
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
S := canonical_cbor_decode(P) as SealedQub
require S.qub_id == O.qub_id // explicit inner/outer cross-check
return P // P is the validated SealedQubCbor
Fejl-tilstands-kollaps. Forkert K, forkert nonce, AAD-mismatch og manipuleret ciphertext producerer alle samme DECRYPT_FAILED-fejl. Dette er en bevidst AEAD-egenskab: at skelne fejl-tilstanden ville skabe en sidekanal, som en fjern-angriber kunne undersøge ved at sende deforme wrappere og time svaret. Reference-implementeringer MUST kollapse alle AEAD-fejl til en enkelt fejlform.
13.6 Nøglemateriale og distribution
Wrap-nøglen K er en 256-bit ensartet tilfældig værdi genereret pr. qub af en CSPRNG. Reference-implementeringerne henter den fra:
- WASM-skaber:
getrandom(WebCrypto underwasm_js-backend). - Server-side seal-API-opkalder: dennes lokale CSPRNG; opkalderen leverer og beholder
Ksomwrapper_key_b64url. Workeren brugerKi hukommelsen til wrapperen, men MUST NOT persistere den. Dette lader et idempotent genforsøg genskabe et redigeret svar ved hjælp af opkalderens beholdte kapabilitet i stedet for at afhænge af en engangshemmelighed genereret på serveren.
Distribution: K MUST kodes som URL-sikker base64 (RFC 4648 §5, ingen padding) og tilføjes leveringslinket som fragment-komponenten:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragmentet transmitteres aldrig til nogen server af en konform browser. Genoprettelseskanaler (server-side historik-indeks, opt-in e-mail-auto-send), der persisterer det fulde leveringslink — inklusive fragmentet — ud over brugerens enhed, er et eksplicit kompromis mod standard-crypto-shredding-positionen og MUST gates ved eksplicit brugersamtykke.
Tab af fragment. Hvis en bruger mister URL-fragmentet og ikke har nogen genoprettelseskanal, er qub'en ulæselig. Dette er designets bærende kompromis og MUST oplyses brugeren på forseglingstidspunktet. MVP'en styrker forseglingstids-oplysningen med eksplicit "gem dette link"-tekst og en verificeret-e-mail-genoprettelseskanal for brugere, der vælger det til.
13.7 Uden for denne sektions omfang
- Forfatter-signering (§9) er uændret: signaturer beregnes inde i den indre
QubEnvelopeog genskabes efter unwrap → tlock-dekryptering → CBOR-parse. - Kryptering med modtagerens offentlige nøgle (det reserverede
recipient_pubkey-felt) er en fremtidig funktion, der adskiller sig fra den nuværende private indpakningstilstand, som er styret af linkkapabilitet. - Det nuværende serversideflow til medunderskrift af pagter udsender offentlig/bar
SealedQubCbormed synlighed0x01; det kan ikke opfylde browserens model, hvor K holdes hemmelig, fordi den endelige forsegling sker efter serverformidlet medunderskrift. En fremtidig producent af private pagter kan bruge den samme indpakning, som er blind for den indre indholdstype.
13.8 Offentlige qubs (udeladelse af wrapper)
Den ydre wrapper er valgfri på leveringslaget. En skaber kan forsegle en qub som offentlig, og i så fald går den kanoniske SealedQubCbor direkte ind i lagerpipelinen, uden noget OuterWrapper-lag og uden nogen nøgle K:
SealedQubCbor bytes ──(public)──▶ stored as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored
En offentlig qub er tidslåst, men ikke link-gated: den forbliver ulæselig, indtil dens drand-runde udgives (tlock-laget er uændret), men efter oplåsning kan enhver, der har lagertransaktions-id'et, dekryptere den — der kræves intet URL-fragment, fordi der ikke findes nogen K. Dette er det bevidste kompromis for overflader, som serveren skal drive: afsløringsnotifikations-e-mails, fragmentløse oEmbed-/auto-embed-links og rigere SEO efter afsløring har alle brug for et link, der virker uden en hemmelighed, serveren aldrig besidder (§13.6). En privat qub kan stadig bruge den eksplicitte form <qub-embed src="full_delivery_url">, når udgiveren leverer den komplette kapabilitet med fragmentet.
Konsekvenser, en producent MUST tage højde for:
- Ingen optællings-immunitet. Offentlige qubs giver per konstruktion afkald på §13.1-optællings-immunitetsegenskaben. Reference-upload-tjenesten stempler et
Visibility: public-tag på permanent lager på dem (og kun dem), så de bevidst er opdagelige; private qubs bærer intet sådant tag og bevarer deres byte-uskelnelighed. - Klartekst-titel eksponeret på forseglingstidspunktet. §3.2-
title-feltet er klartekst inde iSealedQubCbor. Under wrapperen er det skjult, indtil en læser levererK; uden wrapperen er det verdenslæsbart i permanent lager fra uploadøjeblikket, før oplåsning. Konforme skaber-apps MUST oplyse om dette på forseglingstidspunktet. - Detektion er strukturel og krydskontrolleres. En konform læser/embed skelner de to lagrede former ved parse: bytes, der parser som
OuterWrapper, tager unwrap-med-K-vejen; bytes, der parser som barSealedQubCbor, accepteres direkte. Den genskabte indre værdi MUST stemme overens (0x00for indpakket/privat,0x01for bar/offentlig).qub_idbinder ikke synlighed, men de kanoniskeSealedQub-bytes bærer den, så de offentlige og private indre kodninger ikke er byte-identiske.
Privat (wrappet) forbliver standarden; offentlig er et eksplicit pr.-qub-skabervalg.
14. Testvektorer
14.1 qub_id-udledning
Input:
version = 0x01
content_type = 0x01
created_at = 1735689600 (2025-01-01 00:00:00 UTC)
unlock_at = 1736294400 (2025-01-08 00:00:00 UTC)
outcome_at = absent
drand_round = 4695446 (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, drand mainnet params §14.2)
body = "Hello, future." (UTF-8, 14 bytes)
title = absent
Intermediate:
body_hash = SHA3-256("Hello, future.")
= 76ab8b3f843c6ed4f2d0fd75b9f457b4
ad49dd4450f9c22723ae430e3af3211d
title_hash = [0u8; 32] (title absent — §4.2.1 sentinel)
Domain separator (10 bytes):
[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
Preimage (108 bytes—current protocol v1):
domain_separator || // 10 bytes
0x01 || // version
0x01 || // content_type
0x0000000067748580 || // created_at as i64 big-endian (1735689600)
0x00000000677DC000 || // unlock_at as i64 big-endian (1736294400)
0x0000000000000000 || // outcome_at_or_zero (outcome_at absent)
0x000000000047A596 || // drand_round as u64 big-endian (4695446)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 4a84e3dfaec32954949c30073f8e6506
fd3204c1bb97f9162b81c7587afe412e
Implementeringer MUST producere identiske body_hash- og qub_id-værdier for dette input. Denne testvektor SHOULD være den første enhedstest, der skrives. De kanoniske værdier ovenfor blev beregnet af referenceimplementeringen og MUST matche bit for bit. Historiske prototypelayouts før lancering (ingen aktive qubs afhang af de første to) brugte 92 bytes før outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) og 100 bytes efter tilføjelsen af outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Det nuværende 108-byte-layout tilføjede derefter drand_round og domæneseparatoren QUB_ID_V2. En tidlig 108-byte-vektor brugte den ældre ceil-rundekortlægning (drand_round = 4695445) og producerede 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4 — stadig et gyldigt qub_id for det rundeinput, mens eksemplet ovenfor følger kortlægningen til den aktuelle runde i §4.3.
14.2 Mapping fra oplåsning til runde
Input:
unlock_at = 1735689600
chain_genesis_time = 1595431050
chain_period_seconds = 30
Calculation:
(1735689600 - 1595431050) / 30 = 4675285.0
floor(4675285.0) + 1 = 4675286
drand_round = 4675286
Runde 4675286 udgives på 1595431050 + (4675286 - 1) * 30 = 1735689600 — præcis ved unlock_at, aldrig før. (Den ældre ceil-kortlægning før udgivelsen gav 4675285, som blev udgivet på 1735689570 — 30 sekunder for tidligt; verifikatorer accepterer den ældre runde i henhold til §4.3.)
14.3 Kanonisk CBOR-rundtur
Implementeringer MUST verificere, at serialize(parse(serialize(qub))) == serialize(qub) for alle gyldige input. Dette er en property-test, ikke en enkelt vektor.
14.4 PactTerms CBOR (content_type 0x03)
Input:
pact_version = 1
title = "Scooter deposit"
terms = [
{ key: "Item", value: "Honda Metropolitan scooter" },
{ key: "Price", value: "$100" },
{ key: "Deposit", value: "$10" }
]
party_a = { label: "Alice" }
party_b = { label: "Bob", contact: "bob@example.com" }
notes = absent
Canonical CBOR key order (PactTerms):
"notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)
Canonical CBOR key order (PactTerm):
"key"(4) < "value"(6)
Canonical CBOR key order (PartyIdentifier):
"label"(6) < "contact"(8)
De kanoniske CBOR-bytes og SHA3-256 body_hash beregnes af reference-implementeringen. Implementeringer MUST producere byte-identisk CBOR for dette input.
Implementeringer MUST også verificere, at serialize(parse(serialize(pact))) == serialize(pact) for alle gyldige PactTerms-input (property-test).
14.5 Krydssprogs-vektorer for ydre wrapper
Den ydre wrapper (§13) har en separat kanonisk fixture på crates/qub-core/tests/vectors/wrapper_v1.json. Hvert tilfælde fastsætter en (key, nonce, qub_id, sealed_cbor)-tuple som uigennemsigtige hex-input og asserter et bestemt expected_wrapper_hex-output. Begge reference-implementeringer forbruger samme JSON-fil:
- Rust:
crates/qub-core/tests/wrapper_vectors.rs(cargo test -p qub-core --test wrapper_vectors). - TypeScript:
workers/api/src/crypto/__tests__/wrapper.test.ts(npm test).
Fixturen fastlåser i øjeblikket tre wrapper-tilfælde på lavt niveau. De tester deterministisk OuterWrapper-kodning og AEAD-interoperabilitet uafhængigt af leveringsformsinvarianten i §13.8; navnlig gør det historiske navn basic-text-public og dets indre visibility = 0x01 ikke de wrappede bytes til en konform offentlig levering. En producent SKAL stadig lagre offentlige indre bytes uden wrapper og kun wrappe private (0x00) indre bytes.
| Tilfælde | Dækning |
|---|---|
basic-text-public |
Historisk navn på fixture på lavt niveau. Mindste realistiske SealedQub-form uden valgfrie felter; tester kun wrapper-bytes og er ikke en konform §13.8-lagret levering. |
with-recipient-pubkey |
SealedQub med recipient_pubkey sat (reserveret fremtidig sti). Afprøver et andet indre CBOR-nøglesæt; fixture-indholdets egen forskel giver uafhængigt et andet qub_id (recipient_pubkey selv indgår ikke i §4.1-præbilledet). |
longer-body |
~4 KiB body — udøver multi-byte CBOR-længde-præfikser inde i både den indre envelope og den ydre ciphertext. |
Implementeringer MUST producere byte-identisk expected_wrapper_hex for de optagne input. Regenerering af fixturen kræver QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors og er forbeholdt bevidste formatændringer.
15. Krypto-profil-governance (fremtid)
Denne sektion er informativ for v1 og bliver normativ første gang en anden algoritme træder ind i nogen af qubs kryptografiske primitiver.
15.1 Nuværende stilling
Protokol v1 binder præcis én algoritme pr. primitiv:
- Signatur: ML-DSA-65 (
sig_alg = 0x01; 1952-byte offentlig nøgle, 3309-byte signatur) og usigneret (sig_alg = 0x00). Kodebasen reserverer0x02til Ed25519, men protokol v1 aktiverer den ikke; en v1-verifikator MUST afvise enhversig_alguden for{0x00, 0x01}. - Timelock: kun drand quicknet — chain hash, offentlig nøgle, genesis-tid og periode er faste netværksparametre båret af reference-
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) ogconfig/drand-endpoints.json. - Ydre wrapper: kun AES-256-GCM v1 (§13).
Verifikatorer hårdkoder i øjeblikket nøgle- og signaturlængder pr. aktiv primitiv. Bytene for sig_alg og indpakningsversion er udtrykkelige vælgere, men v1 udfører ingen in-band-forhandling og tillader kun de aktive værdier ovenfor.
15.2 Tilsigtet form
Når en anden algoritme træder ind i protokollen, vil verifikatoren blive konfigureret til en navngivet CryptoProfile (f.eks. ExqubV1), der opregner det præcise sæt af tilladte værdier pr. primitiv — sig_algs, drand-kæder, wrapper-versioner, content types. Profilen er fastsat ved verifikationstid, aldrig forhandlet in-band. Enhver værdi uden for den aktive profil afvises.
Dette garanterer, at tilføjelse af ML-DSA-87 eller aktivering af Ed25519 ikke retroaktivt kan svække eksisterende verifikator-konfigurationer: en v1-verifikator forbliver en v1-verifikator, selv efter at en v2-profil er udgivet.
15.3 Udløsningsbetingelser
Forfremme §15 til normativ status, når et af følgende foreslås:
- En anden
sig_alg-byte (Ed25519-aktivering, ML-DSA-87 eller enhver ny post i §9-registret). - En anden drand-kæde i produktionsbrug.
- En anden ydre wrapper-version.
- En rotation af transparensloggens tillidsrod —
LogProfile.anchor_owner-adressen eller den fastgjorte offentlige kvitteringsnøgle (§16.6).LogProfileføjes til §15.2-profiloverfladen som en styret primitiv: En rotation er et signeretLogProfile-versionsskift, der leveres i en verifikatoropdatering (planlagte rotationer krydssignerer udgående → indgående; kompromisdrevne rotationer kan ikke og er afhængige af dette versionsskift, mens kontrollen af forrige ankers forgrening afgrænser den midlertidige skade). Transparensloggens versionsrum (LOG_VERSION,ANCHOR_FORMAT) udvikler sig som uafhængige søskende, præcis som indpakningsversionen i §12.5 er uafhængig af protokolversionen.
Indtil da er §15 en pladsholder, der fastsætter migrationsformen, så fremtidige PR'er lander mod et kendt mål i stedet for at gen-litigere forhandlingsoverfladen fra bunden.
16. Transparenslog og holdbarhedsniveauer (implementeret — gennemgang færdig)
Status. Dette afsnit er implementeret (W5/UP-B1, Trin 1–8), med producent- og tillidsrod-omfang angivet her. Wire-formaterne, hashing og verifikationsstier er aktive: de kerne-Merkle + canonical-CBOR-typer (
qub-core), TypeScript-spejlet + ANS-104 bundler (workers/api/src/crypto/), den enlige-skriverLogDO+ koordinat-nøglede R2 node-lager,/uploadlog-append forsøg, de daglige anchor + bundler-drain crons,GET /api/v1/qub/:tx_id/proof(inklusion) ogGET /api/v1/log/consistency(RFC 9162) proof-endpoints, den typede inklusionsbevis båret i.qubbundlet (§17.5), den native ANS-104 anchor-verifikator (tools/qub-verify), og det dobbelte self-published-heads hook (§16.6). En succesfuld/uploader altid R2-holdbar, men dækkes kun af loggen, nårLOG_DOer konfigureret, og inline append lykkes; først da indeholder dens responslog_seq,receiptoganchor_status. HvisRECEIPT_SKmangler eller er ugyldig, er den kvitteringssig_b64urltom og leverer ingen ikke-afviselighed. De nuværende/sealog pact-publicationsstier planlægger individuelle Arweave-transaktioner, men føjer ikke et log-blad til. Ingen kode udfører i øjeblikket/uploadkommentarens foreslåede senere afstemning efter en append-fejl. W5 ekstern gennemgang er færdig: §16.15 registrerer designbeslutninger og lanceringsbegrænsninger, men disse begrænsninger udvider ikke producentdækningen som anført. Tre tillids-/implementeringspunkter forbliver låst: (a) den dedikerede anchor-wallet (ANCHOR_JWK;LogProfile.anchor_ownerer stadig[0xAB; 32]placeholder); (b) kvitteringssigneringsnøglen og tilhørende offentlige nøgle-pin (RECEIPT_SKer valgfri ogLogProfile.receipt_pubkeyer i øjeblikket tom); og (c) self-published-heads GitHub repository + token (§16.6). Indtil anchor/profile pins er provisioneret, rapporterer en selvstændig verifikator proof-status ærligt i stedet for at hævde fuldt forankret, pinned verifikation. Designet er strengt additivt, og der er ingen ændring afSealedQub/QubEnvelopewire-formatet.
16.1 Begrundelse og holdbarhedsniveauer
De nuværende publikationsstier adskiller kvittering fra Arweave-bekræftelse: de udleder og signerer en individuel transaktion, gemmer artefaktet og den nøjagtige indsendelsesstatus i R2, og poster derefter asynkront. Transparensloggen tilføjer et uafhængigt forankret ordre-lag for det delmængde af generelle /upload forespørgsler, hvis LogDO append lykkes:
| Niveau | Navn | Garanti | Hvornår |
|---|---|---|---|
| T1 | R2-første synkroniserede kvittering | Holdbarhedsgulv — forseglede bytes og den nøjagtige publiceringsstatus skrives til holdbar lagring, før succes returneres. | Implementeret på tværs af nuværende publiceringsveje. |
| T2 | Batchet gennemsigtighedslog-indførelse | Kun vedhæftning, manipulationsbevisende forpligtelse + total rækkefølge, når inkluderet og forankret. | Nuværende producent: vellykkede LogDO vedhæftninger fra /upload; svar bærer kvitteringstuplet. Ikke universelt. |
| T3 | Per-qub Arweave permanens | En individuel Arweave-transaktion for quben. | Forberedt for hver accepteret publicering og lagt ud asynkront; den præcise signerede transaktion forbliver i den drænable udgående kasse, indtil den er leveret. |
Niveauerne beskriver forskellige bevis- og holdbarhedsegenskaber, ikke den nuværende kommercielle plan. Den nuværende kode planlægger stadig en individuel Arweave-transaktion for hver accepteret publicering; den udsætter ikke T3 kun som et betalt opsalg. API-nøgle/konto kvotetakster forbliver separate applikationskontroller.
Holdbarhedsaftesthed. T1-skrivningen er synkron, så et vellykket svar etablerer applikationsniveau holdbarhed uden at vente på en Arweave-gateway. Det etablerer ikke selv et uafhængigt tidsstempel. En bekræftet individuel transaktion leverer dens bloktidens øvre grænse. For et svar, der bærer den komplette T2-kvitteringstuple, kan den næste bekræftede forankring levere logbeviset beskrevet nedenfor. Hvis tuplet mangler, kan ingen overflade antyde, at denne qub allerede er i gennemsigtighedsloggen. Forankring og publiceringslatens har ingen numerisk SLA på protokolniveau.
16.2 LogLeaf-struktur (to forpligtede former)
En logpost er en LogLeaf, kodet som håndskrevet kanonisk CBOR under §3.1-profilen (definit længde, ingen tags, ingen flydende tal, kortest-form heltal, NFC-tekst, valgfrie felter udeladt når fraværende, nøgler ordnet efter kodet byte-længde stigende og derefter bytevis). Den §3.1 parse → re-encode → compare kanoniske sikring anvendes på kodningsstien før hashing (ikke kun ved dekodning), så to implementeringer ikke kan være uenige om leaf-bytes gennem forskelle i heltalsbredde eller nøgleorden. Alle heltal er u8 / u64 / i64; alle digest-værdier er 32-byte bytestrings (bstr[32]). En lagret Arweave-transaktions-id er en rå 32-byte SHA-256 digest båret som bstr[32], aldrig en base64url tekststræng (matcher §3.3).
Leafen har to former valgt af en kind byte, fordi den generelle upload-sti er byte-blind: POST /api/v1/upload behandler bevidst begge accepterede payload-former som uigennemtrængelige og modtager qub_id og unlock_at kun som utiltro klientpåstande. På den standard private sti, body_hash, drand_round, created_at og drand_chain_version er yderligere skjult inde i §13 ydre wrapper, hvis nøgle Arbejderen aldrig besidder. Typesystemet definerer også en attestet form for en producent, der selv afleder body_hash / drand_round. Den aktuelle /seal rute har disse værdier, men kalder ikke LogDO, så produktionen udsender i øjeblikket kun erklærede (0x02) leaves fra vellykkede generelle-upload append. Opdelingen holder hver forpligtet værdi ærlig uden at foregive, at den attesterede producent er koblet:
| Nøgle | Enc. len | Type | Presence | Meaning |
|---|---|---|---|---|
seq |
4 | u64 |
krævet | globalt 0-baseret bladindeks; den position, inklusionsbeviset forpligter sig til. |
kind |
5 | u8 |
krævet | 0x01 attested-capable (defineret, ikke aktuelt udsendt) eller 0x02 asserted (client-seal / byte-blind upload). |
ref |
4 | bstr[32] |
krævet | Leaf-reference-id. Bekræftet → rå qub_id. Hævdet → blindet-id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
6 | bstr[32] |
krævet | indholdsadresse SHA3-256(stored_bytes) — det ene indholdsbånd, som Worker altid ærligt kan beregne på begge veje. |
unlock_at |
10 | i64 |
krævet | Kopieret (attesteret) eller asserted (asserted); valideret > 0 før det går ind i bladet. |
received_at |
12 | i64 |
krævet | Arbejdervægur ved R2-ak. Ikke-bevismæssigt (operator-asserted; §16.6). Til stede for selvbeskrivelse, aldrig bevis. Valideret > 0. |
body_hash |
10 | bstr[32] |
kind=0x01 kun |
udeladt på 0x02 — Arbejderen mangler det under §13. |
drand_round |
12 | u64 |
kind=0x01 kun |
udeladt på 0x02. |
Et kind=0x02-blad committer bevidst hverken body_hash eller drand_round: det bekræfter forpligtelsen og rækkefølgen af en uigennemsigtig chiffertekst ved indholdsadresse chash, og hævder qub_id og unlock_at — ikke dens klartekst eller runde. Klartekst-/rundbenene for en assertet qub kommer fra den eksisterende §11 .qub-bundle-verifikation, ikke fra logarmens (§16.11). drand_chain_version er ikke i bladet (det er inde i wrapperen på standardstien); kædegranularitet lever på ankeret (§16.7). Encoder-disciplin: afvis en ref eller chash med kun nul, og afvis ikke-positive unlock_at / received_at, hvilket spejler den outcome_at > 0 sentinel guard i cbor.rs.
16.2.1 Privat-qub blindning
Loggen må ikke blive det opregningsorakel, som §13 ydre omslag eksisterer for at forhindre (§13.1). For en privat (indpakket) qub forpligter asserted bladet den blindede identifikator SHA3-256(qub_id ‖ log_blind_secret), hvor log_blind_secret er en server-opbevaret hemmelighed, og udelader body_hash. En tredjepart kan ikke knytte et sådant blad til en specifik qub_id; qub'ens indehaver, som har leverings-URL'en og derfor qub_id, kan genberegne blinden for at bekræfte deres egen inklusion. En offentlig qub (allerede opregnelig, allerede bærende Visibility: public Arweave-tag i henhold til §13.8) forpligter den rå qub_id. Dette er det eneste sted, hvor stand-alone verificerbarhed bevidst giver efter for en bærende privatlivsinvariant; den stand-alone binding for private qubs er chash (§16.9).
log_blind_secret varetægt (løsning — §16.15 Q4). Blinden beskytter blad-uafhængighed, ikke almindelig tekst fortrolighed (det §13 omslag håndterer det uafhængigt). Ved et log_blind_secret kompromis, for enhver qub_id som angriberen allerede har eller kan rekonstruere (hver qub, hvis bundle/URL den har, plus enhver lav-entropi eller offentlig qub_id) genberegner den bladet ref i én hash og knytter det — dette er direkte kobling af en kendt population, ikke en brute-force over et ukendt rum. Klassificer log_blind_secret som en korrelation/Sybil-grads hemmelighed i samme varetægtsniveau som andre serverhemmeligheder, og roter fremad kun (en rotation re-blinder fremtidige blade; den kan ikke bagudrettet afkoble allerede-ankrede).
16.3 Blad og Node Hashing
RFC 6962 §2.1 domæne-separeret hashing med SHA-256 erstattet af SHA3-256:
leaf_hash = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree = SHA3-256("") // defined but never anchored
Domænepræfiks bytes 0x02 (indgangskæde, §16.4) og 0x03 (STH hash, §16.6) er reserveret og adskilt fra disse. De er enkeltbytes og kan derfor ikke kollidere med de eksisterende 10-bytes ASCII domæne-separatorer (QUB_ID_V2, osv.). Træet er RFC 6962 venstre-fyldt ubalanceret træ (hver indvendig split ved den største potens af to, der er strengt mindre end deltræets bladantal), hvilket lader inklusions- og konsistensbeviser dele én audit-path algoritme. Referencen spec indeholder eksplicit pseudokode for venstre/højre afledning og fastsætter en ikekraft-af-to (5-blad) testvektor, så højre-kants-forfremmelses-tilfælde — som en 4-blad vektor skjuler — bliver afprøvet.
16.4 Hash-kædning (intern)
LogDO opretholder en intern indgangskæde kun for crash-konsistens. Den er aldrig offentliggjort og aldrig verifierer-rettet:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
Den offentliggjorte append-only autoritet er kumulative Merkle-rod + dens anker (§16.5–16.6), aldrig den rå rækkefølge, hvori operatoren tilfældigvis betjener blade: kæden genberegnes for enhver ordre, der serveres, så kun den forankrede rod stifter den kanoniske position.
16.5 Kumulativt Merkle-træ og batching
Der er ét stadigt voksende RFC 6962-træ over alle blade i seq rækkefølge — ikke isolerede per-batch-træer. (En carry-leaf-kædet per-batch-konstruktion blev afvist: det er ikke en ægte præfiksrelation, så dets "konsistensbeviser" er uholdbare.) Det kumulative træ giver ægte RFC 9162-konsistensbeviser og lader et enkelt nyligt anker bevise inklusion for enhver ældre qub.
Det LogDO Holdbare Objekt er single writer (blockConcurrencyWhile, spejling QuotaDO / EntitlementDO) — tilføjelse til en delt log er læs-modificere-skriv på delt tilstand og SKAL derfor igennem en DO, aldrig KV. Den cacher træets højrekantsgrænse (O(log n) hashes), så lukning af en batch er O(batch). En batch er mængden af blade, der er forankret sammen; dens implementerede triggere er et tree_size fremskridt på mindst LOG_BATCH_MAX_LEAVES (standard 4096), alder når ankerkadencen eller en eksplicit administrativ/cron-force-close. root_i er den kumulative Merkle Tree Hash over blade 0 .. tree_size_i.
16.6 Skiltet træhoved via Arweave Anchor
Arweave-ankertransaktionen er det signerede træhoved og erstatter en operatorsignatur for selve træhovedet: det daglige anker behøver ingen qub-nøgle, fordi Arweave tx owner er signaturen. Moat-tesen gælder — det uforanderlige substrat, ikke en qub-holdt hemmelighed, bærer den forankrede rod.
Log-designet kræver én varm succesfuld tilføjelse kvitteringsnøgle (§16.10), fastgjort i LogProfile og krydssigneret af anchor_owner. Den nuværende implementering har ikke afsluttet denne tillidsrod-provisionering: RECEIPT_SK er valgfri, en fraværende/ugyldig nøgle giver sig_b64url: "", og den kompilerede LogProfile.receipt_pubkey er tom. En sådan kvittering kan beskrive det tilføjede blad, men er ikke en ikke-omstridelig signeret kvittering. Den stærkere designpåstand gælder kun, efter at en verifikatorfrigivelse har fastgjort den matchende offentlige nøgle, og ankerejeren krydsunderskriver den. Et publiceringssvar uden den komplette kvitteringstuple fremsætter ingen log-accept-påstand; en med en tom signatur fremsætter et append-position-krav, men ingen signatur-verifikationskrav.
SignedTreeHead er kanonisk CBOR (nøgler efter kodet længde): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (prior sth_hash; genesis = 32 nul bytes), log_id:bstr[32], first_seq:u64, anchored_at:i64. Dens hash er sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Fastgjort tilltro rod. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). En overensstemmende verifier skal KRÆVE anchor_tx.owner == LogProfile.anchor_owner, hvor anchor_owner (og kvitteringsnøglens offentlige nøgle) er indlejret i qub_core som LogProfile — sammen med de allerede eksisterende quicknet-konstanter i DrandTimelockProvider::quicknet() — og distribueret med verifieringsprogrammet. Verifieren skal også verificere Arweave-transaktionen data → tx_id binding lokalt i stedet for at stole på et gateway /raw/-svar. Dette lukker hullet for rogue-wallet dobbeltspil: "forankret på Arweave" er meningsløst, indtil verifieren fastslår hvilken wallet.
Rotation er en §15 styrings-udvidelse, ikke en genbrug (løst — §16.15 Q3). §15.2's profilsurface opregner i øjeblikket kun sig_algs / drand-kæder / wrapper-versioner / indholdstyper, og §15.3's triggers lister ingen af disse — LogProfile / anchor_owner er ikke endnu på §15's surface. Rotationstyring skal derfor bygges: §15.3 udvides (nedenfor) til at tilføje LogProfile-triggeren, og en rotation er et signeret LogProfile-boost, der leveres i en verifieringsopdatering. En planlagt rotation bærer en outgoing → incoming cross-signatur; en kompromismotiveret rotation kan ikke (den outgoing nøgle er netop da ikke pålidelig/tilgængelig) og falder tilbage på §15-styret boost, med prev-anchor fork-checken (nedenfor) til at begrænse skaden midlertidigt.
Dobbeltspilsvindue (førsteklasses tillidsparameter). Et blad er kun dobbeltspilresistent, når dets dækkende forankring er Arweave-bekræftet. Vinduet er received_at → anchor confirmation (kadence + Arweave-finalitet, uden garanti for protokol-latenstid). Før tiltro-rod-provisioning leverer den nuværende implementering qub's operationelle integritet plus enhver tilgængelig usigneret appendmetadata; den leverer ikke den planlagte non-repudiation-garanti. Tre ansvarlighedsartefakter definerer det færdige design (vidnemodellen er §16.15 Q2's løsning):
Forseglet kvittering (afhængig af provisioning) — SCT-analogen returneres, når en upload-logtilføjelse lykkes (§16.10). Den bliver kun ikke-benægtelig, når
sig_b64urlikke er tom og det matchende offentlige nøgle/anker-ejer-forhold er fastgjort i verificeringssystemet. Den nuværende tomme produktionsprofil-pin kan ikke understøtte denne afgørelse. Denne kontrol gælder ikke for et udeladt kvitteringstuple eller en usigneret kvittering.Offentliggjort monitoreringsmetodologi + tidligere-kæde-gennemgang — kæden af ankeren
prevgennemgås fra head→genesis; en forgrening (to ankre ved sammesizemed forskelligeroot, eller en brudtprev) er offentliggørbar bevis for dårlig opførsel. Tvetydighedsdetektion er en erklæret operationel forpligtelse, ikke en tavs antagelse.Dobbelt selv-offentliggjorte heads — hvert nyt head
{sth_hash, tree_size}postes til et dedikeret qub-ejet offentligt, kun-tilføjende GitHub-repository (benbærende manipulations-sikkert selv-offentliggørelsesled), med et socialt opslag kun som best-effort korroboration. En mislykket posting SKAL sende alarm (ikke fejle stille). Implementeret (Fase 8) sompublishHead-hooken på anker-cron (workers/api/src/utils/heads-publish.ts): enPUTtil indholds-API’en uden enshaer kun-tilføjende (en422betyder, at head allerede er publiceret, aldrig en overskrivning); valgfrit / deployment-styret påPUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}og inaktiv indtil repository er provisioneret. En hård GitHub-fejl sender alarm viahealth_alert-kanalen og øger en holdbar fejlameter (m:tlog_publish_head_fail); selve Arweave-ankeret ruller aldrig tilbage ved en publiceringsfejl. "Ikke fejle stille" sikres af den holdbare måling — som ops-operatører SKAL overvåge via dashboard-alert — selvom best-effort email alarm ikke kan leveres. To ærlige begrænsninger følger af "anker-på-fremdrift" (cron publicerer kun, når størrelsen skrider frem): en midlertidig GitHub-fejl efterlader et hul i rækken af publicerede-heads for den størrelse — afgrænset, ikke stille (den sender alarm), og da hvert head forpligter et superset-træ, broder et §16.9 konsistensbevis hullet; afgørende, at dette konsistensbevis beregnes fra det autoritative Arweave-ankrede træ, ikke fra GitHub-overfladen, så et GitHub-hul svækker aldrig verificerbarheden. En catch-up backfill, der udfylder huller i publicerede-heads, er en udsat forbedring.
Ærlighedsbundet (bindende begrænsning). Fordi qub kontrollerer begge planlagte postingsflader, er dette selvpubliceret, ikke uafhængigt vidnet. Ingen produkt-, marketing- eller juridisk flade må hævde, at loggen er "uafhængigt vidnet". Efter kvitterings-/profil-/hovedgate er provisioneret, er den tilladte påstand, at tvetydighed kan opdages, og et succesfuldt underskrevet append efterlader en ikke-benægtelig kvittering. Før da er denne påstand ikke tilgængelig. En virkelig uafhængig tredjepartsvidne udsættes til en fremtidig §15 regeringsopgradering.
received_at er operatør-asserted, og ingen påstand må støtte sig på den — den fremvises aldrig som bevis eller som bekræftelse af tvist på nogen produkt-/juridisk-/API-/bevisafbildningsflade. Arweave-anckerbloktid T er den eneste tillidsløse tidsstempel (en øvre grænse for "logget af"). Enhver overvågningskontrol af received_at SKAL sammenlignes med T, ikke med operatørkontrolleret anchored_at STH-felt; en sådan kontrol er kun en beskyttelse mod en ærlig operatørs urfejl, ikke en ansvarlighedskontrol mod en ondsindet operatør (§16.15 Q5).
16.7 Ankestransaktionsformat og -frekvens
AnchorBundle er den kanoniske CBOR Arweave transaktionskrop, skrevet via §16.8 bundler: ver:u8, sth:bstr (kanoniske SignedTreeHead bytes), prev_anchor:bstr (forrige ankstransaktions-id rå bytes; udeladt ved genesis), chain_hash:tstr (den gældende drand-kæde — quicknet), og batchens leaf-CBOR strøm i seq rækkefølge, så ankeret er selvstændigt: en overvåger gendanner root fra kroppen uden qub-afhængighed. (Hvis leaf-strømmen bliver stor ved høj volumen, kan en fremtidig revision alene referere til et leaf-interval; bemærket, ikke adopteret i v1.)
Arweave-tags er bevidst optællelige — loggen er ment at blive fundet, i modsætning til private qubs: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tags er utroværdige hints; CBOR-kroppen er den eneste autoritet.
Cadence: dagligt som standard, genbesøges med volumen (størrelsesudløseren forkorter automatisk den effektive cadence under belastning). Den nuværende producer implementerer ikke et betalt-segl force-anchor hook. Anchor wallet er dedikeret og lavhastighed, adskilt fra upload-wallet — den SKAL være sin egen JWK (en særskilt nøgle, ikke en logisk rolle på upload-walleten), så et kompromis af upload-walleten ikke kan forfalske ankre — med et fast dagligt anchor-transaction-budget. Custody-holdningen er klart angivet: en snæver hot key med en stram circuit breaker og lav saldo, ikke "cold" — en wallet, der auto-signerer dagligt, kan ikke være cold, og specifikationen foregiver ikke andet.
16.8 ANS-104 Bundler
En internt udviklet ANS-104 DataItem-koder og deep-hash signer, cirka 300 linjer, kun Web Crypto, ingen npm-afhængigheder (begge Turbo SDK'er fejler 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
Signing er Arweave deepHash — en rekursiv SHA-384 digest (Arweaves wire-krav, crypto.subtle.digest("SHA-384")) over ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — derefter RSA-PSS over deep hash med wallet JWK via crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 her er karantæneret som en Arweave-wire-only primitive, aldrig en qub trust primitive (§15 registrerer hegnet; qub trust hashing er SHA3-256 overalt).
ANS-104 kodevejen tjener den udsatte fallback/drain maskineri og skriver AnchorBundle DataItems. Den almindelige publiceringsvej opretter først en præcis signeret Arweave-transaktion og beholder dens JSON i en holdbar outbox; direkte posting er en latensoptimering, og drain-vejen prøver den samme transaktion igen, før bundler fallback anvendes. Signaturordning (afklaret — §16.15 Q8): v1 signerer med RSA-PSS (signaturtype 1) genbruger den eksisterende Arweave wallet JWK-mekanisme (ingen nye langtidsholdbare nøgler, der tjener "one fewer secret" tesen); Ed25519 er udsat til §15 PQ-migrationsvejen.
Den håndlavede deep hash er den højrisiko, lavest-naturlig-dækning kode i W5, så dens gating er ikke-forhandlingsbar (§16.15 Q8):
- Kryds-sprogs-fiksturen
tlog_v1.json(Rust + TS, §14.5wrapper_v1.json-mønsteret) dækker deep-hash, DataItem bytes + id, leaf hashes, en 5-leaf root + audit path, en STH-hash, et inklusionsbevis og et konsistensbevis — i både sign- og verify-retningerne (verify-retningen er vigtig, fordi §16.6's lokale tx → tx_id-tjek trækker deep hash ind i hver standalone verifier, ikke kun skribenten). - En engangsinterop-retur gennem en reference ANS-104 bundler, brugt som statisk testdata kun — aldrig som et npm runtime-afhængighed (Web-Crypto-only / ingen-installation-scripts holdning gælder).
- Deep-hash + RSA-PSS-stien skal kunne rundrejse gennem de samme
crypto.subtle-primitiver, som produktionen bruger, så den interne encoder er byte-kompatibel. - En løbende post-bundle accept-monitor bekræfter, at hver anchor / fallback DataItem faktisk opnår Arweave-accept, med alarm + circuit breaker — fordi deep hash også tjener Arweave-utilgængelighed fallback-køen, så en stille regression ville fylde køen med netværksafviste elementer under præcis den nedetid, den er lavet til at dække.
16.9 Inklusions- og konsistensbeviser
Begge er RFC 9162, SHA3-256, serveret som kanonisk CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (det præcise leaf CBOR — verifikatoren genberegner leaf_hash selv og stoler aldrig på en leveret hash), index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.
ConsistencyProof — GET /api/v1/log/consistency?first=<size_a>&second=<size_b>: ver:u8, first_size:u64, second_size:u64, first_root:bstr[32], second_root:bstr[32], nodes:[bstr[32]], first_anchor, second_anchor. En enkelt utvetydig nøgelliste, fastlagt af testvektor.
Standalone verification (ingen qub-server, udvider §11):
1. Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2. Read leaf.kind.
3a. kind=0x01 (attested):
assert leaf.ref == qub_id
assert leaf.body_hash == SHA3-256(body)
assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
assert leaf.ref == SHA3-256(qub_id || blind) // holder supplies blind
OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4. Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
using index/size; require derived root == proof.root.
5. Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
(do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
LogProfile.anchor_owner.
6. Parse AnchorBundle; require committed root == proof.root and size ==
proof.size; read the Arweave block time T.
7. Emit the claim scoped by leaf.kind (§16.11).
Proof-serving storage SKAL være koordinatnøglet (løst — §16.15 Q7, blokeringsforudsætning). Koldblad-proof-generering er korrekthedsneutral kun hvis R2-revisionsmaterialet er en persistent Merkle-node-butik nøglet af absolut trækoordinat (level, index) — ikke pr. batch nodedeltas. Med en koordinatnøglebutik er enhver (leaf i, size N) revisionssti et sæt af O(log N) direkte R2 GET'er med ingen recomputation over batchgrænser; med en batch-nøgle butik er den ikke, hvilket er det lager-layout-gab, denne resolution lukker. Bladkroppe kan ligeledes adresseres af indhold via seq. En W5-testvektor SKAL bevise et genesis-æraens koldblad mod en meget senere rod ved kun at bruge R2 + Arweave med LogDO-lagringen slettet, så kravet om genopretning af sikkerhed i §16.13 understøttes snarere end hævdes. De O(log N) sekventielle R2 GET'er hører kun til det asynkrone bevisendepunkt — aldrig på den forseglede hot-sti (§16.10) eller en per-tick cron.
16.10 R2-First Ack-bestilling
Den implementerede POST /api/v1/upload-sekvens er:
- Forreste halvdel af porte (autentificering, validering, idempotens shard-nøgle) — uændret.
- Oprette, tag og underskriv den præcise individuelle Arweave-transaktion. Dette udledes
tx_idlokalt, selvom transaktionsoprettelse kan hente belønnings-/ankermetadata fra en gateway. En forberedelsesfejl får stadig anmodningen til at fejle før bekræftelsen. - Skriv synkront det valgte artefakt ved
qub-cache/<tx_id>og bevare de stabile oprettelses-operation/udbakke-poster. Disse er holdbarheds- og genprøvingsgulv; fejl før afvikling returnerer 503. - Når
LOG_DOkonfigureres, forsøgerLogDO.append(leaf)synkront. Den enkelte skriver tildelerseq, udvider indgangskæden og opdaterer grænsen. Den tilføjede RPC gør kun det; batch-lukning kører off-path på alarmen. En fejl i append-transport/applikation er i øjeblikket fail-soft: svaret kan stadig lykkes udenlog_seq,receiptelleranchor_status. På trods af en implementeringskommentar er der i dag ingen automatisk senere logafstemning. - Returner kvitteringen. Inkluder kun
{ log_seq, anchor_status: "pending", receipt }, når appenden returnerer den komplette succesfulde tuple.receipt.sig_b64urler tom, når kvitteringsunderskriver ikke er tilgængelig; klienter MÅ IKKE kalde den værdi signeret eller ikke-omstridbar. Fravær af tuple betyder kun varig publikation, ikke acceptation af gennemsigtighedslog. - Brug én udskudt opgave til at poste den nøjagtige signerede transaktion. Succes fjerner udbakken; fejl efterlader den til den begrænsede dræn-cron og må ikke ændre den allerede anerkendte
tx_id. Foreløbige metadata og andre best-effort sidecars udskydes også.
Latensgrænse. Anmodningsstien inkluderer front-half autoritets-/kvotearbejde, transaktionsforberedelse/signering, varige R2-skrivninger og (når konfigureret) LogDO forsøg. < 300 ms optræder i designgennemgangen som et operationelt mål, ikke som en protokolgaranti; det nuværende transaktionsforberedelsestrin kan udføre en gateway-metadataanmodning. Latensalarmer og launch gates er operationelle kontroller, ikke beviser tilgængelige for en verifikator.
16.11 Trust-modellen — den præcise påstand, afgrænset efter bladtype
For kind=0x01 (attesteret): *"Dette indhold — kropsmatchende body_hash, identificeret af qub_id — blev indskrevet i qub's append-only log på position seq og eksisterede senest i Arweave block time T; det var kryptografisk ulæseligt indtil drand runde R = unlock_round(unlock_at)." * Dette er den fulde {tlock round binding + Merkle inclusion + anchored root} triple.
For kind=0x02 (bekræftet, standard): *"En uigennemsigtig chiffertekst med indholdsadresse chash, der hævder qub_id og unlock_at, blev indviet i append-only-logen på position seq og eksisterede senest i Arweave-bloktiden T." * Rund- og kropsbenene leveres af den eksisterende §11 .qub-bundle verification (qub_core::unlock), ikke af logs; det, logen tilføjer over en bar per-qub transaktion, er manipulationssikker rækkefølge, en tillidsløs øvre bund forpligtelsestid og modstand mod equivokation.
Begge krav udelukker, ifølge §11: forfatterskab uden sig_alg ≥ 0x01, hensigt og timing af subanker-granularitet. Ingen af dem lader nogen påstand hvile på received_at.
Kravloft (bindende lanceringsbegrænsning — løst §16.15 Q1). For et påstået (kind=0x02) blad er det omfangede krav ovenfor loftet for, hvad ethvert produkt, markedsføring, vilkår eller bevisrenderingsflade må hævde. Ingen overflade må angive eller antyde, at logen beviser indholdet eller oplåsningsrunden af en byte-blind upload — logen beviser rækkefølge + en tillidsløs øvre bund forpligtelsestid for en uigennemsigtig chiffertekst. Indhold og rundebevis kommer udelukkende fra den eksisterende §11 .qub-bundle verifikation, som er log-uafhængig. En publikation uden succesfuld append/kvittering har slet ingen log-påstand.
16.12 Versionsstyring og W3-koordinering
Der er ingen SealedQub ledningsopgradering og derfor ingen protokolversionsopgradering (§12.2): loggen er en sidecar, der forpligter sig til eksisterende felter og bytes, så den indgår ikke i §12.3 protokolversionshistorik. W3's valgfrie drand_chain_version forbliver urørt og forbliver det eneste valgfrie SealedQub felt. Loggen introducerer i stedet sine egne uafhængige versionsområder — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — som spejler §12.5 wrapper-versionsuafhængighed (wrapperen bærer en versionsbyte uafhængig af protokolversionen, og loggens versioner følger samme adskillelse).
Bevislevering hentes som standard, med en valgfri medfølgende fil. Et bevis kan ikke eksistere på tidspunket for forseglingen (ankeret er endnu ikke skrevet), så seal-time .qub bundtet forbliver bevisfrit. W7's verificerer henter GET …/proof én gang, eller i fuldt offline-tilstand rekonstruerer beviset fra den offentlige AnchorBundle via en Arweave-forespørgsel på Log-Id. .qub bundtet (W7) reserverer et valgfrit inclusion_proof medlem — fraværende ved forsegling, fyldt op via en post-anker re-eksport for kold arkivering — og følger samme mønster "valgfri, udeladt som standard, additivt" som W3's drand_chain_version.
16.13 Opbevaring
Opbevaringsvinduer for LogDO åbne hale, R2 bevisleveringssubstrat, anker-kredsløbsafbryder tællere og bundler fallback-kø er angivet i docs/DATA-RETENTION.md. Princip: loggens varme per-indgangsopbevaring (LogDO) kan genindvindes efter anker; dets revisionsmateriale — den koordinatnøglede (level, index) Merkle-nodebunke + de seq-adresserede leaf-kroppe (§16.9) + Arweave-ankre — er permanente. Genindvinding af et koldt leaf fra DO invaliderer aldrig et udstedt bevis, fordi et bevis løser sig mod den permanente R2 nodebunke og Arweave-ankeret, ikke DO (og §16.9 slettet-DO testvektor beviser det).
16.14 Testvektorer
W5 leverer cross-language fixture tlog_v1.json (§16.8) plus bearbejdede vektorer: et kind=0x01 og et kind=0x02 leaf → leaf_hash; den 5-leaf kumulative rod; et inklusionsbevis; et konsistensbevis; et AnchorBundle; og én DataItem id. Disse findes sammen med §14.5 outer-wrapper vektorer og benyttes af både Rust (qub-core) og TypeScript (Worker) implementeringer.
16.15 Reviewbeslutninger (W5 — afklaret)
W5 eksterne review (en adversarial design-gennemgang + ejer-godkendelse) er afsluttet. Hver beslutning nedenfor er afklaret og afspejlet i §16 teksten ovenfor; de bindende lanceringsbetingelser er gentaget til sidst. Implementering kan fortsætte under disse.
Standardsti (
kind=0x02) bladærlighed — LØST. Send to-blad-type split som specificeret:kind=0x02commider hverkenbody_hashellerdrand_round. Intet*_body_hashfelt på byte-blind stien (det ville være det mest læselige falske "verificerede" signal for integratorer og er en bekvemmelighed, som §11 allerede giver fra pakken). Kræver ikke server-seal for log-attesterede qubs (der ville tvinge klartekst gennem Worker og ødelægge den krypto-shredding voldgrav). Enhver selvbeskrivende kortslutning hører hjemme i.qubbundle / proof envelope som et verifier-genberegnet felt, aldrig som et leaf-felt. Ejerbekræftet kravloft: §16.11.Uklarhed / udeladelse af ansvar — DESIGN LØST, PROVISIONERING UFULDSTÆNDIG. Designet kræver, at forseglingskvitteringsnøglen er fastgjort i
LogProfileog krydsunderskrevet afanchor_owner, plus monitor-metodologi, tidligere kædegang og dobbelte selvpublicerede hoveder. Den kompilerede profil og udrulningskrogene er stadig pladsholdere/valgfrie som beskrevet i §16.6, så den stærkere detekterbare + modtagne påstand er ikke aktuel, før disse porte lukkes. Den må aldrig markedsføres som uafhængigt bevidnet. Et ægte tredjepartsvidne udsættes til en §15 governance-bump.Fastgjort anker-ejer tillidsrod + rotation — LØST. Adoptér den
LogProfilepin (§16.6); verificereren tjekkeranchor_tx.owner == anchor_ownerog verificerer tx-dataene → tx_id binding lokalt. Rotationsstyring er en §15 udvidelse til build (§15.3 trigger tilføjet), ikke en genbrug; planlagte rotationer krydstegner, kompromisdrevne rotationer falder tilbage til §15-bumpet med gaffeltjekket for bounding damage.Private-qub bladblindning — LØST. Bevar blinding for private qubs (
ref = SHA3-256(qub_id ‖ log_blind_secret)), råqub_idfor offentlige qubs (allerede §16.2.1),chashsom den selvstændige tie.log_blind_secreter en korrelations-/Sybil-kvalitetshemmelighed, kun roterende frem (§16.2.1).received_at— LØST. Behold det i bladet, forpligtet men eksplicit ikke-bevismæssigt; aldrig dukket op som bevis eller tvist om bekræftelse på nogen overflade. Enhver monitor-sanity-kontrol sammenlignes med Arweave-bloktidenT, ikke med operatørstyredeanchored_at(§16.6).Niveauvis bevisbar timing — DESIGNOPLØSNING, IKKE NUVÆRENDE ROUTING. Det gennemgåede design tildeler anchor-block timing til det batchede niveau og nøjagtig-time bevis til betalt T3, uden numerisk SLA for førstnævnte. De nuværende ruter har ikke denne kommercielle skelnen: de planlægger en individuel transaktion for hver accepteret publikation, og logdækningen forbliver betinget som angivet i §16.1/§16.10. Produktkopi skal beskrive implementeringen, ikke denne fremtidige tier-opdeling.
Kumulativt træ over Arbejdere — LØST. Enkelt kumulativt RFC 9162 træ + frontcached single-writer LogDO (komfortabel margin i forhold til ~1k skriver/sec DO loft; udskyde Merkle-of-shard-roots sharding indtil nær det). Den koordinatnøglerede
(level, index)R2 nodebutik + slettet-DO kold-blads testvektor er implementeret (§16.9).< 300 msforbliver et design-/driftsmål, ikke et protokol-lofte (§16.10).ANS-104 signaturordning + deep-hash — LØST. RSA-PSS (sig type 1, genbrug af den dedikerede anchor-wallet JWK); Ed25519 udsat til §15 PQ-banesti. Den håndrullede SHA-384 deep hash er betinget af both-directions cross-impl fixture, en statisk-only reference-bundler interoperabilitetskontrol, den shared-
crypto.subtleround-trip, og post-bundle Arweave-acceptance monitor (§16.8).
Binding lancering begrænsninger (medtages i implementering + produkt/juridisk gennemgang):
- Kravloft (Q1/Q6). Ingen overflade må sige loggen beviser indholdet af et hævdet blad eller låserunden; den tilladte påstand for et succesfuldt forankret
kind=0x02-blad er ordnet, manipulationssikkert, med en tillidsløs øvre-grænse for forpligtelsestid. Et svar uden en kvitteringstuple har ingen logpåstand. Ingen tidsstempelkopi medfører en numerisk latenstidsgaranti. - Vidnes ærlighed (Q2). Markedets dobbeltspil som opdageligt + kvitteret, aldrig uafhængigt vidnet.
- Kvittering + forankringsnøgler (Q2/Q8). Før krav om ikke-benægtelse/forankret-verifikation frigives, lever og kompilér-lås kvitteringens offentlige nøgle, cross-signér den med den leverede forankrings ejer, og hold forankringswallet som sin egen JWK adskilt fra upload-walleten.
- Deep-hash port (Q8). Ingen forankring eller T3-transaktion frigives, før både-retningers indretning + interoperabilitetskontrol er bestået; acceptmonitoren slår alarm ved fejl.
- Lagringsforudsætning (Q7). Koordineringsnøgle-baseret node-lager + slettet-DO kold-blad-vektor er forudsætninger for garantien "genvinding ugyldiggør aldrig et bevis".
17. Portabelt verifikationsbundt (.qub)
Status. Denne sektion er implementeret (W7 / UP-C2):
qub_core::exportproducerer og parser bundtet, ogtools/qub-verifyer en offentlig, selvstændig CLI, som verificerer det offline. §11 og §16.9 omtaler allerede ».qub-bundtet« som den enhed, en selvstændig verifikator forbruger; denne sektion specificerer dets bytes og verifikationsgennemgangen. Den er strengt additiv — bundtet pakker eksisterende §11-input og ændrer intet on-chain-wire-format.
17.1 Formål
§11 fastslår, at enhver tredjepart kan verificere en qubs kryptografiske artefakt uden qubs samarbejde. .qub-bundtet gør verifikationen portabel og offline: Det samler den forseglede CBOR og den drand-rundesignatur, der låser den op, i ét selvstændigt artefakt, så en modtager kan verificere indholdsintegritet, rundebinding og eventuelle forfatterskabssignaturer helt uden netværkskald (ingen lagerhentning, ingen aktiv drand-anmodning, intet qub-API). Et bundt alene beviser ikke, hvornår dets ciphertext blev skabt; en uafhængigt verificeret lagringstransaktion eller et forankret logbevis leverer denne separate eksistenstidspåstand (§11, §17.5).
17.2 Bundtformat
Et QubBundle er håndskrevet kanonisk CBOR under §3.1-profilen (bestemte længder, ingen tags, ingen floats, heltal i korteste form, NFC-tekst, valgfrie felter udeladt ved fravær, nøgler ordnet først efter stigende kodet bytelængde og derefter bytevis). De tre nøgler med 15 tegn sorteres d < i < s. En rå .qub-fil består præcis af disse bytes; til transport via URL eller kopiering bruges de samme bytes som base64url uden padding.
| Nøgle | Kod. længde | Type | Tilstedeværelse | Betydning |
|---|---|---|---|---|
version |
8 | u8 |
påkrævet | Bundtformatversion (0x01). |
sealed_at |
10 | i64 |
valgfri | Skaberens påståede forseglingstid (Unix-sekunder); selvbeskrivende, uden bevisværdi. |
drand_round |
12 | u64 |
påkrævet | Den runde, qub'en er låst til. En projektion af den indlejrede forseglede qub. |
arweave_tx_id |
14 | tstr |
påkrævet | Transaktions-id'et, som de forseglede bytes blev lagret under (proveniensreference). |
drand_chain_id |
15 | tstr |
påkrævet | drand-kæden (hex). En projektion af den indlejrede forseglede qub. |
drand_signature |
16 | bstr |
påkrævet | drand-beacon-signaturen for drand_round — den værdi, der låser ciphertext op. |
inclusion_proof |
16 | bstr |
valgfri | Merkle-inklusionsbeviset fra §16-transparensloggen (§17.5). |
sealed_qub_cbor |
16 | bstr |
påkrævet | De indre SealedQubCbor-bytes (efter udpakning i §13), dvs. verifikationsinputtet fra §11. |
drand_round og drand_chain_id er bekvemme projektioner af sealed_qub_cbor, så værktøjer kan læse dem uden at parse den indre CBOR. De udledes ved konstruktion og krydskontrolleres igen ved afkodning mod den parsede forseglede qub; et bundt, hvis topniveaufelt ikke stemmer overens med payloaden, afvises. Encoderdisciplinen følger resten af wire-formatet: Afvis en tom drand_signature eller arweave_tx_id, og begræns alle felter med variabel længde.
17.3 Hvad den indlejrede drand-signatur beviser
Bundtet medtager drand-signaturen i stedet for at kræve, at verifikatoren henter den. Timelock-dekryptering (tlock over drand-kæden, §8) kan kun lykkes med den ægte beacon-signatur for den bundne runde — en værdi, som kæden først udgiver, når runden er passeret, og som er en gyldig BLS-signatur under kædens offentlige nøgle. En forfalsket eller forkert signatur fejler BLS-verifikationen eller IBE-/AEAD-dekrypteringen. Et bundt, der kan dekrypteres, beviser derfor: Ciphertext er bundet til runde R, og runde R er passeret. Verifikatoren fastgør kæden (DrandTimelockProvider::quicknet()) og anvender rundebindingskontrollen fra §11, så et bundt ikke kan påstå en runde, som dets ciphertext ikke er bundet til.
Dette er et bevis for en udgivelsesbetingelse, ikke et tidsstempel for oprettelse. Når runde R er passeret, kan enhver skabe en ny ciphertext til R og pakke dens allerede offentlige signatur. Derfor MUST bundtet alene ikke beskrives som bevis for, at ciphertext eller indholdet eksisterede før R, før unlock_at eller før nogen begivenhed.
17.4 Gennemgang af offlineverifikation
qub-verify <file.qub> kører standardproceduren fra §11 udelukkende ud fra bundtet og driver qub_core::unlock::unlock med en fastgjort DrandTimelockProvider:
1. Parse the .qub bytes → QubBundle (canonical-CBOR guard; bound every field;
re-check drand_round / drand_chain_id against the embedded sealed qub).
2. BLS-verify bundle.drand_signature for the pinned chain and round, then
tlock_decrypt(sealed.tlock_ciphertext, bundle.drand_signature) → QubEnvelope.
3. Verify SHA3-256(body) == body_hash (§11 step 8).
4. Verify QubEnvelope.qub_id == SealedQub.qub_id (§11 step 9).
5. Verify QubEnvelope.unlock_at == SealedQub.unlock_at (§11 step 10).
6. Verify ciphertext round == unlock_round(unlock_at) and the chain binding.
7. If sig_alg != 0x00: verify author_signature (and any cosigner; §9.4).
8. Report integrity, round-elapsed/round-binding, authorship, and cosigner
verdicts separately, plus the recovered body. Do not report a commitment
timestamp unless step 9 succeeds.
9. Optional existence-time leg: verify an included §16 proof through its pinned
anchor, or independently verify the referenced storage transaction. Report
its block time as an upper bound on ciphertext existence.
CLI'en afslutter med 0 (verificeret), 1 (verifikation mislykkedes — stadig låst, uoverensstemmelse i body-hash, brudt runde-/kædebinding eller en signatur, der ikke kan verificeres) eller 2 (brug / fejlformet bundt). En --json-rapport indeholder de samme resultater til automatisering. Da bundtet er selvstændigt, er verifikatorcraten (qub-core) og CLI'en (qub-verify) den eneste software, en tredjepart behøver; begge er offentlige og genbruger protokollens eksisterende verifikationssti — ingen særskilt kryptografi.
17.5 Forholdet til transparensloggen
inclusion_proof er en valgfri plads til Merkle-inklusionsbeviset fra §16. Verifikation alene ud fra bundtet (§17.4) er komplet for integritet, rundebinding / passeret runde og valgfrit forfatterskab, men har bevidst ingen uafhængigt tidsstemplet eksistenstidspåstand. Et udfyldt, fuldt ankerverificeret inclusion_proof tilføjer den bladtypespecifikke forpligtelse og øvre tidsgrænse fra §16.11 uden at ændre bundtformatversionen. Et fraværende bevis betyder kun »intet bevis medtaget« — ikke »ugyldigt« og ikke nødvendigvis »ikke forankret«.
I referenceimplementeringen er pladsen nu typet: qub_core::export::QubBundle::inclusion_proof_typed() returnerer en Option<InclusionProof>, som bærer hele strukturen fra §16.9 (blad, auditsti, forankret rod og AnchorRef) gennem det samme uigennemsigtige CBOR-felt — uden versionsskift af bundtformatet. Den selvstændige qub-verify-CLI forbruger den via sit --anchor-ben og rapporterer — indtil anker-walleten er klargjort (§16, Status) — et udfyldt bevis med pladsholderejer som kun inklusion frem for fuldt ankerverificeret.