qub-protokollspecifikation
qub är ett protokoll för kryptografiska temporala åtaganden: ett system för att försegla ord till ett framtida datum och senare verifiera exakt vad som förseglades, vilken drand-runda som styrde dess frisättning, och—när en lagringstransaktion eller ett bevis i en transparenslogg är tillgängligt—en oberoende tidsstämplad övre gräns för när chiffertexten åtagits.
Tre primitiva gör att det fungerar. drand är en decentraliserad slumpmässighetsfyr – avslöjandedatumet upprätthålls kryptografiskt snarare än av qubs välvilja. Hållbar lagring bevarar erkända förseglade bytes medan de nuvarande publiceringsvägarna schemalägger individuella transaktioner för permanent lagring; framgångsrika loggtillägg för generell uppladdning kan dessutom ansluta till batchade, förankrade åtaganden. ML-DSA-65 är en post-kvantum digital signatur—när författarskap är aktiverat är qub kopplad till ett nyckelpar vars hemlighet aldrig lämnar författarens enhet.
Tillsammans gör dessa primitiva element ett uttalande som är tidslåst och manipulationssäkert, valfritt kapabla att attribueras, och som kan tidsstämplas oberoende – ett kvitto vars värde ökar i takt med världens förmåga att fabricera det förflutna förbättras.
Resten av detta dokument är den normativa specifikationen som krävs för interoperabla implementationer.
qub-protokollspecifikation
| Fält | Värde |
|---|---|
| Dokumentutgivning | 1.0.0 (protocol-v1.0.0) |
| Trådprotokoll | 0x01 |
| Ytterhölje | 0x01 |
| Ikraftträdandedatum | 2026-09-23 |
| Status | Aktuell |
| Granskad igenom | 2026-09-23 |
Detta dokument är den normativa protokollspecifikationen för qub-systemet för tidsbestämda åtaganden. Det definierar datastrukturer, serialiseringsregler, härledningsformler och verifieringsprocedurer som krävs för interoperabla implementationer.
Omfattning: protokollskiktet är avsiktligt språkneutralt — qub-kroppen är ogenomskinlig klartext / markdown / paktbyte, och språkmedveten rendering är läsarens ansvar (qub.social-webbapp, <qub-embed>-iframe, MCP-klienter, etc.).
1. Notation och konventioner
| Notation | Betydelse |
|---|---|
u8, u64, i64 |
Osignerade/signerade heltal med specificerad bitbredd |
[u8; N] |
Bytearray med fast längd om N byte |
Vec<u8> |
Bytearray med variabel längd |
Option<T> |
Värde av typ T, eller frånvarande |
String |
UTF-8-textsträng, NFC-normaliserad |
| ` | |
SHA3-256(x) |
NIST SHA3-256-hash av bytesträngen x (FIPS 202) |
ceil(x) |
Takfunktion: minsta heltal ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | Mest signifikant byte först |
Alla heltal i pre-image-konstruktioner kodas som big-endian bytearrayer med fast bredd (i64 → 8 byte, u8 → 1 byte) om inte annat anges.
Alla tidsstämplar är Unix-sekunder i UTC.
2. Datastrukturer
2.1 ComposeQub (skaparens tillstånd i minnet)
Serialiseras inte till CBOR. Skrivs inte till permanent lagring. Lokal för skaparappen.
ComposeQub {
draft_id: [u8; 16], // Random, generated locally
created_at: i64, // Unix seconds UTC
unlock_at: Option<i64>, // Unix seconds UTC; None while composing
visibility: u8, // 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 (dekrypterad nyttolast)
Serialiseras med kanonisk CBOR (§3). Krypterad inuti SealedQub. Detta är strukturen som bevisar innehållsintegritet efter dekryptering.
QubEnvelope {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Derived (see §4.1)
content_type: u8, // Content type registry (see §6)
created_at: i64, // Unix seconds UTC
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // 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
}
Baslinje (osignerad text qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; signatur- och medsignaturfält saknas. Andra valfria metadatafält kan finnas.
Andra v1-konfigurationer: content_type = 0x03 (paktkropp, se §6.1); sig_alg = 0x01 (ML-DSA-65) med author_signature och author_pubkey närvarande (se §9.3); cosigner_pubkey och cosigner_signature närvarande tillsammans för motundertecknade pakter (se §9.7); reply_to satt till föräldra-qubbens qub_id för svarskedjor (se §9.3 för signaturomfångskonsekvenserna).
2.3 SealedQub (kanoniskt trådformat)
Serialiserad med kanonisk CBOR (§3). Detta är det inre trådartefaktet: offentlig leverans lagrar dessa bytes utan omslag, medan privat leverans omsluter dem i OuterWrapper innan 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äsarens applikationstillstånd)
Serialiseras inte till CBOR. Lokal för läsarappen. Konstrueras efter framgångsrik dekryptering och verifiering.
RevealedQub {
qub_id: [u8; 32],
arweave_tx_id: String,
visibility: u8,
content_type: u8,
created_at: i64,
unlock_at: i64,
outcome_at: Option<i64>, // 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
All serialisering av SealedQub och QubEnvelope MUST följa denna profil. Två implementationer som ges samma logiska struktur MUST producera identiska byte.
3.1 Kodningsregler
| Regel | Specifikation |
|---|---|
| Standard | RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements) |
| Nyckelordning i kartor | Sorterade efter kodad bytelängd först (kortare före längre), sedan lexikografiskt (byte-för-byte för kodningar av samma längd) |
| Heltalskodning | Kortaste form: 0–23 i initial byte; 24–255 i 2 byte; 256–65535 i 3 byte; etc. |
| Längdkodning | Endast bestämda längder. Inga arrayer, kartor, bytestrings eller textstrings av obestämd längd (additional info = 31 är förbjudet). |
| Taggar | Inga CBOR-taggar (major-typ 6 är förbjudet). |
| Flyttal | Inga flyttal (major-typ 7 värden 0xF9–0xFB är förbjudna). |
| Textstrings | UTF-8-kodade, NFC-normaliserade (Unicode Normalization Form C). |
| Bytestrings | Råa byte. Ingen base64-kodning i CBOR-skiktet. |
| Dubblettnycklar | Avvisa med fel. Tolkare MUST NOT tyst acceptera dubblettnycklar i kartor. |
| Okända nycklar | Avvisa med fel. Tolkare MUST NOT tolerera kartnycklar utanför typens kanoniska nyckeluppsättning — två distinkta kanoniska bytesträngar får aldrig avkodas till samma värde (encode(decode(x)) == x), och för signerade nyttolaster vore en extra nyckel dolt innehåll som båda signaturerna åtar sig. Schemautveckling sker genom version, aldrig genom extra nycklar. |
| Enkla värden | Endast true (0xF5), false (0xF4) och null (0xF6) är tillåtna. |
| Frivilliga fält | Frånvarande frivilliga fält utelämnas helt från CBOR-kartan (kodas inte som null). Närvarande frivilliga fält inkluderas i sorterad nyckelordning. |
3.2 Verifierade kanoniska nyckelordningar
Dessa nyckelordningar är normativa. Implementationer MUST avge nycklar i exakt denna ordning. Debug-assertioner SHOULD verifiera ordning i icke-release-byggen.
QubEnvelope (version 0x01, osignerad, alla frivilliga fält frånvarande):
"body" (5 encoded bytes)
"qub_id" (7 encoded bytes)
"sig_alg" (8 encoded bytes)
"version" (8 encoded bytes)
"reply_to" (9 encoded bytes) ← only if present (reply chains)
"body_hash" (10 encoded bytes)
"unlock_at" (10 encoded bytes)
"created_at" (11 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
Härledning av QubEnvelopes nyckelordning: varje nyckel är en CBOR-textsträng. Kodad längd = 1 byte header + stränglängd (för strängar under 24 byte). Sortera först efter total kodad längd, sedan lexikografiskt för nycklar av samma längd.
SealedQub (version 0x01, publik, ingen mottagare):
"title" (6 encoded bytes) ← only if present
"qub_id" (7 encoded bytes)
"version" (8 encoded bytes)
"unlock_at" (10 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (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 (paktkropp, content_type 0x03):
"notes" (6 encoded bytes) ← only if present
"terms" (6 encoded bytes)
"title" (6 encoded bytes)
"party_a" (8 encoded bytes)
"party_b" (8 encoded bytes)
"pact_version" (13 encoded bytes)
PactTerm (rad i terms-arrayen):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier (party_a / party_b-karta):
"label" (6 encoded bytes)
"contact" (8 encoded bytes) ← only if present
3.3 Referens för bytekodning
| Typ | CBOR-kodning | Exempel |
|---|---|---|
| SHA3-256-hash (32 byte) | 0x58 0x20 + 32 byte |
body_hash, qub_id |
| Tidsstämplar (i64) | Major-typ 0 (positiv) eller 1 (negativ), kortaste kodning | Unix-sekunder |
| Version (u8, värde 1) | 0x01 (enskild byte) |
|
| Innehållstyp (u8, värde 1) | 0x01 (enskild byte) |
|
| sig_alg (u8, värde 0) | 0x00 (enskild byte) |
|
| ML-DSA-65-signatur (3 309 byte) | 0x59 0x0C 0xED + 3 309 byte |
author_signature, cosigner_signature |
| ML-DSA-65 publik nyckel (1 952 byte) | 0x59 0x07 0xA0 + 1 952 byte |
author_pubkey, cosigner_pubkey |
4. Normativa härledningar
4.1 qub_id
qub_id identifierar unikt en qub och binder QubEnvelope till SealedQub. Den härleds deterministiskt från envelopinnehåll.
qub_id = SHA3-256(
"QUB_ID_V2" || // domain separator: ASCII bytes [0x51 0x55 0x42 0x5F 0x49 0x44 0x5F 0x56 0x32] (9 bytes) + 0x00 padding (1 byte) = 10 bytes
version || // u8 (1 byte)
content_type || // u8 (1 byte)
created_at || // i64 big-endian (8 bytes)
unlock_at || // i64 big-endian (8 bytes)
outcome_at_or_zero || // i64 big-endian (8 bytes; 0 when outcome_at is absent)
drand_round || // u64 big-endian (8 bytes)
body_hash || // [u8; 32] (32 bytes)
title_hash // [u8; 32] (32 bytes; absent-sentinel = [0u8; 32])
)
// Total preimage: 108 bytes → 32-byte output
Kodning av domänseparator: Strängen "QUB_ID_V2" är 9 ASCII-byte. En enskild 0x00-utfyllnadsbyte läggs till för att nå 10 byte för justering. Implementationer MUST använda exakt dessa 10 byte: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
outcome_at kodning: En förhandsrevisionsimplementation utökade förbilden från 92 till 100 byte för att veckla det valfria outcome_at fältet i bindningen. Frånvarande outcome_at kodas som 8 noll-byte; protokollvaliderarna avvisar outcome_at <= 0 överallt så att denna vaktpost inte kan kollidera med ett giltigt värde. Se §3.2 (trådfomat) och i-trädet tasks/verdict-uplift-plan.md för domsmekanikern som motiverar detta fält.
drand_round kodning: En senare pre-release implementeringsrevision utökade förbilden från 100 till 108 byte att vika drand_round (det målinriktade drand-omgången, §4.3) in i bindningen, och höjde domänavgränsaren till QUB_ID_V2. Detta binder timelock-omgången till qub-identiteten: en gateway kan inte binda om chiffertexten till en annan (t.ex. redan passerad) omgång än den som visas unlock_at innebär. Låsningsproceduren (§8) verifierar dessutom att rundan som är inbakad i tlock-krypteringstextens strof stämmer unlock_round(unlock_at), så den visade upplåsningstiden är bevisligen omgången som öppnar dekryptering.
Egenskaper:
- Att ändra något fält bundet av förbilden—
version,content_type,created_at,unlock_at,outcome_at,drand_round, det råabodybyten (genombody_hash), ellertitle(genomtitle_hash)—producerar ett annatqub_id. - Qub_id beräknas innan kryptering. Både QubEnvelope och SealedQub bär samma qub_id. Betraktaren verifierar att de matchar efter dekryptering.
qub_idberoende inte påsender_label,reply_to, signaturbitar eller offentliga nycklar för signering. Under den nuvarande V2-signeringskonstruktionen, däremot,sender_labelochreply_toautentiseras direkt avsender_label_hashochreply_to_or_zero(§9.3) när signaturer är närvarande.- Byta den FörsegladeQub
title(med allt annat fixat) ändringarqub_idviatitle_hash. En gateway kan därför inte byta den okrypterade titel som visas på nedräkningen utan att ogiltigförklara qub-identiteten. - Byta den FörsegladeQub
outcome_at(med allt annat fixat) ändringarqub_idvia förbilden. En gateway kan inte byta det föravslöjade domen-om datum som visas på nedräkningen utan att ogiltigförklara qub-identiteten. - Förändrar
drand_round(med allt annat fixat) ändringarqub_idvia förbilden. En gateway kan inte binda om timelock-kryptexten till en annan runda utan att ogiltigförklara qub-identiteten; kombinerat med §8 unlock-time stanza-round-kontrollen, visasunlock_atär rundan som faktiskt styr avkodningen.
4.2 body_hash
body_hash = SHA3-256(body)
Där body är den råa Vec<u8>-innehållsnyttolasten. För text-qubs är detta den UTF-8-kodade qub-kroppen.
4.2.1 title_hash
title_hash = SHA3-256(NFC(title).utf8_bytes) if title is present
title_hash = [0u8; 32] if title is absent
Där title är den frivilliga klartexttiteln som visas på läsarens nedräkning före avslöjande (se §3.2). NFC-normalisering körs vid hashtid så att sammanfattningen är stabil över visuellt likvärdiga kodpunktssekvenser. Sentinelvärdet med alla nollor är reserverat för det frånvarande fallet; en tom sträng avvisas vid den kanoniska CBOR-gränsen som en icke-kanonisk kodning av "frånvarande" (den kanoniska kodningen utelämnar fältet helt).
4.3 Lås upp-runda mappning
drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
| Parameter | Källa | Exempel |
|---|---|---|
unlock_at |
Användarvalda Unix-sekunder UTC | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
drand-kedjeinformation (genesis_time) |
1595431050 |
chain_period_seconds |
drand-kedjeinformation (period) |
30 |
Detta är referenskarta för tlock (drand's) CurrentRound). drand publicerar omgång N vid chain_genesis_time + (N - 1) * chain_period_seconds, så formeln väljer rund ström vid unlock_at — omgången vars signatur är den första som en besökare som anländer unlock_at kan använda.
Justeringsegenskap (det fall som spelar roll i praktiken): när (unlock_at - chain_genesis_time) är exakt delbart med chain_period_seconds, det valda rundans signatur publiceras exakt vid unlock_at, aldrig tidigare. Detta gäller alltid för referensdistributionen: quicknets starttid (1692803367) är delbart med sin 3-sekundersperiod, och referensapparna låser upp tider till hela minuter. För en icke-justerad unlock_at, den valda rundans signatur publiceras strikt mindre än en period innan unlock_at — den temporala precisionen för åtagandet är en beacon-period.
Förhandsversion av äldre kartläggning och tolerans på upplåsningssidan: den ursprungliga kartläggningen var ceil((unlock_at - chain_genesis_time) / chain_period_seconds), vilket—för det ovanstående periodanpassade fallet—valde den rundade publicerade en hel period före unlock_at, vilket gör att chiffertexten kan avkrypteras tidigt med exakt en period. De två mappningarna skiljer sig exakt med +1 när deltan delar perioden, och håller med annars. Eftersom drand_round är vikt in i det oföränderliga qub_id preimage (§4.1), artefakter som förseglats under den äldre mappningen kan inte härledas på nytt; verifierare som utför §8 steg 6a rundkorskontroll MÅSTE därför acceptera en lagrad drand_round lika med antingen den härledda rundan eller det härledda rundan minus ett (och MÅSTE kräva att tlock-strofen runda är exakt lika med den lagrade rundan). Toleransen vidgar den tidigaste styrningssignaturen med högst en period. Pact-sceneringstjänsten tillämpar samma tolerans när den härleder en scenerad pacts qub_id (på scen och vid medsignering): om den nuvarande mappningens runda inte reproducerar det som har åtagits qub_id och deltavärdet delar perioden, det försöker igen med rundan minus ett, och det förseglar det slutliga avtalet till vilken runda som qub_id faktiskt binder—aldrig blint till den omräknade omgången, vilket skulle göra artefakten permanent obesvårbar.
Validering: unlock_at MÅSTE vara i framtiden vid säl-tid. unlock_at FÅR INTE vara mer än 10 år från created_at (för att begränsa riskerna med långsiktig drand-beroende; gränssnittet BÖR varna för upplåsningsdatum längre än 2 år).
5. Newtypes för trådformat
Newtypes för trådformat ger kompileringstidssäkerhet mot att förväxla CBOR-byte med JSON, rå klartext eller andra bytekodningar.
| Typ | Innehåller | Producerad av | Uppslukad av |
|---|---|---|---|
SealedQubCbor |
Kanonal CBOR av SealedQub | serialize_sealed_qub() |
Inre trådartefakt; lagrad bar för offentlig leverans eller inlindad för privat leverans, och sedan återvunnen av åskådaren |
QubEnvelopeCbor |
Kanonisk CBOR av QubEnvelope | serialize_qub_envelope() |
tlock kryptera indata, tlock dekryptera utdata |
5.1 Konstruktionsregler
// Production code — only through CBOR serialisers:
let sealed = SealedQubCbor::from_encoded(cbor_bytes);
// There is deliberately NO From<Vec<u8>> implementation.
// You cannot accidentally wrap arbitrary bytes in a wire format type.
// Accessing raw bytes:
let bytes: &[u8] = sealed.as_bytes();
let bytes: Vec<u8> = sealed.into_bytes();
5.2 Validering vid konstruktion
from_encoded() SHOULD validera att indata börjar med ett giltigt CBOR-kartheader. Fullständig strukturell validering sker vid tolkningstid, inte konstruktionstid, för att undvika dubbeltolkning.
6. Register för innehållstyper
| Värde | Typ | Max kroppsstorlek | Anmärkningar |
|---|---|---|---|
0x00 |
Reserverad (ogiltig) | — | MUST NOT användas |
0x01 |
Klartext (UTF-8, begränsad Markdown) | 50 KB betald / 10 KB gratis | Se §10 för renderingsregler. Uppdelningen gratis / betald upprätthålls av uppladdningstjänsten; protokollskiktets hårda tak är 50 KB. |
0x02 |
Reserverad (framtida) | — | Tilldelad för en framtida innehållstyp; ogiltig i v1. Läsare MUST avvisa enligt regeln nedan. |
0x03 |
Pakt (bilateralt avtal, CBOR-kropp) | 100 KB | Kroppen är kanonisk CBOR PactTerms (§6.1). Motundertecknarsignering enligt §9.7. |
0x04 |
Verdikt (skaparens självbetygsättning, CBOR-kropp) | 8 KB | Kroppen är kanonisk CBOR VerdictBody (§6.2). Avges endast av den systemsidiga verdict-intentionen. Föräldrarelationen ligger på Parent-Tx-Id-Arweave-taggen, inte på kroppen. Se verdict-uplift-plan §3.4. |
Läsare MUST avvisa okända innehållstyper med ett tydligt användarsynligt felmeddelande. Läsare MUST NOT försöka rendera okända typer som text.
6.1 Paktkropp (content_type = 0x03)
En paktkropp är den kanoniska CBOR-kodningen av ett PactTerms-värde:
PactTerms {
pact_version: u8, // 0x01 for structured/v1
title: String, // ≤ 200 bytes, NFC
terms: Vec<PactTerm>, // ≤ 20 rows
party_a: PartyIdentifier, // initiator
party_b: PartyIdentifier, // counter-signer
notes: Option<String>, // ≤ 5,000 bytes, NFC; absent key if none
}
PactTerm { key: String (≤ 100 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }
Kanoniska CBOR-nyckelordningar för alla tre kartorna anges i §3.2. Totalt serialiserad pakt-CBOR MUST NOT överskrida 100 KB (matchar §6).
Schemadiskriminator. Den första raden i terms för en structured/v1-pakt MUST vara { key: "pact_schema", value: "structured/v1" }. Rader utan denna markör är "custom"-pakter och får ingen strukturerad validering eller schemamedveten rendering.
Frusna bekräftelseplatser. structured/v1-pakter bär exakt fyra bekräftelserader under dessa nycklar:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value för var och en är en av åtta frusna engelska strängar valda av paret (role, kind), där role ∈ { seller, buyer, provider, client } och kind ∈ { standard, capacity }. Strängarna själva är normativa protokolldata — båda parternas ML-DSA-65-signaturer förbinder sig till de exakta bytena via body_hash. De är INTE lokaliserade; den signerade kroppen är språkneutral. Varje formuleringsändring kräver en ny schemaversion (structured/v2).
De åtta strängarna, deras uppslagning (acknowledgement_for(role, kind)) och motiveringen för var och en är fastställda av referensimplementationen. Konforma implementationer MUST avge byte-identiska bekräftelsevärden; golden-fixture SHA3-256 body-hash-tester som täcker alla fyra rollkombinationer fångar all drift.
Visningsordning för läsare. Bekräftelsesträngarna innehåller fraser såsom "described above", som förutsätter att beskrivning / omfattning-raderna renderas före bekräftelserna. Läsare MUST rendera terms-arrayen i CBOR-ordning; omordning bryter prosasemantiken.
Motpartskontakt. När Part B:s contact är en giltig e-postadress skickar qub-uppladdningstjänsten automatiskt en granskning / motundertecknings-inbjudan via e-post vid stage-tid och binder den slutliga motunderskriften till verifiering av samma adress (§9.7). Pakter vars Part B-kontakt är frånvarande kan fortfarande motundertecknas, men endast via en kanal utanför bandet — tjänsten avvisar motunderskriftsförfrågningar som inte kan producera en matchande 15-minuters e-postverifieringsmarkör.
6.2 Verdiktkropp (content_type = 0x04)
En verdiktkropp är den kanoniska CBOR-kodningen av ett VerdictBody-värde:
VerdictBody {
verdict_version: u8, // 0x01 for structured/v1
outcome: u8, // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
reflection: Option<String>, // ≤ 2,000 bytes NFC; "what changed, what did you learn"
evidence_url: Option<String>, // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}
Kanonisk CBOR-nyckelordning:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
Totalt serialiserad verdikt-CBOR MUST NOT överskrida 8 KB (matchar registerraden ovan).
Utfallsenum. Wire-byten är intentionsneutral; de fyra kategorierna Right / Partial / Wrong / Unfalsifiable täcker varje verdiktbärande intentions utfallsrymd. Per-intentionsetiketter ("Rätt sagt" / "Höll det" / "Levererat" / "Bekräftad" för Right, etc.) är en läsarsidig renderingsfråga som löses mot föräldra-qubbens intention — wire förblir språk- och intentionsneutral. Värden utanför 1..=4 MUST avvisas vid avkodning.
Föräldralänkning. En verdikt-qub bär INTE föräldrareferensen i sin kropp. Föräldra-qubbens Arweave-transaktions-id avges som Parent-Tx-Id-lagringstaggen vid uppladdning (§7 lagringstaggsskikt). Detta håller kroppen som ett självständigt signerat uttalande av självbedömning; granskningskedjan ("rätt om vad?") etableras via Arweave-taggsökningen.
Säkerhet för bevis-URL (normativt). När evidence_url är närvarande MUST validerare (kompositionssidan, wire-sidan, Worker-kanten) upprätthålla:
- Endast HTTPS. Strängen MUST börja med bytesekvensen
https://. Varje annat schema —http,ftp,javascript,data,file, etc. — avvisas. - Längdtak. ≤ 2 048 byte (webbläsarens praktiska URL-gräns).
- NFC + kontroll av fientliga kodpunkter. Samma regel som för
titleochreflection— bidi-override / nollbredd / tag-block / BOM / C0 / C1-kodpunkter avvisas. Definitionen matchar Rust-crate::handle::contains_hostile_text_codepointoch TS-workers/api/src/utils/unicode.ts::isHostileCodepoint(håll i lås). - Inga blanksteg, inga ASCII-styrtecken. Blanksteg / DEL / byte under
0x20var som helst i URL:en avvisas — stänger\n/\t-injektionsvektorn som bidi-regeln inte täcker. - Icke-tomt värdsegment. Allt mellan
https://och första/,?eller#MUST vara icke-tomt.
Ingen serversidig hämtning. Workern MUST NOT proxyera, hämta eller förhandsgranska URL:en. Protokollet lagrar en sträng; renderingen sker läsarsidigt med rel="nofollow noopener noreferrer" target="_blank" och en synlig värd visad bredvid länktexten.
Reflektion. Frivillig skaparskriven reflektionstext ("vad förändrades, vad lärde du dig"). Samma NFC + kontroll av fientliga kodpunkter som för title. Tom / endast-blanksteg-inmatning faller till frånvarande vid konstruktion.
Schemaversion. v1 stöder endast verdict_version = 0x01. Framtida schemarevisioner bumpar denna byte och landar tillsammans med en ny protokollversion enligt §12.
7. Förseglingsprotokoll
Den fullständiga förseglingssekvensen. Varje steg är normativt.
1. User composes plaintext and metadata in ComposeQub.
2. Validate:
a. body is non-empty.
b. body size ≤ max for content_type and user tier (see §6).
c. unlock_at is in the future.
d. unlock_at ≤ created_at + 10 years.
e. content_type is a known, supported value.
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.
Lagringstaggsskikt (out-of-band). Qub-uppladdningstjänsten bifogar en avsiktligt liten uppsättning lagringstransaktionstaggar tillsammans med den valda uppladdningsnyttolasten. Content-Type=application/octet-stream är normativt krävd. Referenstjänsten bifogar dessutom tre frivilliga taggar när skaparen väljer att visa dem: Intent (tillåtlista-validerad komponeringsavsikt—announcement, thesis, prediction, letter, secret, commitment, proof, eller systemgenererad verdict), Author (skapares §9.3 pubnyckelfingeravtryck som 64-tecken långt hexadecimalt tecken i gemener), och Parent-Tx-Id (förälderns qub:s lagringstransaktions-ID för svarskedjor, 43 tecken lång base64url).
Den Author tag är gå med per qub: referensskapandeappen bifogar det endast när användaren uttryckligen aktiverar offentlig attribuering vid förseglingstillfället. När reglaget är av — standardinställningen — inget Author taggen är skriven och quben är utan attribut på kedjan: ingenting i det permanenta lagret kopplar uppladdningen till en skapares alias, e-post eller andra qubar. När växeln är på, Author fingeravtryck motsvarar skaparen valda @handle via §9.5 intygskedjan. Svarskedjerelationer och Intent är icke-identifierande. För privat leverans krypterar det yttre omslaget (§13) det igenkännliga inre SealedQub artefakt så att skördning av lagrade omslag och att erhålla offentliga drand-signaturer fortfarande är otillräckligt för att återställa kroppen utan K; lagringsetiketter förblir avsiktligt offentlig metadata.
Referenstjänsten fäster avsiktligt INTE App-Name-, App-Version- eller Type-taggar: varje sådant ensamvärdesfilter skulle returnera hela qub-korpusen till en GraphQL-fråga, vilket är inkonsekvent med omslagets kroppsbara konfidentialitetsomfång.
En konform verifierare MUST NOT vara beroende av någon lagringstagg för §11 tredjepartsverifiering; body-hashen / qub_id / signaturen förbinder sig endast till den inre CBOR:en, aldrig till taggsetet.
8. Upplåsningsprotokoll
Den fullständiga upplåsningssekvensen. Varje steg är normativt.
1. Viewer opens delivery URL. Extract arweave_tx_id from 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. Signering av författarskap
9.1 Motivering
qubs lagras i permanent lagring. Signaturer för författarskap måste förbli oförfalskbara på obestämd tid, varför v1.0 använder det postkvantsäkra schemat ML-DSA-65 (FIPS 204) snarare än ett klassiskt schema vars säkerhet kan försämras inom qubbens permanenta livstid.
9.2 Algoritmregister
sig_alg |
Schema | Nyckelstorlek | Signaturstorlek | Status |
|---|---|---|---|---|
0x00 |
Ingen signatur (osignerad) | — | — | Aktiv |
0x01 |
ML-DSA-65 (FIPS 204) | 1 952 byte | 3 309 byte | Aktiv |
0x02 |
Ed25519 | 32 byte | 64 byte | Reserverad konstant; stöds inte i protokoll v1 |
Protocol-v1-tittare MÅSTE avvisa varje värde utanför {0x00, 0x01}, inklusive
den reserverade 0x02 värde. Reservation förhindrar oavsiktlig återanvändning; det är inte
aktivering. Att aktivera det kräver den styrda förändring som beskrivs i §15.
9.3 Konstruktion av signerad pre-image
Två pre-image-versioner har funnits. Alla signaturer MUST använda V2, och verifierare MUST endast acceptera V2. Den äldre V1-pre-imagen (dokumenterad nedan som historisk referens) accepterades som en enbart-verifierings-fallback under migreringen till V2; den fallbacken har avvecklats och en signatur som endast är V1 avvisas nu.
V2 (aktuell — produceras av all ny författarsignering, och av båda signaturerna i paktens staging-/motundertecknarflöde):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V2" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 || // u8 (1 byte): MUST be 0x00 in v1.x
sender_label_hash || // [u8; 32]: SHA3-256(NFC(sender_label)),
// or 32 zero bytes when absent
reply_to_or_zero // [u8; 32]: parent qub_id, or 32 zero
// bytes when absent
)
// Total preimage: 155 bytes → 32-byte hash
signature = Sign(author_secret_key, sig_input)
sender_label_hash följer samma frånvaro-sentinel-konvention som title_hash (§4.2.1): 32 nollbyte är inte en giltig SHA3-256-utdata, så "frånvarande" kan aldrig kollidera med en närvarande etikett. Alla fält är av fast bredd, så pre-imagen är otvetydig utan längdprefix.
V1 (äldre — AVVECKLAD; produceras inte längre och accepteras inte längre vid verifiering):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V1" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 // u8 (1 byte): MUST be 0x00 in v1.0
)
// Total preimage: 91 bytes → 32-byte hash
V1-pre-imagen utelämnade sender_label och reply_to. Den accepterades som en enbart-verifierings-fallback under migreringen till V2; den fallbacken har sedan dess avvecklats — verifierare MUST endast acceptera V2-pre-imagen. Definitionen behålls här som historisk referens och för att förklara domänseparatorn nedan. En signatur som endast verifierar mot V1 MUST behandlas som ett verifieringsfel.
Domänseparatorer: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" är 17 ASCII-byte vardera ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Ingen utfyllnad. Den skilda separatorn domänseparerar de två konstruktionerna, så en signatur över en pre-image kan aldrig verifiera som den andra.
org_id_present-byte: byten efter unlock_at MUST vara 0x00. Referensimplementationen exponerar detta som konstanten ORG_ID_PRESENT_INDIVIDUAL = 0x00 i crates/qub-core/src/signing.rs; läsare som rekonstruerar sig_input för verifiering MUST avge samma byte.
Signaturomfång — vad som är och inte är täckt. V2-sig_input förbinder sig direkt till version, qub_id, body_hash, unlock_at, sender_label och reply_to (plus den fasta domänseparatorn och org_id_present-byten). qub_id härleds själv från version, content_type, created_at, unlock_at, outcome_at, drand_round och body_hash via §4.1-pre-imagen, så varje ändring av dessa fält producerar ett annat qub_id och ogiltigförklarar signaturen transitivt. Den autentiserade ytan är därför:
| Fält | Verifierad med signatur | Hur |
|---|---|---|
version |
✓ | Direktinmatning till sig_input |
qub_id |
✓ | Direkt inmatning |
body_hash |
✓ | Direkt inmatning |
unlock_at |
✓ | Direkt inmatning |
sender_label |
✓ | Direktinmatning via sender_label_hash (V2 förbild — den enda accepterade formen) |
reply_to |
✓ | Direktinmatning via reply_to_or_zero (V2 förbild — den enda accepterade formen) |
content_type |
✓ | Transitivt, via qub_id förbild |
created_at |
✓ | Transitivt, via qub_id förbild |
outcome_at |
✓ | Transitivt, via qub_id förbild |
drand_round |
✓ | Transitivt, via qub_id förbild |
body |
✓ | Transitivt, via body_hash = SHA3-256(body) |
author_pubkey |
— (underförstått) | Nyckeln som verifierade signaturen är författaren, per definition |
cosigner_pubkey / cosigner_signature |
— | Självständigt undertecknat över samma sig_input (se §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | Yttre SealedQub fält, inte inne i kuvertet — täckta av sina egna strukturella invarianta (rund / kedjekonsistens) men inte av författarens signatur. (drand_round är nu bundet transitivt via qub_id preimage — se ovan.) |
Varför V2 är den enda accepterade pre-imagen.
- Under den avvecklade V1-pre-imagen kunde en part med skrivåtkomst till de lagrade bytena byta
sender_label("Alice" → "Mallory") eller om-förälderreply_to— och kryptera om efter rundan — utan att ogiltigförklara författarsignaturen, eftersom inget av fälten fanns i den signerade pre-imagen. V2 täcker båda, så varje ändring av något av fälten vänder verifieringen till "misslyckad". Eftersom verifierare nu endast accepterar V2 är detta byte stängt för varje signatur: en signatur som inte binder något av fälten (dvs. endast verifierar mot V1) avvisas rakt av snarare än att nedgraderas till. author_pubkeyinuti envelopen förblir det sanna identitetsankaret — läsare MUST härleda visningsidentiteten frånauthor_pubkey(via §9.5-attesteringsskiktet) snarare än att lita påsender_label.
Implementationer som visar sender_label eller reply_to för slutanvändare MUST lyfta fram den autentiserade identiteten (pubkey-fingeravtryck, attestering) som den primära identitetssignalen, inte etiketten.
9.4 Verifieringsprocedur
1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
only accepted form — the legacy V1 fallback is retired (§9.3), so a
signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."
Signaturverifiering är den dyraste operationen (särskilt ML-DSA-65). Den SHOULD utföras efter att alla billigare kontroller (hash, qub_id, unlock_at) har passerat.
9.5 Identitetsattesteringar
Identitetsattesteringar — mappningen av author_pubkey till mänskligt igenkännbara identitetspåståenden såsom ett qub-handtag, en e-postadress, ett socialt handtag eller en passkey-uppgift — är en progressiv förbättring på läsarsidan och är inte obligatoriska för signaturverifiering. Läsare som löser attesteringar till en visningsidentitet MUST tillämpa företrädet:
handle > email > social > fingerprint
Fingeravtrycks-fallbacken är gemen hex av SHA3-256(author_pubkey); den är alltid tillgänglig för varje signerad qub. Läsare MAY förkorta den för visning — referensläsaren renderar qub: följt av de första och sista fyra byten (qub:<8 hex>…<8 hex>).
En konform verifierare kan slutföra varje kontroll i §9.4 utan att kontakta qub-API:et, utan något nätverk utöver permanent lagring och drand, och utan någon serversideuppslagning. Attesteringsupplösning är ett separat steg på bästa möjliga sätt som utförs endast efter att signaturverifiering har lyckats.
9.6 Storlekspåverkan
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| Signatur | 64 byte | 3 309 byte |
| Publik nyckel | 32 byte | 1 952 byte |
| Totalt per qub | 96 byte | 5 261 byte |
| Lagringskostnadsdelta (vid ~5 USD/MB) | ~0,0005 USD | ~0,026 USD |
För en text-qub på 500–2 000 byte tredubblar ML-DSA-65 ungefär den lagrade storleken. Den absoluta kostnaden är försumbar.
9.7 Motundertecknarverifiering (bilaterala paktavtal)
För bilaterala avtal (content_type = 0x03) bevisar ett andra signaturskikt att båda parter samtyckte till samma villkor.
Envelopfält:
cosigner_pubkey: ML-DSA-65 publik nyckel för motundertecknaren (Part B).cosigner_signature: Signatur över sammasig_inputsom författaren (§9.3).
Båda fält MUST vara närvarande tillsammans eller båda frånvarande. Om exakt ett är närvarande MUST läsare rapportera ett integritetsfel.
Verifieringsprocedur:
1. If cosigner_pubkey absent and cosigner_signature absent → no cosigner. Done.
2. If exactly one is present → integrity error.
3. Verify cosigner_pubkey != author_pubkey (prevent self-cosigning).
Fail → display "cosigner pubkey must differ from author."
4. Reconstruct sig_input using the same formula as §9.3 (V2 only — the
legacy V1 fallback is retired; all pact clients produce V2 signatures).
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."
Egenskaper:
- Motundertecknaren signerar det identiska
sig_inputsom författaren — båda parter förbinder sig till sammaqub_id,body_hashochunlock_at(och, under V2, sammasender_label_hashochreply_to_or_zero). - För att låta motundertecknaren rekonstruera V2-pre-imagen utan åtkomst till de råa envelop-bytena upprätthåller staging-tjänsten vid stage-tid att en paktenvelops
sender_labelär lika medpact_terms.party_a.labeloch attreply_toär frånvarande. Båda gäller för varje referensklient-pakt; envelopar som bryter mot detta avvisas vid stage. qub_id-härledning (§4.1) inkluderar INTE motundertecknarfält. Att lägga till en motundertecknare till en befintlig envelop ändrar intequb_id.- En pakt kan vara endast författarsignerad (ensidigt åtagande), endast motundertecknad (ovanligt) eller båda (fullt bilateralt bevis).
Grind för e-postbindning (operativt). När en staged pakt bär en Part B-e-postkontakt (§6.1) MUST qub-uppladdningstjänsten avvisa motundertecknarförfrågan om inte en kortlivad e-postverifieringsmarkör finns som matchar både staging-ID:t och den normaliserade e-posthashen av den kontakten. Markören skrivs av /api/v1/auth/verify när magic-link-token bär en staging_id och den verifierade adressen matchar SHA-256(normalise_email(party_b.contact)) — där normalise_email(addr) bevarar lokaldelens skiftläge och gemener endast domändelen (enligt RFC 5321 §2.3.11), och SHA-256 här är NIST FIPS 180-4-hashen (skild från SHA3-256 som används i §4-härledningar) — och förfaller 900 sekunder (15 minuter) efter utfärdande. Detta är en operativ anti-imitations-grind, INTE en del av qub-beviset på kedjan — en tredjepartsverifierare som spelar upp §11 behöver endast permanent lagring och drand, utan någon serversideuppslagning. Markören finns endast på serversidan och är aldrig en del av den signerade kroppen.
Storlekspåverkan (ML-DSA-65 författare + motundertecknare):
| Komponent | Storlek |
|---|---|
| Författarsignatur | 3 309 byte |
| Författares publika nyckel | 1 952 byte |
| Motundertecknarsignatur | 3 309 byte |
| Motundertecknares publika nyckel | 1 952 byte |
| Totalt kryptoöverhang | 10 522 byte |
| Lagringskostnadsdelta | ~0,05 USD |
10. Markdown-rendering och sanering
Denna sektion är säkerhetskritisk. Läsaren renderar text-qubs (content_type = 0x01) med en begränsad Markdown-delmängd.
10.1 Tillåtna element
- Rubriker:
#till####(inte#####eller######) - Betoning: fet (
**), kursiv (*), genomstrykning (~~) - Listor: ordnade (
1.) och oordnade (-,*) - Blockcitat (
>) - Kod: inline-spans (```) och fenced blocks (`````)
- Horisontella linjer (
---) - Radbrytningar (två avslutande mellanslag eller blank rad)
- Stycken
10.2 Förbjudna element
| Element | Hantering |
|---|---|
Rå HTML (<div>, <script>, etc.) |
Tas helt bort. Ingen HTML passerar igenom. |
Bilder () |
Tas bort. Bildsyntax tas bort från utdata. |
Länkar ([text](url)) |
URL renderas som synlig klartext. Inte auto-länkad. Inte klickbar utan explicit användaråtgärd. |
| Farliga URL-scheman | javascript:, data:, vbscript:, file: — tas bort. |
| Iframes, embeds, objects | Tas bort. |
| HTML-entiteter | Avkodas till visningstecken endast om de är säkra. |
10.3 Implementation
Implementationer MUST använda en strikt allowlist-tolkare, inte en blocklist. Den rekommenderade metoden:
- Tolka Markdown med
pulldown-cmark(eller motsvarande). - Gå igenom AST:n och släng varje nod som inte finns i allowlist (§10.1).
- För länknoder: avge URL:en som synlig text, inte som ett klickbart
<a>-element. - Konvertera den filtrerade AST:n till en typad mellanrepresentation (t.ex. en
MarkdownNode-enum med endast säkra varianter). Rå HTML är strukturellt icke-representerbar i denna IR. - Rendera från den typade IR:n till mållagrets vy (t.ex. reaktiva vykomponenter, DOM-noder). Ingen HTML-strängkonkatenering eller
innerHTMLvid någon punkt.
Blocklist-metoder är ömtåliga eftersom nya Markdown-utvidgningar eller tolkningskvistar kan introducera ofiltrerade element. Den typade AST-metoden gör XSS strukturellt omöjligt — det finns ingen variant som kan bära godtycklig HTML.
10.4 Storleks- och strukturgränser
- Maximalt renderad rubrikdjup:
####(H4).#####och djupare renderas som fet text. - Ingen gräns på antal stycken (kroppsstorleksgränser i §6 är begränsningen).
- Fenced kodblock: ingen syntaxmarkering i MVP. Renderas som monospace preformaterad text.
11. Verifiering av tredje part
Varje tredje part som innehar de lagrade bytena (och K för en privat/inkapslad qub) kan verifiera den kryptografiska artefakten utan qub:s samarbete. Självständigt tidsstämplad existens krav kräver dessutom antingen verifierad per-qub permanent-lagringsinkludering eller ett verifierat §16 transparensloggbevis.
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.
Vad verifiering bevisar:
| Bevisinmatning | Vad det fastställer |
|---|---|
| Giltig paket / förseglad artefakt + drand-signatur | Det återfunna liket matchar body_hash; metadata bundet in qub_id är intakt; chiffertexten är bunden till den deklarerade drand-rundan; och den rundan har förflutit. Detta gör inte fastställa när chiffret skapades. |
| Giltig V2-författares/medunderskrivares signatur | Innehavaren/inhavarna av motsvarande hemliga nyckel/nycklar autentiserade den signerade ytan i §9.3. |
| Självständigt verifierad per-qub-lagringstransaktion | Den exakta lagrade chiffertexten fanns senast vid dess blocktidsstämpel. |
| Giltigt förankrat transparensloggbevis | Blad-typ-specifik påstående i §16.11, inklusive en övre gräns för åtagandetid från ankarsblocket. |
Vad verifiering INTE bevisar:
| Icke-bevis | Varför |
|---|---|
| Författarskap | Den sender_label är dekorativ. Utan sig_alg ≥ 0x01, vem som helst kunde ha förseglat detta innehåll. |
| Avsikt | Artefakten bevisar bytes och kryptografiska relationer, inte vad skaparen subjektivt menade. |
Förhandsåtagande från .qub ensam |
En skapare kan sätta ihop ett giltigt paket efter att den bundna rundan har förflutit. Den inbäddade drand-signaturen bevisar att rundan förflutit, inte att chiffret existerade före den. |
| Exakt sigill-knappstid | En lagrings- eller förankringsblockstidsstämpel är en oberoende verifierbar övre gräns och kan ligga efter användarens lokala handling. sealed_at / received_at påståenden är icke-bevisande. |
Den implementerade transparensloggen (§16) utökar verifiering över qubs med
manipulationssäker beställning och en förtroendelös övre-gräns åtagandetid (det
ankare blocktid), avgränsad efter bladsort (§16.11). Det lägger inte till författarskap eller
avsikt; för den standard byte-blinda uppladdningsvägen bevisar det i sig självt inte
body_hash eller drand_round, som fortsätter att komma från artefaktkontrollerna.
12. Versionshantering och releasekontroll
Dokumentutgåvor, den inre trådprotokollet och det yttre omslaget är separata versionsutrymmen. Enbart ett dokumentförtydligande gör därför inte tyst ändra bytes, och en framtida överföringsmigrering kan inte utge sig för att vara en redaktionell revision.
12.1 Dokumentutgivningsversion
Denna specifikation använder semantiska dokumentutgåvor (MAJOR.MINOR.PATCH) och
en oföränderlig Git-tag som heter protocol-v<release>.
- LAPP: noggrannhet eller redaktionell korrigering som inte ändrar överensstämmande byte eller krävd funktionalitet.
- MINDRE: bakåtkompatibel normativ tillägg, ny registerpost eller nytt självständigt versionerat sidovagnsformat.
- MAJOR: inkompatibel normativ förändring, inklusive en ny obligatorisk ledningsinterpretation.
Utgivningsstatus är en av Utkast (ännu inte normativ), Aktuell (den enda
rekommenderat implementeringsmål), eller Ersatt (behållen för historiska
verifiering). Den oversionerade /protocol rutten visar den aktuella versionen;
utgivningstaggen bevarar dess exakta källa och varje lokalisering som publicerats med den.
Att ändra status eller versionsnummer kräver att denna tabell och utgåvan uppdateras
historia i samma granskade ändring.
| Dokumentutgivning | Ikraftträdandedatum | Status | Trådprotokoll | Förpackning | Källa |
|---|---|---|---|---|---|
| 1.0.0 | 2026-09-23 | Aktuell | 0x01 |
0x01 |
protocol-v1.0.0 |
12.2 Protokollversion
Den version fält (u8) i båda SealedQub och QubEnvelope identifierar den största protokollversionen.
- Tittare MÅSTE avvisa okända större versioner med ett tydligt felmeddelande.
- Inom en känd huvudversion MÅSTE dekodare avvisa okända kartnycklar (§3.1) — schemautveckling sker genom att införa en ny
version, inte genom att lägga till nycklar som befintliga avkodare skulle hoppa över. (Tidigare versioner av denna specifikation tillät att okända valfria fält kunde tolereras; den klausulen har dragits tillbaka — det gjordeencode(decode(x))icke-injektiv och öppnade en dold signerad-innehållsvektor på PACT-nyttolaster.) - Innehållstyper (
content_type) och signaturmetoder (sig_alg) är versionsbegränsade: nya värden kan endast introduceras tillsammans med en ny protokollversion eller en uttrycklig registeruppdatering.
12.3 Protokollversionshistorik
| Version | Värde | Beskrivning |
|---|---|---|
| v1 | 0x01 |
Privat/inlindad och offentlig/obearbetad leverans; text (0x01), pakt (0x03), och dom (0x04) kroppar; ML-DSA-65 V2 författare/medundertecknare som signerar; drand quicknet tlock; SHA3-256. |
12.4 Framåtkompatibilitet
En v1-visare som stöter på ett QubEnvelope med okända CBOR-mapnycklar (nycklar som inte är i §3.2 kanonisk ordning) MÅSTE avvisa det med ett avkodningsfel (§3.1). Framåtkompatibilitet beror på version fält, inte på nyckeltolerans: framtida tillägg — även mindre metadata — levereras under en ny version värde, vilket en v1-visare avvisar med ett tydligt "nyare protokoll"-fel istället för att tyst släppa innehåll som signaturerna förbinder sig till.
En v1-visare som stöter på sig_alg = 0x01 (ML-DSA-65) men utan ML-DSA-65-verifieringsstöd SKA visa qub-innehållet med ett meddelande om 'signatur närvarande men ej verifierbar', inte avvisa qub helt. Referensimplementeringen idag avvisar varje sig_alg värde annat än 0x00 och 0x01 eftersom v1-registret inte innehåller någon annan giltig algoritm — strikt avvisning och mjuk-fel är observerbart identiska tills en tredje algoritm registreras. Mjuk-fel-beteendet ovan blir bärande först när §9.2 medger en ny post, och referensvisaren kommer att uppdateras till mjuk-fel vid den tidpunkten.
12,5 Yttre omslag version
Den yttre omslagaren som beskrivs i §13 bär sin egen version byte, oberoende av SealedQub.version och QubEnvelope.version. De två versionsutrymmena utvecklas separat: en framtida post-quantum-säker symmetrisk ersättning höjer wrapper-bytet utan att röra den inre protokollversionen, och ett framtida protokoll-lager-tillägg (t.ex. ett nytt kuvertfält) höjer den inre versionen utan att röra wrapper-bytet.
OUTER_WRAPPER_VERSION_* |
Värde | Algoritm | Status |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM med 12-byte-icke, 16-byte-autentiseringstag, AAD bunden till qub_id |
Aktiv för privat leverans |
| — | 0x02–0xFF |
Reserverad | Framtid |
Tittare MÅSTE avvisa okända wrapper-versioner med ett tydligt fel. Protokollet håller avsiktligt wrapper-versionsutrymmet smalt tills en konkret migrationsdrivare uppstår (t.ex. NIST-riktlinjer som förespråkar en annan AEAD); a 0x02 En plats kommer att tilldelas i samma revidering som introducerar algoritmen.
13. Yttre krypteringsomslag
13.1 Motivering
Protokollskikten (QubEnvelope → tlock → SealedQub) gör en förseglad qub tidslåst: kroppen är oläsbar fram till unlock_at och drand-rundsignaturen har publicerats. Efter upplåsning är emellertid rundsignaturen offentlig och den kanoniska CBOR-formen för SealedQub är igenkännbar, så en skördare som indexerade transaktioner i permanent lagring skulle kunna massdekryptera hela qub-korpusen.
För privat leverans stänger det yttre krypteringsskiktet den kanalen genom att infoga ett ytterligare symmetriskt AEAD-lager mellan det kanoniska SealedQubCbor och de lagrade bytearna. I webbläsar-sealvägen, 256-bitars nyckeln K liv endast i URL-fragmentet av leverans-URL:en och på användarenheter; webbläsare skickar inte URL-fragment till servrar, så qub.social, varje lagringsgateway och varje CDN framför någon av dem är observationsmässigt blinda för K. En privat qubs lagrade representation är därför ogenomskinlig chiffertext vars klartext är oåterkallelig utan den URL som skaparen valt att dela. Offentlig leverans utelämnar medvetet detta lager (§13.8).
Nettoeffekt:
- Uppräkningsmotstånd för privat leverans.
OuterWrapperförblir igenkännbar strukturerad CBOR—det är inte bokstavligen omöjligt att skilja från slumpmässiga byte—men dess krypterade fält döljer det igenkännliga inreSealedQubform. Den dokumenterade skördestrategin för "GraphQL-fråga för nakna qub-formade uppladdningar, mass-dekryptera med offentliga drand-signaturer" avslutas inte med klartext utan K. - Krypto-nedbrytningsekretesshållning för standardflödet i privat webbläsare. qub.social kan inte dekryptera de lagrade artefakterna från dess standardserverdata. Explicit återställning, offentlig leverans och betrodd server-side försegling har olika avslöjade förtroendegränser.
- Tvåstegs sekretessstege. Standard = länkstyrd åtkomst (detta avsnitt). Mottagar-krypterade privata qubs (en reserverad Fas-2-funktion, ännu inte specificerad) lägger sig ovanpå som andra nivån.
13.2 Skiktning
plaintext body ← QubEnvelope.body (§2.2)
↓ canonical CBOR (§3)
envelope CBOR
↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
↓ canonical CBOR (§3)
SealedQubCbor bytes ← inner wire artifact
├─ 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
Försegling och upplåsning vid protokollskiktet (§7, §8) är oförändrade under omslagsgränsen; omslaget fästs vid anropsplatsen för seal() och tas bort vid anropsplatsen för unlock().
13.3 Datastruktur för OuterWrapper
struct OuterWrapper {
version: u8, // 0x01, see §12.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
}
Fältinvarianter.
versionMÅSTE vara lika0x01för v1.0 wrapper-bitar.qub_idMÅSTE vara lika medqub_idfältet av SealedQub återvunnen efter att ha packats upp. Båda referenserwrap_sealed_qubochunwrap_sealed_qubanalysera den inre CBOR och upprätthålla denna likhet direkt; AAD-bindningen gör separat efter-omslagsmanipulation med det yttrequb_idautentisering misslyckades.nonceMÅSTE vara 96 bitar (12 byte), genererade färskt av en CSPRNG för varje wrap-operation. Att återanvända ett nonce under samma nyckel tillåter AEAD-nonce-återanvändningsattacker som återställer klartexten; producenter MÅSTE behandla (key,nonce) paras ihop som one-shot.ciphertextär AES-256-GCM-utdata: chiffertextbyte sammanfogade med den 16-byte autentiseringstaggen.ciphertext.len() == SealedQubCbor.len() + 16exakt.
CBOR-kodning. Kanonisk CBOR enligt §3, med samma nyckelordningsregel (sorterad efter kodad bytelängd stigande, sedan lexikografiskt). De fyra nycklarna är:
| Nyckel | Kodade byte | Ordning |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
Den första byten av OuterWrapper-CBOR:en är därför det bestämda kart-headern för en 4-posters karta (0xA4).
13.4 AAD-bindning till qub_id
Omslaget binder qub_id som AEAD ytterligare autentiserade data. Detta är det bärande strukturella försvaret mot tre attackklasser:
| Attack | Försvar |
|---|---|
Flytta chiffertext under en annan qub_id fält i omslaget |
AAD-mismatch → AEAD-autentisering misslyckas |
| Blanda URL-fragmentet av qub A med de lagrade bytena av qub B | Fel nyckel (och oberoende bunden AAD) → AEAD-autentisering misslyckas |
Blandas med qub_id fältet för omslaget efter uppladdning |
AAD-mismatch → AEAD-autentisering misslyckas |
Att bära qub_id i omslagets klartext försvagar inte uppräkningsimmuniteten meningsfullt — qub_id är själv en SHA3-256-hash av §4.1-pre-imagen utan återställbar pre-image från sammanfattningen, och en uppräknare som redan skördade omslagsbyten lär sig inget från det synliga qub_id som de inte kunde sluta sig till från själva uppladdningens existens.
13.5 Inslagnings- och uppackningsalgoritmer
wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
require K.len() == 32 and N.len() == 12 and Q.len() == 32
I := canonical_cbor_decode(S) as SealedQub
require I.qub_id == Q // reject mismatched caller AAD
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.5
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
S := canonical_cbor_decode(P) as SealedQub
require S.qub_id == O.qub_id // explicit inner/outer cross-check
return P // P is the validated SealedQubCbor
Kollaps av felläge. Fel K, fel nonce, AAD-omatchning och manipulerad chiffertext producerar alla samma DECRYPT_FAILED-fel. Detta är en avsiktlig AEAD-egenskap: att skilja felläget skulle skapa en sidokanal som en fjärrangripare kunde sondera genom att skicka felformade omslag och tida svaret. Referensimplementationer MUST kollapsa alla AEAD-fel till en enskild felform.
13.6 Nyckelmaterial och distribution
Inslagningsnyckeln K är ett 256-bitars enhetligt slumpmässigt värde som genereras per qub av en CSPRNG. Referensimplementationerna hämtar det från:
- WASM-skapare:
getrandom(WebCrypto underwasm_js-backenden). - Serversidig förseglings-API-anropare: dess lokala CSPRNG; anroparen tillhandahåller och behåller
Ksomwrapper_key_b64url. Workern använderKi minnet för omslaget men MUST NOT lagra den. Detta gör att ett idempotent återförsök kan återhämta ett maskat svar med anroparens behållna kapabilitet i stället för att bero på en servergenererad engångshemlighet.
Distribution: K MUST kodas som URL-säker base64 (RFC 4648 §5, ingen utfyllnad) och läggas till leveranslänken som fragmentkomponenten:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragmentet överförs aldrig till någon server av en konform webbläsare. Återställningskanaler (serverside-historikindex, opt-in automatisk e-postsändning) som persisterar den fullständiga leveranslänken — inklusive fragmentet — bortom användarens enhet är en uttrycklig avvägning mot den standardmässiga krypto-strimlings-hållningen och MUST gating på explicit användarsamtycke.
Fragmentförlust. Om en användare förlorar URL-fragmentet och inte har någon återställningskanal är qubben oläsbar. Detta är den bärande avvägningen i designen och MUST avslöjas för användaren vid förseglingstid. MVP:n förstärker förseglingstidsavslöjandet med uttrycklig "spara denna URL"-text och en verifierad-e-poståterställningskanal för användare som väljer att delta.
13.7 Utanför omfattning för denna sektion
- Författarskap påteckning (§9) är oförändrad: signaturer beräknas inuti den inre
QubEnvelopeoch återvinns efter att ha packats upp → tlock-dekrypteras → CBOR-parsas. - Mottagarens offentliga nyckelkryptering (den reserverade
recipient_pubkeyfält) är en framtida funktion som skiljer sig från dagens privata, länk-förmågebegränsade wrapper-läge. - Det nuvarande server-sidans paktmedsigneringsflöde sänder ut public/bare
SealedQubCbormed synlighet0x01; det kan inte uppfylla webbläsarens K-hemlighetsmodell eftersom slutlig försegling sker efter servermedierad medunderskrift. En framtida privat-avtalstillverkare kan använda samma förpackning, som är bytessluten för den inre innehållstypen.
13.8 Offentliga qubs (utelämnande av omslag)
Det yttre höljet är valfritt på leveranslagret. En skapare kan försegla en qub som offentlig, i vilket fall den kanoniska SealedQubCbor går in i lagringspipeline direkt, utan OuterWrapper lager och ingen nyckel K:
SealedQubCbor bytes ──(public)──▶ stored as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored
En offentlig qub är tidslåst men inte länkbegränsad: det förblir oläsbart tills dess dragna rundpublicering (tlocklagret är oförändrat), men efter upplåsning kan vem som helst som har lagringstransaktions-ID:t dekryptera det — ingen URL-fragment behövs, eftersom det inte finns någon K. Detta är den avsiktliga handeln för ytor som servern måste driva: avslöja-meddelande e-post, fragment-fria oEmbed/auto-embed-länkar och rikare sökmotoroptimering efter avslöjande behöver alla en länk som fungerar utan en hemlighet som servern aldrig håller (§13.6). En privat qub kan fortfarande använda den explicita <qub-embed src="full_delivery_url"> form när utgivaren tillhandahåller sin fullständiga fragmentbärande kapacitet.
Följder som en producent MÅSTE ta hänsyn till:
- Ingen uppräkning immunitet. Offentliga qubs avstår från §13.1-uppräkning-immunitetsegenskapen genom konstruktion. Referensuppladdningstjänsten stämplar en
Visibility: publicpermanent-storage-märke på dem (och endast dem) så att de är avsiktligt upptäckbara; privata qubs har inget sådant märke och behåller sin byte-odifferentierbarhet. - Okrypterad titel exponerad vid förseglingstid. §3.2
titlefält är oformaterad text inutiSealedQubCbor. Under omslaget är det dolt tills en tittare tillhandahållerK; utan förpackningen är den världsläsbar på permanent lagring från det ögonblicket av uppladdning, innan upplåsning. Följsamma skapareappar MÅSTE avslöja detta vid förseglingstidpunkten. - Upptäckt är strukturell och dubbelkontrollerad. En konform visare/inbäddning skiljer de två sparade formerna åt genom tolkning: byte som tolkas som
OuterWrapperta bort-Ksökväg; bytes som tolkas som en barSealedQubCboraccepteras direkt. Det återvunna inre värdet MÅSTE överensstämma (0x00för inlindad/privat,0x01för bar/offentlig).qub_idbinder inte synlighet, men den kanoniskaSealedQubbytes gör att det bärs, så de offentliga och privata interna kodningarna är inte byte-identiska.
Privat (inslaget) förblir standard; offentligt är ett uttryckligt skaparval per qub.
14. Testvektorer
14.1 qub_id-härledning
Input:
version = 0x01
content_type = 0x01
created_at = 1735689600 (2025-01-01 00:00:00 UTC)
unlock_at = 1736294400 (2025-01-08 00:00:00 UTC)
outcome_at = absent
drand_round = 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
Implementeringar MÅSTE producera identiska body_hash och qub_id värden för denna indata. Denna testvektor SKA vara det första enhetstestet som skrivs. De kanoniska värdena ovan beräknades av referensimplementeringen och MÅSTE matcha bit för bit. Historiska prototyplayouter före lansering (inga aktiva qubs berodde på de första två) använde 92 byte innan outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) och 100 byte efter att ha lagt till outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Den nuvarande 108-byte layouten lades sedan till drand_round och den QUB_ID_V2 domänseparator. En tidig 108-byte vektor använde den äldre ceil rund kartläggning (drand_round = 4695445) och producerade 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—fortfarande giltig qub_id för det där runda inmatningen, medan exemplet ovan följer §4.3 nuvarande rundkartläggning.
14.2 Lås upp-runda mappning
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
Runda 4675286 publiceras vid 1595431050 + (4675286 - 1) * 30 = 1735689600—precis vid unlock_at, aldrig tidigare. (Den förhandsversion av Legacy ceil mappade 4675285, publicerad vid 1735689570—30 sekunder tidigt; verifierare accepterar den äldre rundan enligt §4.3.)
14.3 Kanonisk CBOR-rundresa
Implementationer MUST verifiera att serialize(parse(serialize(qub))) == serialize(qub) för alla giltiga indata. Detta är ett egenskapstest, inte en enskild vektor.
14.4 PactTerms CBOR (content_type 0x03)
Input:
pact_version = 1
title = "Scooter deposit"
terms = [
{ key: "Item", value: "Honda Metropolitan scooter" },
{ key: "Price", value: "$100" },
{ key: "Deposit", value: "$10" }
]
party_a = { label: "Alice" }
party_b = { label: "Bob", contact: "bob@example.com" }
notes = absent
Canonical CBOR key order (PactTerms):
"notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)
Canonical CBOR key order (PactTerm):
"key"(4) < "value"(6)
Canonical CBOR key order (PartyIdentifier):
"label"(6) < "contact"(8)
De kanoniska CBOR-byten och SHA3-256 body_hash beräknas av referensimplementationen. Implementationer MUST producera byte-identisk CBOR för denna indata.
Implementationer MUST också verifiera att serialize(parse(serialize(pact))) == serialize(pact) för alla giltiga PactTerms-indata (egenskapstest).
14.5 Tvärspråkliga vektorer för yttre omslag
Det yttre omslaget (§13) har en separat kanonisk fixtur i crates/qub-core/tests/vectors/wrapper_v1.json. Varje fall fixerar en tupel (key, nonce, qub_id, sealed_cbor) som ogenomskinlig hex-indata och hävdar en specifik expected_wrapper_hex-utdata. Båda referensimplementationerna konsumerar samma JSON-fil:
- 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 kopplar för närvarande tre låg-nivå wrapper-fall. De testar deterministisk OuterWrapper kodning och AEAD-interoperabilitet oberoende av §13.8-leveranstypinvarianten; särskilt det historiska namnet basic-text-public och dess inre visibility = 0x01 göra inte gör de resulterande omslutna bytesen till en förenlig offentlig leverans. En producent MÅSTE fortfarande lagra offentliga inre bytes öppet och omsluta endast privata (0x00) inre bytes.
| Fall | Täckning |
|---|---|
basic-text-public |
Historiskt låg-nivå armaturnamn. Minsta realistiska SealedQub form, utan valfria fält; testar bara omslagsbytes och är inte en överensstämmande §13.8 lagrad leverans. |
with-recipient-pubkey |
SealedQub med recipient_pubkey sätt (reserverad framtida väg). Använder en annan intern CBOR-nyckeluppsättning; dess distinkta fixeringsinnehåll ger oberoende ett annat qub_id (recipient_pubkey självt är inte i §4.1-förebilden). |
longer-body |
~4 KiB kropp — övar multi-byte CBOR-längd prefix både i det inre kuvertet och den yttre chiffertexten. |
Implementationer MUST producera byte-identiskt expected_wrapper_hex för de registrerade indata. Att regenerera fixturen kräver QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors och är reserverat för avsiktliga formatändringar.
15. Styrning av kryptoprofil (framtid)
Denna sektion är informativ för v1 och blir normativ första gången en andra algoritm träder in i någon av qubs kryptografiska primitiver.
15.1 Nuvarande hållning
Protokoll v1 binder exakt en algoritm per primitiv:
- Signatur: ML-DSA-65 (
sig_alg = 0x01; 1952-byte publik nyckel, 3309-byte signatur) och osignerad (sig_alg = 0x00). Kodbasen reserverar0x02för Ed25519, men protokoll v1 aktiverar det inte; en v1-verifierare MÅSTE avvisa varjesig_algutanför{0x00, 0x01}. - Tidslås: drand quicknet endast — kedjehashen, offentliga nyckeln, genesis-tiden och perioden är fasta nätverksparametrar som följs av referensen
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) ochconfig/drand-endpoints.json. - Ytterhölje: AES-256-GCM v1 endast (§13).
Verifierare hårdkodar för närvarande nyckel- och signaturlängder per aktiv primitiv. sig_alg och wrapper-version-byten är explicita selektorer, men v1 utför ingen intern förhandling och tillåter endast de aktiva värdena ovan.
15.2 Avsedd form
När en andra algoritm träder in i protokollet kommer verifieraren att konfigureras för en namngiven CryptoProfile (t.ex. ExqubV1) som listar den exakta uppsättningen tillåtna värden per primitiv — sig_algs, drand-kedjor, omslagsversioner, innehållstyper. Profilen fastställs vid verifieringstid, aldrig förhandlas i bandet. Varje värde utanför den aktiva profilen avvisas.
Detta garanterar att tillägg av ML-DSA-87 eller aktivering av Ed25519 inte retroaktivt kan försvaga befintliga verifierarkonfigurationer: en v1-verifierare förblir en v1-verifierare även efter att en v2-profil publiceras.
15.3 Utlösningsvillkor
Befordra §15 till normativ status när något av följande föreslås:
- En sekund
sig_algbyte (Ed25519-aktivering, ML-DSA-87, eller någon ny post i §9-registret). - En andra drand-kedja i produktionsanvändning.
- En andra ytterinpackningsversion.
- En rotation av transparensloggens förtroenderot —
LogProfile.anchor_owneradress eller den fastnålda kvitto-nyckelns offentliga nyckel (§16.6). DenLogProfileansluter till §15.2-profilytan som en styrd primitiv: en rotation är signeradLogProfilebump skickades i en verifier-uppdatering (planerade rotationer cross-signerar utgående → inkommande; kompromissdrivna rotationer kan inte, och förlitar sig på denna bump med prev-anchor fork-kontroll som begränsar interimsskador). Versionutrymmena i transparensloggen (LOG_VERSION,ANCHOR_FORMAT) utvecklas som oberoende syskon, precis som §12.5-wrapperversionen är oberoende av protokollversionen.
Tills dess är §15 en platshållare som fastställer migrationsformen så att framtida PR:er landar mot ett känt mål snarare än att åter-förhandla förhandlingsytan från grunden.
16. Transparensslogg och hållbarhetsnivåer (Design — granskning klar)
Status. Den här sektionen är implementerad (W5/UP-B1, Stadier 1–8), med producent- och förtroenderotens omfattning angiven här. Trådförmaken, hashning och verifieringsvägar är aktiva: kärn-Merkle + kanoniska CBOR-typer (
qub-core), TypeScript-spegeln + ANS-104-bundlern (workers/api/src/crypto/), den ensamförfattarenLogDO+ koordinatnyckel-R2 nodlagring, den/uploadlogg-tilläggsförsök, den dagliga ankaren + bundler-dränerings-crons, denGET /api/v1/qub/:tx_id/proof(inkludering) ochGET /api/v1/log/consistency(RFC 9162) bevisändpunkter, det typade inklusionsbeviset som bärs i.qubpaket (§17.5), den inhemska ANS-104 ankarkontrollanten (tools/qub-verify), och den dubbla egenutgivna-huvudkroken (§16.6). En framgångsrik/uploadär alltid R2-beständig men är täckt av loggar endast närLOG_DOär konfigurerad och den inbyggda tillägget lyckas; först då bär dess svarlog_seq,receipt, ochanchor_status. OmRECEIPT_SKär frånvarande eller ogiltig, det kvittotsig_b64urlär tom och ger ingen bevisning mot förnekande. Den nuvarande/sealoch pact-publiceringsvägar schemalägger individuella Arweave-transaktioner men lägger inte till ett logblad. Ingen kod utför för närvarande/uploadkommenterar det föreslagna senare försoningsförslaget efter ett tilläggsfel. Den externa granskningen W5 är slutförd: §16.15 dokumenterar designbeslut och lanseringsbegränsningar, men dessa begränsningar utökar inte den tidigare nämnda producenttäckningen. Tre förtroende-/distributionsobjekt förblir spärrade: (a) den dedikerade ankarlådan (ANCHOR_JWK;LogProfile.anchor_ownerär fortfarande den[0xAB; 32]platshållare); (b) kvittosigneringsnyckeln och matchande publikt nyckel-pin (RECEIPT_SKär valfritt ochLogProfile.receipt_pubkeyär för närvarande tom); och (c) det självpublicerade-heads GitHub-förrådet + token (§16.6). Tills ankaret/profilpinnarna är tillhandahållna, rapporterar en fristående verifierare bevisstatus ärligt istället för att hävda en fullt förankrad, pinnad verifiering. Designen är strikt additiv och det finns ingen ändring tillSealedQub/QubEnvelopetrådfomat.
16.1 Motivering och hållbarhetsnivåer
De nuvarande publiceringsvägarna separerar bekräftelse från Arweave-bekräftelse: de härleder och signerar en individuell transaktion, sparar artefakten och det exakta inskickningstillståndet i R2, och skickar sedan asynkront. Transparensloggen lägger till ett oberoende förankrat ordningslager för delmängden av allmänheten /upload förfrågningar vars LogDO append lyckas:
| Nivå | Namn | Garanti | När |
|---|---|---|---|
| T1 | R2-första synkrona kvittens | Hållbarhetsnivå — förseglade bytes och exakt publiceringsstatus skrivs till hållbart lagringsutrymme innan framgång returneras. | Implementerat över nuvarande publiceringsvägar. |
| T2 | Partiell transparens-logginkludering | Endast-tillägg, manipulationssäker förpliktelse + total ordning när den väl är inkluderad och förankrad. | Nuvarande producent: framgångsrik LogDO läggs till från /upload; svaret innehåller kvittotuppeln. Inte universellt. |
| T3 | Per-qub Arweave permanenthet | En individuell Arweave-transaktion för qub. | För närvarande förberedd för varje accepterad publikation och skickad asynkront; den exakt signerade transaktionen förblir i den tömbara utkorgen tills den levereras. |
Nivåerna beskriver olika bevis- och hållbarhetsegenskaper, inte den nuvarande kommersiella planen. Den nuvarande koden schemalägger fortfarande en individuell Arweave-transaktion för varje accepterad publikation; den exponerar inte T3 enbart som en betald uppgradering. Gränser för API-nyckel/kontokvoter förblir separata applikationskontroller.
Hållbarhet ärlighet. T1-skrivningen är synkron, så ett lyckat svar etablerar hållbarhet på applikationsnivå utan att vänta på en Arweave-gateway. Den etablerar inte i sig själv en oberoende tidsstämpel. En bekräftad individuell transaktion ger dess blocktidsövre gräns. För ett svar som bär hela T2-kvitto-tuplen kan nästa bekräftade ankare tillhandahålla loggbeviset som beskrivs nedan. Om tuplen saknas kan ingen yta antyda att denna qub redan finns i transparensloggen. Ankarets och publikationslatensen har ingen numerisk SLA på protokollnivå.
16.2 LogLeaf-struktur (två åtagna former)
En loggpost är ett LogLeaf, kodat som handskriven kanonisk CBOR enligt §3.1-profilen (bestämd längd, inga taggar, inga flyttal, kortast-formade heltal, NFC-text, frivilliga fält utelämnade när de saknas, nycklar ordnade efter kodad bytelängd stigande sedan lexikografiskt). Den kanoniska parse → re-encode → compare-vakten från §3.1 tillämpas på kodningsvägen före hashning (inte bara vid avkodning), så att två implementationer inte kan vara oense om leaf-byten genom en heltalsbredd- eller nyckelordningsskillnad. Alla heltal är u8 / u64 / i64; alla sammanfattningar är 32-byte bytesträngar (bstr[32]). Ett lagrat Arweave-transaktions-id är en rå 32-byte SHA-256-sammanfattning som bärs som bstr[32], aldrig en base64url-textsträng (matchar §3.3).
Bladet har två former valda av en kind change, eftersom den allmänna uppladdningsvägen är byte-blind: POST /api/v1/upload behandlar medvetet båda accepterade lastformer som ogenomskinliga och tar emot qub_id och unlock_at endast som otrovärdiga klientpåståenden. På standard privata sökvägen, body_hash, drand_round, created_at, och drand_chain_version är dessutom dolda inuti §13 yttre omslag, vars nyckel Arbetaren aldrig håller. Typystemet definierar också en intygad form för en producent som härleder body_hash / drand_round självt. Den nuvarande /seal rutten har de värdena men anropar inte LogDO, så produktionen släpper för närvarande endast ut bekräftad (0x02) lämnar från framgångsrik generell uppladdning bifogas. Uppdelningen håller varje åtaget värde ärligt utan att låtsas att den intygade producenten är kopplad:
| Nyckel | Bif. längd | Typ | Närvaro | Betydelse |
|---|---|---|---|---|
seq |
fyra | u64 |
krävs | Global 0-baserad lövindex; den position som inklusionsbeviset förbinder sig till. |
kind |
fem | u8 |
krävs | 0x01 bevisad-kapabel (definierad, inte för närvarande utsänd) eller 0x02 påstådd (klient-sigill / byte-blind uppladdning). |
ref |
fyra | bstr[32] |
krävs | Bladreferens-id. Intygad → rå qub_id. Påstådd → the bländad id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
sex | bstr[32] |
krävs | Innehållsadress SHA3-256(stored_bytes) — det enda innehållsband som Arbetaren alltid kan beräkna ärligt, på båda vägarna. |
unlock_at |
tio | i64 |
krävs | Kopierad (intygad) eller påstådd (påstådd); validerad > 0 innan det går in i bladet. |
received_at |
tolv | i64 |
krävs | Arbetsklocka vid R2-ack. Icke-bevisande (operatörsintygat; §16.6). Närvarande för självbeskrivning, aldrig ett bevis. Validerad > 0. |
body_hash |
tio | bstr[32] |
kind=0x01 endast |
Utesluten på 0x02 — arbetaren saknar det enligt §13. |
drand_round |
tolv | u64 |
kind=0x01 endast |
Utesluten på 0x02. |
Ett kind=0x02-leaf åtar sig avsiktligt varken body_hash eller drand_round: det attesterar åtagandet och ordningen för en ogenomskinlig chiffertext på innehållsadressen chash, med påstående om qub_id och unlock_at — inte dess klartext eller runda. Klartext- och rundbenen för en påstådd qub kommer från den befintliga §11 .qub-buntverifieringen, inte från loggen (§16.11). drand_chain_version finns inte i leaf-strukturen (den ligger inuti omslaget på standardvägen); kedjegranulariteten lever på förankringen (§16.7). Kodningsdisciplin: avvisa ett enbart-nollor ref eller chash, och avvisa icke-positivt unlock_at / received_at, vilket speglar sentinelvakten outcome_at > 0 i cbor.rs.
16.2.1 Blindning av privata qubbar
Loggen får inte bli det uppräkningsorakel som det yttre §13-omslaget finns till för att förhindra (§13.1). För en privat (inslagen) qub åtar sig asserted-leaf-strukturen den blindade identifieraren SHA3-256(qub_id ‖ log_blind_secret), där log_blind_secret är en serverhållen hemlighet, och utelämnar body_hash. En tredje part kan inte knyta ett sådant leaf till ett specifikt qub_id; qubbens innehavare, som har leveranslänken och därför qub_id, kan beräkna om blindningen för att bekräfta sin egen inkludering. En publik qub (redan uppräkningsbar, bär redan Arweave-taggen Visibility: public enligt §13.8) åtar sig det råa qub_id. Detta är den enda platsen där fristående verifierbarhet avsiktligt ger vika för en bärande integritetsinvariant; den fristående kopplingen för privata qubbar är chash (§16.9).
Förvar av log_blind_secret (avgjort — §16.15 Q4). Blindningen skyddar leaf-okopplingsbarhet, inte klartextkonfidentialitet (det yttre §13-omslaget håller det oberoende). Vid en log_blind_secret-kompromiss, för varje qub_id som angriparen redan håller eller kan rekonstruera (varje qub vars bunt/URL den har, plus varje lågentropi- eller publikt qub_id), beräknar den om leaf-ref i en hash och kopplar det — detta är direkt koppling av en känd population, inte en uttömmande sökning över ett okänt utrymme. Klassificera log_blind_secret som en korrelations-/Sybil-gradig hemlighet i samma förvarsnivå som andra serverhemligheter, och rotera enbart framåt (en rotation blindar om framtida leaf-strukturer; den kan inte retroaktivt avkoppla redan förankrade).
16.3 Leaf- och nodhashning
RFC 6962 §2.1 domänseparerad hashning med SHA-256 utbytt mot SHA3-256:
leaf_hash = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree = SHA3-256("") // defined but never anchored
Domänprefixbyten 0x02 (postkedja, §16.4) och 0x03 (STH-hash, §16.6) är reserverade och disjunkta från dessa. De är enskilda byte och kan därför inte kollidera med de befintliga 10-byte ASCII-domänseparatorerna (QUB_ID_V2, etc.). Trädet är det RFC 6962 vänster-fulla obalanserade trädet (varje inre uppdelning vid den största tvåpotensen strikt mindre än subträdets leaf-antal), vilket låter inkluderings- och konsistensbevis dela en gemensam granskningsvägsalgoritm. Referensspecifikationen bär explicit vänster/höger-härledningspseudokod och nålar en icke-tvåpotens-(5-leaf-)testvektor så att höger-kantbefordringsfallet — som en 4-leaf-vektor döljer — utövas.
16.4 Hashkedjning (intern)
LogDO upprätthåller en intern postkedja enbart för kraschkonsistens. Den publiceras aldrig och är aldrig verifierarvänd:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
Den publicerade append-only-auktoriteten är den kumulativa Merkle-roten + dess förankring (§16.5–16.6), aldrig den råa ordning i vilken operatören råkar servera leaf-strukturer: kedjan beräknas om för varje serverad ordning, så endast den förankrade roten nålar den kanoniska positionen.
16.5 Kumulativt Merkle-träd och batchning
Det finns ett ständigt växande RFC 6962-träd över alla leaf-strukturer i seq-ordning — inte isolerade per-batch-träd. (En carry-leaf-kedjad per-batch-konstruktion avvisades: den är inte en äkta prefixrelation, så dess "konsistensbevis" är osunda.) Det kumulativa trädet ger äkta RFC 9162-konsistensbevis och låter en enda nyligen tillkommen förankring bevisa inkludering för vilken äldre qub som helst.
Den LogDO Durable Object är enskild författare (blockConcurrencyWhile, speglande QuotaDO / EntitlementDO) — att lägga till i en gemensam logg är läs-ändra-skriv på delat tillstånd och måste därför gå igenom en DO, aldrig KV. Det cachar trädets högra kantens gräns (O(log n) hashar) så att stänga en sats är O(batch). A parti är mängden av löv som är förankrade tillsammans; dess implementerade triggers är en tree_size förskott på minst LOG_BATCH_MAX_LEAVES (standard 4096), ålder når ankarkedans, eller en uttrycklig administrativ/cron tvångsavslutning. root_i är den kumulativa Merkle-trädhashen över löven 0 .. tree_size_i.
16.6 Signerat trädhuvud via Arweave-förankring
Arweave-förankringstransaktionen är det signerade trädhuvudet och ersätter en operatörssignatur för själva trädhuvudet: den dagliga förankringen behöver ingen qub-nyckel eftersom Arweave-transaktionens owner är signaturen. Vallgravstesen håller — det oföränderliga substratet, inte en qub-hållen hemlighet, är bärande för den förankrade roten.
Loggdesignen kräver en varm lyckad-tillägg kvittonyckel (§16.10), fastnålad i LogProfile och korssignerad av anchor_owner. Den nuvarande implementeringen har inte slutfört den förtroenderotstilldelningen: RECEIPT_SK är valfritt, en frånvarande/ogiltig nyckel ger sig_b64url: "", och den kompilerade LogProfile.receipt_pubkey är tom. Ett sådant kvitto kan beskriva det bifogade bladet men är inte ett icke-avvisbart signerat kvitto. Den starkare designpåståendet gäller endast efter att en verifieringsfrisläppare har låst den matchande offentliga nyckeln och ankarets ägare har korssignerat den. Ett publiceringssvar utan hela kvittotuplen gör inget logg-godkännandepåstående; ett med en tom signatur gör ett tilläggspositionspåstående men inget signaturverifieringspåstående.
SignedTreeHead är kanonisk CBOR (nycklar efter kodad längd): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (föregående sth_hash; genesis = 32 nollbyte), log_id:bstr[32], first_seq:u64, anchored_at:i64. Dess hash är sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Nålad förtroenderot. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). En konform verifierare MÅSTE kräva anchor_tx.owner == LogProfile.anchor_owner, där anchor_owner (och kvittonyckelns publika nyckel) är inbakad i qub_core som LogProfile — vid sidan av quicknet-konstanterna som redan finns i DrandTimelockProvider::quicknet() — och distribuerad med verifierarbinären. Verifieraren MÅSTE också verifiera Arweave-transaktionens data → tx_id-bindning lokalt snarare än att lita på ett gateway-/raw/-svar. Detta stänger hålet för olovlig-plånboks-tvetydighet: "förankrad på Arweave" är meningslöst tills verifieraren nålar vilken plånbok.
Rotation är en §15-styrningsutvidgning, inte ett återbruk (avgjort — §16.15 Q3). §15.2:s profilyta uppräknar för närvarande endast sig_algs / drand-kedjor / omslagsversioner / innehållstyper, och §15.3:s utlösare listar ingen av dessa — LogProfile / anchor_owner är ännu inte i §15:s yta. Rotationsstyrning måste därför byggas: §15.3 utvidgas (nedan) för att lägga till LogProfile-utlösaren, och en rotation är en signerad LogProfile-höjning som levereras i en verifierar-uppdatering. En planerad rotation bär en utgående → inkommande korssignatur; en kompromissdriven rotation kan inte (den utgående nyckeln är obetrodd/otillgänglig just då) och faller tillbaka till den §15-styrda höjningen, med föregående-förankrings-forkkontrollen (nedan) som begränsar skadan under mellantiden.
Tvetydighetsfönster (första klassens förtroendeparameter). Ett blad är endast motståndskraftigt mot tvekan när dess täckande ankare är Arweave-bekräftad. Fönstret är received_at → anchor confirmation (kadens + Arweave-finalitet, utan någon garanti för protokollfördröjning). Innan förtroenderotens provisionering tillhandahåller den nuvarande implementeringen qub:s operativa integritet samt vilken osignerad tilläggsmetadata som finns; den tillhandahåller inte den planerade garantin för icke-förnekande. Tre ansvarsskyldighetsartefakter definierar den fullständiga designen (vittnesmodellen är §16.15 Q2:s resolution):
- Säkerställ kvitto (beroende av försörjning) — SCT-analogen återvände när ett uppladdningslogg-tillägg lyckades (§16.10). Det blir icke-förnekbart endast när
sig_b64urlär icke-tom och det matchande offentliga nyckel-/ankare-ägarskapet är fastställt i verifieraren. Den för närvarande tomma produktionsprofilspinnen kan inte stödja det beslutet. Denna kontroll gäller inte för en utelämnad kvittotuppel eller ett osignerat kvitto. - Publicerad overvakar metodik + tidigare kedjevandring — ankaret
prevkedja gås huvud→genesis; en gaffel (två ankare på ensizemed olikaroot, eller en trasigprev) är publicerbart bevis på dåligt uppförande. Upptäckt av tvetydighet är ett uttalat operativt åtagande, inte ett tyst antagande. - Dubbla egenpublicerade huvuden — varje nytt huvud
{sth_hash, tree_size}är postad till en dedikerad qub-ägd offentlig, bara-tillägg GitHub-förråd (det bärande, manipulationssäkrade självpubliceringsbenet), med ett socialt inlägg endast som bästa möjliga bekräftelse. En misslyckad publicering MÅSTE sända sida (inte misslyckas tyst). Implementerad (Steg 8) sompublishHeadkrok på ankarkronan (workers/api/src/utils/heads-publish.ts): enPUTtill innehålls-API:t utan enshaär endast tilläggbar (en422betyder att huvudet redan är publicerat, aldrig en överskrivning); valfritt / distribueringsstyrt påPUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}och inaktiv tills lagringsplatsen tillhandahålls. Ett hårt GitHub-fel sidor viahealth_alertkanal och stöter på ett hållbart felmått (m:tlog_publish_head_fail); Arweave-anchoret självt rullar aldrig tillbaka vid ett publiceringsfel. "Misslyckas inte tyst" garanteras av den hållbara mätningen — vilket drift måste övervaka med dashboard-varning — även om e-postsidan som gör sitt bästa-införande inte kan levereras. Två ärliga begränsningar följer från "anchor-on-advance" (cron publicerar bara när storleken ökar): ett tillfälligt GitHub-fel lämnar en klyfta i den publicerade-huvuden-sekvensen för den storleken — begränsad, inte tyst (den paginerar), och eftersom varje huvud förbinder ett superset-träd, överbryggar ett §16.9-konsistensbevis gapet; avgörande är att det konsistensbeviset beräknas från auktoritativ Arweave-förankrad träd, inte från GitHub-yta, så ett GitHub-gap försvagar aldrig verifierbarheten. En upphämtnings-backfill som fyller publicerade-huvud-gap är en uppskjuten förbättring.
Ärlighet bunden (bindande begränsning). Eftersom qub kontrollerar båda de planerade publiceringsytorna, är detta självpublicerad, inte oberoende bevittnat. Ingen produkt, marknadsföring eller juridisk yta får påstå att loggen är "oberoende bevittnad". Efter att kvittot/profilen/huvudgrindarna har tillhandahållits är det tillåtna påståendet att tvetydighet är upptäckbar och en framgångsrikt signerad bilaga lämnar ett icke-förnekbart kvitto. Före dess är påståendet otillgängligt. Ett verkligt oberoende tredjepartsvederbörande vittne skjuts upp till en framtida §15 styrningsuppgradering.
received_at är operatörspåstådd och inget påstående får luta sig mot den — den ytas aldrig som bevis eller som tvistbekräftelse på någon produkt-/juridisk/API-/bevisåtergivningsyta. Arweave-förankringsblocktiden T är den enda förtroendefria tidsstämpeln (en övre gräns för "loggad senast"). Varje övervakningsförnuftskontroll på received_at MÅSTE jämföra mot T, inte mot det operatörskontrollerade STH-fältet anchored_at; en sådan kontroll är en vakt mot en hederlig operatörs klockfel enbart, inte en ansvarskontroll mot en illvillig operatör (§16.15 Q5).
16.7 Förankringstransaktionsformat och kadens
AnchorBundle är den kanonisk-CBOR Arweave-transaktionskroppen, skriven via §16.8-bundlaren: ver:u8, sth:bstr (kanoniska SignedTreeHead-byte), prev_anchor:bstr (föregående förankrings-transaktions-id i råa byte; utelämnad vid genesis), chain_hash:tstr (drand-kedjan i kraft — quicknet), och batchens leaf-CBOR-ström i seq-ordning så att förankringen är självständig: en övervakare härleder om root från kroppen med noll qub-beroende. (Om leaf-strömmen blir stor vid hög volym kan en framtida revision åta sig endast ett leaf-intervall genom referens; noterat, inte antaget i v1.)
Arweave-taggar är avsiktligt uppräkningsbara — loggen är menad att hittas, till skillnad från privata qubbar: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Taggar är obetrodda ledtrådar; CBOR-kroppen är den enda auktoriteten.
Kadens: dagligen som standard, återbesökt med volym (storleksutlösaren förkortar automatiskt den effektiva kadensen under belastning). Den nuvarande producenten implementerar inte en betald-sigill kraft-ankare krok. Den ankarlommebok är dedikerad och låg-hastighet, separat från uppladdningsplånboken — den MÅSTE vara dess egen JWK (en distinkt nyckel, inte en logisk roll på uppladdningsplånboken) så att ett intrång i uppladdningsplånboken inte kan förfalska ankare — med en strikt daglig budget för ankare-transaktioner. Förvaringsläget anges tydligt: en snävt avgränsad snabbknapp med en tät strömbrytare och låg balans, inte "kall" — en plånbok som automatiskt signerar dagligen kan inte vara kall, och specifikationen låtsas inte något annat.
16.8 ANS-104-bundlare
En egenutvecklad ANS-104 DataItem-kodare och deep-hash-signerare, ungefär 300 rader, enbart Web Crypto, noll npm-beroenden (båda Turbo-SDK:erna underkänns av §npm ci --ignore-scripts-leveranskedjegrinden). DataItem-bytelayout:
signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data
Signering är Arweave deepHash — en rekursiv SHA-384-sammanfattning (Arweaves trådkrav, crypto.subtle.digest("SHA-384")) över ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — sedan RSA-PSS över deep-hashen med plånbokens JWK via crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 här är karantänsatt som en enbart-Arweave-tråd-primitiv, aldrig en qub-förtroendeprimitiv (§15 dokumenterar stängslet; qub-förtroendehashning är SHA3-256 genomgående).
ANS-104-kodvägen tjänar den uppskjutna reserv/fördröjningsmekanismen och skriver AnchorBundle DataItems. Den vanliga publiceringsvägen skapar först en exakt signerad Arweave-transaktion och behåller dess JSON i en tålig utkorg; direktpublicering är en latensoptimering, och tömningsvägen försöker samma transaktion igen innan den tillämpar sin bundler-fallback. Signaturschema (lösts — §16.15 Q8): v1 signerar med RSA-PSS (signaturtyp 1) återanvända den befintliga Arweave-plånbokens JWK-mekanism (ingen ny långlivad nyckelhantering, som tjänar tesen "en hemlighet mindre"); Ed25519 skjuts upp till §15 PQ-migrationsväg.
Den handrullade deep-hashen är den högst riskfyllda, lägst naturligt täckta koden i W5, så dess grindning är icke-förhandlingsbar (§16.15 Q8):
- Den tvärspråkliga fixturen
tlog_v1.json(Rust + TS, §14.5-mönstretwrapper_v1.json) täcker deep-hash, DataItem-byte + id, leaf-hashar, en 5-leaf-rot + granskningsväg, en STH-hash, ett inkluderingsbevis och ett konsistensbevis — i både sign- och verify-riktningen (verify-riktningen är viktig eftersom §16.6:s lokala tx → tx_id-kontroll drar in deep-hashen i varje fristående verifierare, inte bara skribenten). - En engångs-interop-rundresa genom en referens-ANS-104-bundlare, konsumerad som enbart statisk testdata — aldrig ett npm-runtime-beroende (den enbart-Web-Crypto-/inga-installationsskript-hållningen står fast).
- Deep-hash- + RSA-PSS-vägen MÅSTE rundresa genom samma
crypto.subtle-primitiver som produktionen använder, så att den egenutvecklade kodaren är byte-kompatibel. - En pågående post-bunt-accept-övervakare bekräftar att varje förankrings-/fallback-DataItem faktiskt uppnår Arweave-accept, med ett larm + kretsbrytare — eftersom deep-hashen även betjänar fallback-kön vid Arweave-otillgänglighet, så att en tyst regression skulle fylla den kön med nätverksavvisade poster under just det avbrott den finns till för att täcka.
16.9 Inkluderings- och konsistensbevis
Båda är RFC 9162, SHA3-256, serverade som kanonisk CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (den exakta leaf-CBOR:en — verifieraren beräknar om leaf_hash själv och litar aldrig på en tillhandahållen hash), index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.
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 enda otvetydig nyckellista, nålad av testvektor.
Fristående verifiering (ingen qub-server, utvidgar §11):
1. Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2. Read leaf.kind.
3a. kind=0x01 (attested):
assert leaf.ref == qub_id
assert leaf.body_hash == SHA3-256(body)
assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
assert leaf.ref == SHA3-256(qub_id || blind) // holder supplies blind
OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4. Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
using index/size; require derived root == proof.root.
5. Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
(do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
LogProfile.anchor_owner.
6. Parse AnchorBundle; require committed root == proof.root and size ==
proof.size; read the Arweave block time T.
7. Emit the claim scoped by leaf.kind (§16.11).
Bevisserverande lagring MÅSTE vara koordinatnycklad (avgjort — §16.15 Q7, blockerande förutsättning). Kall-leaf-bevisgenerering är korrekthetsneutral endast om R2-granskningsmaterialet är ett persistent Merkle-nodlager nycklat efter absolut trädkoordinat (level, index) — inte per-batch-noddeltan. Med ett koordinatnycklat lager är varje (leaf i, size N)-granskningsväg en mängd O(log N) direkta R2-GET med ingen omberäkning över batchgränser; med ett batchnycklat lager är den det inte, vilket är den lagringslayoutlucka denna lösning stänger. Leaf-kropparna är likaledes innehållsadresserbara efter seq. En W5-testvektor MÅSTE bevisa ett genesis-era kallt leaf mot en mycket senare rot med enbart R2 + Arweave och LogDO-lagringen utraderad, så att återvinningssäkerhetspåståendet i §16.13 är underbyggt snarare än påstått. De O(log N) sekventiella R2-GET hör hemma enbart på den asynkrona bevis-endpointen — aldrig på förseglings-hotvägen (§16.10) eller en per-tick-cron.
16.10 R2-först-ack-ordning
Den implementerade POST /api/v1/upload sekvens är:
- Framhälftportar (auktorisation, validering, idempotens shard-nyckel) — oförändrad.
- Skapa, tagga och signera den exakta individuella Arweave-transaktionen. Detta härleds
tx_idlokalt, även om transaktionsskapande kan hämta belönings-/ankarmetadata från en gateway. Ett förberedelsefel misslyckas fortfarande med begäran innan bekräftelse. - Synkront skriv det valda artefaktet vid
qub-cache/<tx_id>och bevara de stabila skapande-/outbox-posterna. Detta är hållbarhets- och återförsökstaket; fel innan uppgörelse returnerar 503. - När
LOG_DOär konfigurerad, försöka synkrontLogDO.append(leaf). Den ensamstående författaren tilldelarseq, utökar inmatningskedjan och uppdaterar gränsen. Append RPC gör bara det; batchavslut körs off-path på larmet. Ett append transport-/applikationsfel är för närvarande fel-säkert: svaret kan fortfarande lyckas utanlog_seq,receipt, elleranchor_status. Trots en implementeringskommentar finns ingen automatisk senare loggavstämning kopplad idag. - Returnera bekräftelsen. Inkludera
{ log_seq, anchor_status: "pending", receipt }bara när append returnerade den fullständiga framgångsrika tuplen.receipt.sig_b64urlär tom när kvittosignatorn inte är tillgänglig; klienter FÅR INTE kalla det värdet signerat eller icke-avvisbart. Avsaknad av tuplen betyder endast hållbar publicering, inte godkännande i transparenslogg. - Använd en fördröjd uppgift för att skicka den exakt signerade transaktionen. Framgång tar bort utboxen; misslyckande lämnar den för den begränsade tömnings-cron och får inte förändra det som redan har bekräftats
tx_id. Provisorisk metadata och andra bästa möjliga sidokomponenter skjuts också upp.
Latensgräns. Förfrågningsvägen inkluderar front-halv auktoritet/kvotarbete, transaktionsförberedelse/undertecknande, hållbara R2-skrivningar och (när det är konfigurerat) LogDO försök. < 300 ms visas i designgranskningen som ett operativt mål, inte som en protokollgaranti; det nuvarande steget för transaktionsförberedelse kan utföra en gateway-metadatar förfrågan. Latenslarm och startgrindar är operativa kontroller, inte bevis tillgängliga för en verifierare.
16.11 Förtroendemodell — det exakta påståendet, omfångat av leaf-typ
För kind=0x01 (attesterad): "Detta innehåll — kropp som matchar body_hash, identifierad av qub_id — åtogs till qubs append-only-logg vid position seq och existerade senast vid Arweave-blocktiden T; det var kryptografiskt oläsbart fram till drand-rundan R = unlock_round(unlock_at)." Detta är den fullständiga {tlock-rundbindning + Merkle-inkludering + förankrad rot}-tripeln.
För kind=0x02 (påstådd, standarden): "En ogenomskinlig chiffertext med innehållsadressen chash, med påstående om qub_id och unlock_at, åtogs till append-only-loggen vid position seq och existerade senast vid Arweave-blocktiden T." Rund- och kroppsbenen tillhandahålls av den befintliga §11 .qub-buntverifieringen (qub_core::unlock), inte av loggen; det loggen tillför utöver en bar transaktion per qub är manipulationsdetekterande ordning, en förtroendefri övre-gräns-åtagandetid och tvetydighetsbeständighet.
Båda påståendena utesluter, enligt §11: författarskap utan sig_alg ≥ 0x01, avsikt, och sub-förankrings-granularitetstidsbestämning. Inget av dem låter något påstående luta sig mot received_at.
Begär tak (bindande lanseringsbegränsning — löst §16.15 Q1). För en påstådd (kind=0x02) bladet, den omfattade påståendet ovan är tak på vad som helst produkt-, marknadsförings-, villkors- eller bevis-renderingsyta kan hävda. Ingen yta får ange eller antyda att loggen bevisar innehållet eller upplåsningsrundan för en byte-blind uppladdning — loggen bevisar ordning + en förtroendelös övre gräns för åtagandetid för en ogenomskinlig chiffertext. Innehålls- och rundbevis kommer uteslutande från den befintliga §11 .qub-bundelverifiering, som är loggoberoende. En publikation utan lyckad tilläggning/mottagande har ingen logganspråk alls.
16.12 Versionshantering och W3-koordinering
Det finns nej SealedQub trådbula och därför ingen protokollversionsuppgradering (§12.2): loggen är en sidovagn som gör åtaganden till befintliga fält och byte, så den går inte in i §12.3-protokollversionshistoriken. W3:s valfria drand_chain_version är orörd och förblir den enda valfria SealedQub fält. Loggen introducerar istället sina egna oberoende versionsrymder — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — speglar §12.5:s oberoende av wrapper-version (wrappers innehåller en versionsbyte oberoende av protokollversionen, och loggversionerna följer samma separation).
Bevisleverans hämtas som standard, med en frivillig medåkning. Ett bevis kan inte existera vid förseglingstid (förankringen har inte skrivits ännu), så förseglingstidens .qub-bunt förblir bevisfri. W7:s verifierare hämtar GET …/proof en gång, eller rekonstruerar i fullt offline-läge beviset från den offentliga AnchorBundle via en Arweave-fråga på Log-Id. .qub-bunten (W7) reserverar en frivillig inclusion_proof-medlem — frånvarande vid försegling, fylld av en post-förankrings-omexport för kall arkivering — som följer samma "frivillig, utelämnad som standard, additiv"-mönster som W3:s drand_chain_version.
16.13 Retention
Retentionsfönster för LogDO-öppna-svansen, R2-bevisserverande substratet, förankrings-kretsbrytarräknarna och bundlar-fallback-kön specificeras i docs/DATA-RETENTION.md. Princip: loggens heta per-post-lagring (LogDO) är återvinningsbar efter förankring; dess granskningsmaterial — det koordinatnycklade (level, index)-Merkle-nodlagret + de seq-adresserade leaf-kropparna (§16.9) + Arweave-förankringarna — är permanent. Att återvinna ett kallt leaf från DO:n ogiltigförklarar aldrig ett utfärdat bevis, eftersom ett bevis löses mot det permanenta R2-nodlagret och Arweave-förankringen, inte DO:n (och §16.9:s utraderad-DO-testvektor bevisar det).
16.14 Testvektorer
W5 levererar den tvärspråkliga fixturen tlog_v1.json (§16.8) plus utarbetade vektorer: ett kind=0x01- och ett kind=0x02-leaf → leaf_hash; den 5-leaf kumulativa roten; ett inkluderingsbevis; ett konsistensbevis; en AnchorBundle; och ett DataItem-id. Dessa lever vid sidan av §14.5-yttre-omslagsvektorerna och utövas av både Rust-(qub-core)- och TypeScript-(Worker)-implementationerna.
16.15 Granskningsbeslut (W5 — löst)
Den externa granskningen W5 (en adversiell designgenomgång + ägarens godkännande) är klar. Varje beslut nedan är avgjort och återspeglas i §16-texten ovan; bindande startbegränsningar återges i slutet. Implementeringen kan fortsätta enligt dem.
- Standardväg (
kind=0x02) löv ärlighet — BESLUTAT. Skicka den två-blads-typen uppdelad som specificerat:kind=0x02åtar sig inte någonbody_hashellerdrand_round. Nej*_body_hashfält på den byte-blinda stigen (det skulle vara den mest läsliga falska "verifierade" signalen för integratörer och är en bekvämlighet §11 redan tillhandahåller från paketet). Gör inte kräver server-sigill för logg-intygade qubs (det skulle tvinga klartext genom Workern och förstöra kryptodammsgraven). Alla självbeskrivande kortslutningar tillhör i.qubbunt / beviskuvert som ett verifierare-omräknat fält, aldrig ett bladfält. Ägarbekräftat kravtak: §16.11. - Tvetydighets-/utlämnandeskuld — DESIGN LÖST, FÖRSEENHET OFULLSTÄNDIG. Designen kräver att tätningsmottagningsnyckeln är fastsatt
LogProfileoch korssignerad avanchor_owner, plus övervakningsmetodik, prev-chain walk och dubbla egenpublicerade huvuden. Den sammanställda profilen och driftsättningskrokarna är fortfarande platshållare/valfria som detaljspecifierat i §16.6, så det starkare detekterbara + kvitterade påståendet är inte aktuellt förrän dessa grindar stängs. Det får aldrig marknadsföras som oberoende bevittnat. En sann tredjepartsobservatör skjuts upp till en §15 styrningshöjning. - Fastnålad förankringsägare förtroenderot + rotation — LÖST. Adoptera
LogProfilepin (§16.6); verifieraren kontrolleraranchor_tx.owner == anchor_owneroch verifierar tx-data → tx_id-bindningen lokalt. Rotationsstyrning är en §15 tillägg att bygga (§15.3-trigger tillagd), inte en återanvändning; planerade rotationer korssigneras, kompromissdrivna rotationer faller tillbaka på §15-ökningen med gaffelkontrollen som begränsar skadan. - Privat qub-blad blindande — LÖST. Fortsätt blända för privata qubs (
ref = SHA3-256(qub_id ‖ log_blind_secret)), råqub_idför offentliga qubs (redan §16.2.1),chashsom den fristående slipsen.log_blind_secretär en korrelation/Sybil-grad hemlighet, endast framåtroterande (§16.2.1). received_at— BESLUTAT. Håll det i bladet, engagerat men uttryckligen icke-bevisande; har aldrig framkommit som bevis eller tvistbekräftelse på någon yta. Alla övervakningskontroller jämförs mot Arweave-blocktidenT, inte den operatörsstyrdaanchored_at(§16.6).- Nivåindelad bevisbar tid — DESIGNUPPLÖSNING, INTE NULIG RUTTNING. Den granskade designen tilldelar ankarlådstid till den batchade nivån och exakt timmes bevis till betald T3, utan något numeriskt SLA för den förstnämnda. De nuvarande rutterna har inte implementerat den kommersiella skillnaden: de schemalägger en individuell transaktion för varje accepterad publikation, och rapporteringen av täckning förblir villkorad som angivet i §16.1/§16.10. Produkttexten måste beskriva implementeringen, inte denna framtida nivåuppdelning.
- Kumulativt träd över arbetare — LÖST. Enkelt kumulativt RFC 9162-träd + frontier-cacherad enkelförfattar-LogDO (bekväm marginal jämfört med ~1k skrivningar/sek DO-tak; skjut upp Merkle-av-skärv-roots-sharding tills nära det). Den koordinatnycklade
(level, index)R2 nodlagring + wiped-DO kall-blads testvektor är implementerad (§16.9).< 300 msförblir ett design-/driftsmål, inte ett protokollåtagande (§16.10). - ANS-104 signeringsschema + djup-hash — LÖST. RSA-PSS (sig typ 1, återanvänder den dedikerade anchor-plånboks JWK); Ed25519 skjuts upp till §15 PQ-vägen. Den handgjorda SHA-384 djupa hashen är beroende av båda-riktningars cross-impl-fixture, en statisk-endast referens-bundler interoperabilitetskontroll, den delade-
crypto.subtletur-och-retur, och efter-paketet Arweave-godkännandeövervakning (§16.8).
Bindande startbegränsningar (föra till genomförande + produkt/juridisk granskning):
- Kravtak (Q1/Q6). Ingen yta får säga stocken bevisar innehållet i ett påstått blad eller låsa upp rund; det tillåtna påståendet för en framgångsrikt förankrad
kind=0x02bladet är ordnat, manipulationssäkert, med ett förtroendefritt övre gränsbindningstid. Ett svar utan en kvittotuppel har inget loggkrav. Ingen tidsstämpelkopia har någon numerisk fördröjningsgaranti. - Bevittna ärlighet (Q2). Marknadens tvetydighet som upptäckbar + kvitterad, aldrig oberoende bevittnad.
- Kvitto + ankarnycklar (Q2/Q8). Innan påståendena om icke-förnekande/förankrad-verifiering skickas, tillhandahåll och kompilera stiftelse-pin för mottagande offentliga nyckel, korssignera den med den tillhandahållna ankareägaren, och behåll ankarlådan som sin egen JWK skild från uppladdningsplånboken.
- Djup-hash-grind (Q8). Inga anchor- eller T3-transportskepp tills både-riktnings-fäste + interoperabilitetskontroll passerar; acceptansövervakningen larmar vid fel.
- Lagringsförutsättning (Q7). Koordinatnyckel-baserad nodlagring + borttagen-DO kall-blad-vektor är förutsättningar för garantin 'återvinning ogiltigförklarar aldrig ett bevis'.
17. Bärbar verifieringspaket (.qub)
Status. Den här sektionen är implementerad (W7 / UP-C2):
qub_core::exportproducerar och analyserar paketet, ochtools/qub-verifyär en offentlig, självständig CLI som verifierar en offline. §11 och §16.9 hänvisar redan till "the".qubbundle som enheten en fristående verifierare använder; detta avsnitt specificerar dess byte och verifieringsgenomgång. Det är strikt additivt — bundle paketera befintliga §11-indata och ändrar inget på kedjans trådfomat.
17.1 Syfte
§11 fastställer att vilken tredje part som helst kan verifiera en qubs kryptografiska artefakt utan qubs samarbete. The .qub paket gör den verifieringen bärbar och offline: det paketerar den förseglade CBOR och drand-rundans signatur som låser upp den i ett enda självständigt artefakt, så att en mottagare kan verifiera innehållsintegritet, rundbindning och eventuella författarskapssignaturer med ingen nätverksanrop alls (ingen lagringshämtning, ingen live drand-förfrågan, ingen qub-API). Ett paket i sig bevisar inte när dess chiffertext skapades; en oberoende verifierad lagringstransaktion eller ett förankrat loggbevis tillhandahåller det separata existens-tidspåståendet (§11, §17.5).
17.2 Paketformat
A QubBundle är handskriven kanonisk CBOR under §3.1-profilen (definit-längd, inga taggar, inga flyttal, kortaste-form heltal, NFC-text, valfria fält utelämnas när de saknas, nycklar ordnade efter kodade byte-längd stigande och sedan bytevis). De tre 15-teckensnycklarna ordnar d < i < s. En rå .qub filen är exakt dessa bytes; för URL- eller kopiera-klistra-transport är samma bytes base64url (utan fyllnad).
| Nyckel | Bif. len | Typ | Närvaro | Betydelse |
|---|---|---|---|---|
version |
åtta | u8 |
krävs | Paketformatversion (0x01). |
sealed_at |
tio | i64 |
valfri | Skapare-angivet sigilltid (Unix-sekunder); självbeskrivande, icke-bevisande. |
drand_round |
tolv | u64 |
krävs | Runt vilket qub är låst. En projektion av den inbäddade förseglade qubben. |
arweave_tx_id |
fjorton | tstr |
krävs | Transaktions-ID:t som de förseglade bytena lagrades under (ursprungspointer). |
drand_chain_id |
femton | tstr |
krävs | Drand-kedjan (hex). En projektion av den inbäddade förseglade quben. |
drand_signature |
sexton | bstr |
krävs | Drand-sändarsignaturen för drand_round — värdet som låser upp chiffertexten. |
inclusion_proof |
sexton | bstr |
valfri | §16 transparens-loggens Merkle-inloggningsbevis, efter att ett förankrat bevis är tillgängligt (§17.5). |
sealed_qub_cbor |
sexton | bstr |
krävs | Den inre SealedQubCbor bytes (post-§13-unwrap), dvs. §11 verifieringsingång. |
drand_round och drand_chain_id är bekvämlighetsprojektioner av sealed_qub_cbor, bärs så att verktyg kan läsa dem utan att behöva tolka inner-CBOR. De härleds vid konstruktion och omkontrollerad vid avkodning mot den parsade förseglade quben; ett paket vars översta fält inte överensstämmer med dess innehåll avvisas. Kodarens disciplin speglar resten av trådfmt: avvisa en tom drand_signature eller arweave_tx_id, och bundet varje fält med variabel längd.
17.3 Vad den inbäddade drand-signaturen bevisar
Paketet bär drand-signaturen istället för att kräva att verifieraren hämtar den. Timelåsavkryptering (tlock över drand-kedjan, §8) kan endast lyckas med äkta fyrsignatur för den bundna omgången—ett värde som kedjan endast publicerar när omgången har passerat, och som är en giltig BLS-signatur under kedjans offentliga nyckel. En förfalskad eller felaktig signatur misslyckas med BLS-verifiering eller IBE/AEAD-dekryptering. Ett paket som dekrypteras bevisar därför: chiffertexten är bunden till rond R, och rond R har förflutit. Verifieraren stiftar kedjan (DrandTimelockProvider::quicknet()) och tillämpar §11 kontroll av rundbindning, så ett paket kan inte påstå att en runda dess chiffertext inte är bunden till.
Detta är ett bevis för släppvillkor, inte en skapandetidpunkt. Efter runda R har
förflutit, vem som helst kan skapa en ny chiffertext för R och paketera dess redan offentliga
signatur. Därför FÅR buntet ensam INTE beskrivas som bevis på att
chiffertext eller innehåll fanns före R, före unlock_at, eller före någon händelse.
17.4 Genomgång av offlineverifiering
qub-verify <file.qub> driver standard §11-förfarandet helt från buntningen, vilket driver qub_core::unlock::unlock med en fastnålad 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 avslutas 0 (verifierad), 1 (verifiering misslyckades — fortfarande låst, kroppsha-sammansättning matchar inte, bruten runda/kedjebindning, eller en signatur som inte kan verifieras), eller 2 (användning / felaktigt paket). A --json rapporten har samma slutsatser för automation. Eftersom paketet är självförsörjande, verifieringskratet (qub-core) och CLI (qub-verify) är den enda programvaran en tredje part behöver; båda är offentliga och återanvänder protokollets befintliga verifieringsväg — ingen specialgjord kryptering.
17.5 Förhållande till transparensloggen
inclusion_proof är en valfri plats för §16 Merkle-inklusionsbeviset. Endast buntverifiering (§17.4) är komplett för integritet, rund bindning / rund förfluten tid, och valfritt författarskap, men har avsiktligt ingen självständig tidsstämplad existenshänvisning. En befolkad, helt ankiverifierad inclusion_proof lägger till löv-typ-specifik åtagande och övre tidsgräns från §16.11 utan att ändra paketformatversionen. Ett saknat bevis betyder endast "inget bevis inkluderat"—inte "ogiltigt" och inte nödvändigtvis "inte förankrat."
I referensimplementeringen är sloten nu skriven: qub_core::export::QubBundle::inclusion_proof_typed() returnerar en Option<InclusionProof> bärande den fullständiga §16.9-strukturen (blad, revisionsväg, förankrad rot, och AnchorRef) genom samma ogenomskinliga CBOR-fält — ingen uppgradering av paketformatets version. Den fristående qub-verify CLI konsumerar det via dess --anchor ben, och — tills ankarplånboken är förberedd (§16, Status) — rapporterar ett bevis på en fylld-men-platsinnehavare som endast inklusion istället för fullständigt ankrare-verifierad.