Specyfikacja protokołu qub
qub jest protokołem do kryptograficznych zobowiązań czasowych: systemem do zapisywania słów na przyszłą datę i późniejszego dokładnego sprawdzania, co zostało zapisane, którego wydanie było regulowane przez rundy drand, a—gdy dostępna jest transakcja magazynowa lub dowód w dzienniku przejrzystości—niezależnie oznaczoną czasowo górną granicą momentu, w którym zaszyfrowany tekst został zobowiązany.
Trzy prymitywy to powodują. drand jest zdecentralizowaną latarnią losowości — data ujawnienia jest wymuszana kryptograficznie, a nie przez dobrą wolę quba. Trwałe przechowywanie zachowuje uznane zapieczętowane bajty, podczas gdy obecne ścieżki publikacji planują pojedyncze transakcje trwałego przechowywania; udane dołączanie dzienników ogólnego przesyłania może dodatkowo uczestniczyć w zbiorowych, zakotwiczonych zobowiązaniach. ML-DSA-65 jest post-kwantowym podpisem cyfrowym — gdy włączona jest autorskość, qub jest powiązany z parą kluczy, której sekret nigdy nie opuszcza urządzenia autora.
Razem te prymitywy tworzą oświadczenie, które jest czasowo zablokowane i odporne na manipulacje, opcjonalnie przypisywalne i niezależnie datowalne — paragon, którego wartość rośnie wraz ze wzrostem możliwości świata do fałszowania przeszłości.
Pozostała część tego dokumentu stanowi normatywną specyfikację wymaganą do interoperacyjnych implementacji.
Specyfikacja protokołu qub
| Pole | Wartość |
|---|---|
| Wydanie dokumentu | 1.0.0 (protocol-v1.0.0) |
| Protokół przewodowy | 0x01 |
| Zewnętrzna powłoka | 0x01 |
| Data wejścia w życie | 23-09-2026 |
| Status | Bieżący |
| Przejrzano | 23-09-2026 |
Ten dokument jest normatywną specyfikacją protokołu dla systemu zobowiązań czasowych qub. Definiuje struktury danych, reguły serializacji, formuły derywacji oraz procedury weryfikacji wymagane dla interoperacyjnych implementacji.
Zakres: warstwa protokołu jest celowo neutralna językowo — ciało qub to nieprzezroczysty tekst jawny / markdown / bajty paktu, a renderowanie z uwzględnieniem lokalizacji jest odpowiedzialnością widza (aplikacja webowa qub.social, ramka iframe <qub-embed>, klienci MCP, itd.).
1. Notacja i konwencje
| Notacja | Znaczenie |
|---|---|
u8, u64, i64 |
Liczby całkowite bez znaku/ze znakiem o określonej szerokości bitowej |
[u8; N] |
Tablica bajtów o stałej długości N bajtów |
Vec<u8> |
Tablica bajtów o zmiennej długości |
Option<T> |
Wartość typu T, albo nieobecna |
String |
Łańcuch tekstowy UTF-8, znormalizowany NFC |
| ` | |
SHA3-256(x) |
Hash NIST SHA3-256 łańcucha bajtów x (FIPS 202) |
ceil(x) |
Funkcja sufitu: najmniejsza liczba całkowita ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | Najbardziej znaczący bajt jako pierwszy |
Wszystkie liczby całkowite w konstrukcjach preimage są kodowane jako tablice bajtów big-endian o stałej szerokości (i64 → 8 bajtów, u8 → 1 bajt), o ile nie określono inaczej.
Wszystkie znaczniki czasu to sekundy uniksowe w UTC.
2. Struktury danych
2.1 ComposeQub (stan twórcy w pamięci)
Nieserializowany do CBOR. Niezapisywany w trwałym magazynie. Lokalny dla aplikacji twórcy.
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 (odszyfrowany ładunek)
Serializowany przy użyciu kanonicznego CBOR (§3). Zaszyfrowany wewnątrz SealedQub. To jest struktura, która dowodzi integralności treści po odszyfrowaniu.
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
}
Linia bazowa (tekst niezapisany qub): version = 0x01, content_type = 0x01, sig_alg = 0x00; pola podpisu i współpodpisu są nieobecne. Inne opcjonalne pola metadanych mogą być obecne.
Inne konfiguracje v1: content_type = 0x03 (ciało paktu, zob. §6.1); sig_alg = 0x01 (ML-DSA-65) z obecnymi author_signature i author_pubkey (zob. §9.3); cosigner_pubkey i cosigner_signature obecne razem dla współpodpisanych paktów (zob. §9.7); reply_to ustawione na qub_id rodzica qub dla qubów w łańcuchu odpowiedzi (zob. §9.3 dla implikacji zakresu podpisu).
2.3 SealedQub (kanoniczny format na drucie)
Seryjnie zapisywane przy użyciu kanonicznego CBOR (§3). To jest wewnętrzny artefakt przesyłu: publiczna dostawa przechowuje te bajty wprost, podczas gdy prywatna dostawa je opakowuje OuterWrapper przed przechowywaniem (§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 (stan aplikacji widza)
Nieserializowany do CBOR. Lokalny dla aplikacji widza. Konstruowany po pomyślnym odszyfrowaniu i weryfikacji.
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. Profil kanonicznego CBOR
Cała serializacja SealedQub i QubEnvelope MUSI być zgodna z tym profilem. Dwie implementacje otrzymujące tę samą logiczną strukturę MUSZĄ wytworzyć identyczne bajty.
3.1 Reguły kodowania
| Reguła | Specyfikacja |
|---|---|
| Standard | RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements) |
| Kolejność kluczy mapy | Sortowane najpierw po długości zakodowanych bajtów (krótsze przed dłuższymi), następnie leksykograficznie (bajt po bajcie dla kodowań tej samej długości) |
| Kodowanie liczb całkowitych | Najkrótsza forma: 0–23 w bajcie początkowym; 24–255 w 2 bajtach; 256–65535 w 3 bajtach; itd. |
| Kodowanie długości | Tylko określone długości. Bez tablic, map, łańcuchów bajtów ani łańcuchów tekstowych o nieokreślonej długości (additional info = 31 jest zabronione). |
| Tagi | Bez tagów CBOR (główny typ 6 jest zabroniony). |
| Zmiennoprzecinkowe | Bez liczb zmiennoprzecinkowych (główne typy 7 wartości 0xF9–0xFB są zabronione). |
| Łańcuchy tekstowe | Kodowane UTF-8, znormalizowane NFC (Unicode Normalization Form C). |
| Łańcuchy bajtów | Surowe bajty. Bez kodowania base64 w warstwie CBOR. |
| Duplikaty kluczy | Odrzucenie z błędem. Parsery NIE MOGĄ po cichu akceptować zduplikowanych kluczy mapy. |
| Nieznane klucze | Odrzucenie z błędem. Parsery NIE MOGĄ tolerować kluczy mapy spoza kanonicznego zestawu kluczy danego typu — dwa różne kanoniczne łańcuchy bajtów nigdy nie mogą dekodować się do tej samej wartości (encode(decode(x)) == x), a w podpisanych ładunkach dodatkowy klucz byłby ukrytą treścią, z którą wiążą się oba podpisy. Ewolucja schematu odbywa się przez version, nigdy przez dodatkowe klucze. |
| Wartości proste | Dozwolone są tylko true (0xF5), false (0xF4) i null (0xF6). |
| Pola opcjonalne | Nieobecne pola opcjonalne są pomijane całkowicie z mapy CBOR (nie są kodowane jako null). Obecne pola opcjonalne są dołączane w posortowanej kolejności kluczy. |
3.2 Zweryfikowane kanoniczne kolejności kluczy
Te kolejności kluczy są normatywne. Implementacje MUSZĄ emitować klucze dokładnie w tej kolejności. Asercje debugujące POWINNY weryfikować kolejność w buildach nierelease.
QubEnvelope (wersja 0x01, niepodpisany, wszystkie pola opcjonalne nieobecne):
"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)
Wyprowadzenie kolejności kluczy QubEnvelope: każdy klucz jest łańcuchem tekstowym CBOR. Długość zakodowana = 1 bajt nagłówka + długość łańcucha (dla łańcuchów poniżej 24 bajtów). Sortowanie najpierw po całkowitej długości zakodowanej, następnie leksykograficznie dla kluczy o tej samej długości.
SealedQub (wersja 0x01, publiczny, bez odbiorcy):
"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 (ciało paktu, 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 (wiersz tablicy terms):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier (mapa party_a / party_b):
"label" (6 encoded bytes)
"contact" (8 encoded bytes) ← only if present
3.3 Odniesienie kodowania bajtów
| Typ | Kodowanie CBOR | Przykład |
|---|---|---|
| Hash SHA3-256 (32 bajty) | 0x58 0x20 + 32 bajty |
body_hash, qub_id |
| Znaczniki czasu (i64) | Główny typ 0 (dodatni) lub 1 (ujemny), najkrótsze kodowanie | Sekundy uniksowe |
| Wersja (u8, wartość 1) | 0x01 (pojedynczy bajt) |
|
| Typ treści (u8, wartość 1) | 0x01 (pojedynczy bajt) |
|
| sig_alg (u8, wartość 0) | 0x00 (pojedynczy bajt) |
|
| Podpis ML-DSA-65 (3 309 bajtów) | 0x59 0x0C 0xED + 3 309 bajtów |
author_signature, cosigner_signature |
| Klucz publiczny ML-DSA-65 (1 952 bajty) | 0x59 0x07 0xA0 + 1 952 bajty |
author_pubkey, cosigner_pubkey |
4. Derywacje normatywne
4.1 qub_id
qub_id jednoznacznie identyfikuje qub i wiąże QubEnvelope z SealedQub. Jest wyprowadzany deterministycznie z zawartości koperty.
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
Kodowanie separatora domeny: Łańcuch "QUB_ID_V2" ma 9 bajtów ASCII. Dla wyrównania dołączany jest pojedynczy bajt wypełniający 0x00, aby osiągnąć 10 bajtów. Implementacje MUSZĄ używać dokładnie tych 10 bajtów: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
outcome_at kodowanie: Wersja przedpremierowa implementacji rozszerzyła preobraz z 92 do 100 bajtów, aby złożyć opcjonalne outcome_at pole w powiązaniu. Brak outcome_at jest zakodowany jako 8 zerowych bajtów; walidatory protokołu odrzucają outcome_at <= 0 wszędzie, aby ten strażnik nie mógł kolidować z prawidłową wartością. Zobacz §3.2 (format przewodowy) oraz in-tree tasks/verdict-uplift-plan.md dla mechanika werdyktu, który motywuje to pole.
drand_round kodowanie: Późniejsza wersja wstępna implementacji wydłużyła preobraz z 100 do 108 bajtów do złożenia drand_round (docelową rundę drand, §4.3) do wiązania, i podniósł separator domeny do QUB_ID_V2. To wiąże rundę timelock z tożsamością qub: bramka nie może ponownie powiązać szyfrogramu z inną (np. już minioną) rundą niż wyświetlana unlock_at implikuje. Procedura odblokowywania (§8) dodatkowo sprawdza, czy runda wpisana w szyfrogram stazy tlock pasuje unlock_round(unlock_at), więc wyświetlany czas odblokowania jest dowodowo rundą, która umożliwia odszyfrowanie.
Właściwości:
- Zmiana jakiegokolwiek pola powiązanego z preobrazem—
version,content_type,created_at,unlock_at,outcome_at,drand_round, surowybodybajty (przezbody_hash), lubtitle(przeztitle_hash)—produkuje innyqub_id. - Qub_id jest obliczany przed szyfrowaniem. Zarówno QubEnvelope, jak i SealedQub zawierają ten sam qub_id. Odbiorca weryfikuje, czy pasują po deszyfrowaniu.
qub_idnie zależy odsender_label,reply_to, bajty podpisu lub publiczne klucze podpisujące. W obecnej konstrukcji podpisywania V2, jednakże,sender_labelireply_tosą uwierzytelniane bezpośrednio przezsender_label_hashireply_to_or_zero(§9.3) zawsze, gdy obecne są podpisy.- Zmiana SealedQub
title(z wszystkim innym naprawionym) zmianyqub_idprzeztitle_hash. Brama nie może zatem zamienić jawnego tytułu wyświetlanego na liczniku, nie unieważniając tożsamości qub. - Zmiana SealedQub
outcome_at(z wszystkim innym naprawionym) zmianyqub_idprzez preobraz. Brama nie może zmienić wyświetlonej na liczniku daty werdyktu przed ujawnieniem bez unieważnienia tożsamości qub. - Zmiana
drand_round(z wszystkim innym naprawionym) zmianyqub_idpoprzez preobraz. Brama nie może powiązać ponownie szyfrogramu blokady czasowej z inną rundą bez unieważnienia tożsamości qub; w połączeniu z §8 sprawdzeniem rundy-strofy czasu odblokowania, wyświetlanyunlock_atto runda, która faktycznie kontroluje odszyfrowywanie.
4.2 body_hash
body_hash = SHA3-256(body)
Gdzie body to surowy ładunek treści Vec<u8>. Dla qubów tekstowych jest to ciało qub zakodowane w UTF-8.
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
Gdzie title to opcjonalny tytuł jawny pokazywany na odliczaniu widza przed ujawnieniem (zob. §3.2). Normalizacja NFC odbywa się w czasie obliczania hasha, dzięki czemu skrót jest stabilny dla wizualnie równoważnych sekwencji punktów kodowych. Wartość-strażnik zawierająca same zera jest zarezerwowana dla przypadku nieobecnego; pusty łańcuch jest odrzucany na granicy kanonicznego CBOR jako niekanoniczne kodowanie "nieobecny" (kanoniczne kodowanie pomija to pole całkowicie).
4.3 Mapowanie odblokowań-rund
drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
| Parametr | Źródło | Przykład |
|---|---|---|
unlock_at |
Sekundy Unix wybrane przez użytkownika UTC | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
informacje o łańcuchu drand (genesis_time) |
1595431050 |
chain_period_seconds |
informacje o łańcuchu drand (period) |
30 |
To jest odniesienie mapowania tlock (drand's) CurrentRound). drand publikuje rundę N w chain_genesis_time + (N - 1) * chain_period_seconds, więc formuła wybiera prąd okrężny przy unlock_at — runda, której podpis jest pierwszym, który zobaczy przybywający widz unlock_at można użyć.
Właściwość wyrównania (przypadek, który ma znaczenie w praktyce): kiedy (unlock_at - chain_genesis_time) dzieli się dokładnie przez chain_period_seconds, podpis wybranego rundy zostaje opublikowany dokładnie o unlock_at, nigdy wcześniej. To zawsze dotyczy wdrożenia referencyjnego: czas powstania quicknet (1692803367) jest podzielny przez swój 3-sekundowy okres, a aplikacje referencyjne przypisują czasy odblokowania PIN do pełnych minut. Dla niezsynchronizowanych unlock_at, podpis wybranej rundy jest publikowany ściśle mniej niż jeden okres wcześniej unlock_at — precyzja czasowa zobowiązania wynosi jeden okres sygnałowy.
Mapowanie wstępnej wersji Legacy i tolerancja po stronie odblokowania: oryginalne mapowanie było ceil((unlock_at - chain_genesis_time) / chain_period_seconds), który — w opisanym powyżej przypadku wyrównanym do okresu — wybrał pełną, opublikowaną rundę jednookresową przed unlock_at, sprawiając, że szyfrogram można odszyfrować wcześniej o dokładnie jeden okres. Dwa odwzorowania różnią się dokładnie o +1 kiedy delta dzieli okres, i zgadzają się inaczej. Ponieważ drand_round jest złożone w niezmiennym qub_id preobraz (§4.1), artefakty zapieczętowane zgodnie ze starszym odwzorowaniem nie mogą być ponownie wyprowadzone; weryfikatorzy wykonujący krok 6a z §8 rundy weryfikacyjnej MUSZĄ w związku z tym zaakceptować zapisany drand_round równy albo pochodna runda lub pochodna runda minus jeden (i MUSI wymagać, aby runda strof tlock była dokładnie równa zapisanej rundzie). Tolerancja rozszerza najwcześniejszy podpis bramkujący maksymalnie o jedną perspektywę czasową. Usługa etapowania paktu stosuje tę samą tolerancję, gdy ponownie wyprowadza etapowany pakt qub_id (na etapie i przy współpodpisie): jeśli obecna runda mapowania nie odtwarza zobowiązanego qub_id a deltą dzieli okres, próbuje ponownie z rundą minus jeden i zatwierdza ostateczne porozumienie dla którejkolwiek rundy qub_id rzeczywiście wiąże—nigdy bezmyślnie do ponownie obliczonej rundy, co sprawiłoby, że artefakt byłby trwale niewyprowadzalny.
Weryfikacja: unlock_at MUSI być w przyszłości w czasie pieczęci. unlock_at NIE MOŻE być starszy niż 10 lat od created_at (aby ograniczyć ryzyko zależności drand w długim horyzoncie; interfejs UŻYTKOWNIKA POWINIEN ostrzegać o datach odblokowania przekraczających 2 lata).
5. Newtype'y formatu drutowego
Newtype'y formatu drutowego zapewniają bezpieczeństwo w czasie kompilacji przed myleniem bajtów CBOR z JSON, surowym tekstem jawnym lub innymi kodowaniami bajtów.
| Typ | Zawiera | Wyprodukowane przez | Pochłonięty przez |
|---|---|---|---|
SealedQubCbor |
Kanoniczny CBOR SealedQub | serialize_sealed_qub() |
Artefakt wewnętrznego przewodu; przechowywany bez opakowania do publicznej dostawy lub owinięty do dostawy prywatnej, następnie odzyskany przez oglądającego |
QubEnvelopeCbor |
Kanoniczny CBOR QubEnvelope | serialize_qub_envelope() |
tlock szyfruj wejście, tlock deszyfruj wyjście |
5.1 Reguły konstrukcji
// 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 Walidacja przy konstrukcji
from_encoded() POWINIEN walidować, że wejście zaczyna się od prawidłowego nagłówka mapy CBOR. Pełna walidacja strukturalna odbywa się w czasie parsowania, a nie konstrukcji, aby uniknąć podwójnego parsowania.
6. Rejestr typów treści
| Wartość | Typ | Maks. rozmiar ciała | Uwagi |
|---|---|---|---|
0x00 |
Zarezerwowane (nieprawidłowe) | — | NIE MOŻE być używane |
0x01 |
Tekst jawny (UTF-8, ograniczony Markdown) | 50 KB płatne / 10 KB darmowe | Zobacz §10 dla reguł renderowania. Podział darmowy / płatny jest egzekwowany przez usługę uploadu; twarda granica warstwy protokołu to 50 KB. |
0x02 |
Zarezerwowane (przyszłe) | — | Przydzielone dla przyszłego typu treści; nieprawidłowe w v1. Widzowie MUSZĄ odrzucić zgodnie z regułą poniżej. |
0x03 |
Pact (umowa bilateralna, ciało CBOR) | 100 KB | Ciało to kanoniczny CBOR PactTerms (§6.1). Współpodpisywanie cosignera zgodnie z §9.7. |
0x04 |
Werdykt (samoocena twórcy, ciało CBOR) | 8 KB | Ciało to kanoniczny CBOR VerdictBody (§6.2). Emitowane wyłącznie przez systemowy intent verdict. Powiązanie z qubem rodzicielskim jest na tagu Arweave Parent-Tx-Id, nie w ciele. Zobacz verdict-uplift-plan §3.4. |
Widzowie MUSZĄ odrzucać nieznane typy treści z wyraźnym błędem widocznym dla użytkownika. Widzowie NIE MOGĄ próbować renderować nieznanych typów jako tekstu.
6.1 Ciało paktu (content_type = 0x03)
Ciało paktu to kanoniczne kodowanie CBOR wartości PactTerms:
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)> }
Kanoniczne kolejności kluczy CBOR dla wszystkich trzech map są podane w §3.2. Całkowity serializowany CBOR paktu NIE MOŻE przekraczać 100 KB (zgodnie z §6).
Dyskryminator schematu. Pierwszy wiersz w terms dla paktu structured/v1 MUSI być { key: "pact_schema", value: "structured/v1" }. Wiersze bez tego znacznika to pakty "custom" i nie otrzymują strukturalnej walidacji ani renderowania świadomego schematu.
Zamrożone sloty potwierdzeń. Pakty structured/v1 niosą dokładnie cztery wiersze potwierdzeń pod tymi kluczami:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value dla każdego jest jednym z ośmiu zamrożonych angielskich łańcuchów wybranych przez parę (role, kind), gdzie role ∈ { seller, buyer, provider, client } i kind ∈ { standard, capacity }. Same łańcuchy są normatywnymi danymi protokołu — podpisy ML-DSA-65 obu stron wiążą się z dokładnymi bajtami przez body_hash. NIE są one zlokalizowane; podpisane ciało jest neutralne językowo. Każda zmiana sformułowania wymaga nowej wersji schematu (structured/v2).
Osiem łańcuchów, ich wyszukiwanie (acknowledgement_for(role, kind)) i uzasadnienie dla każdego są przypięte przez implementację referencyjną. Zgodne implementacje MUSZĄ emitować bajtowo identyczne wartości potwierdzeń; testy hasha ciała SHA3-256 na fikstach złotych obejmujące wszystkie cztery kombinacje ról wychwytują wszelki drift.
Kolejność wyświetlania u widza. Łańcuchy potwierdzeń zawierają frazy takie jak "described above", co zakłada, że wiersze opisu / zakresu renderują się przed potwierdzeniami. Widzowie MUSZĄ renderować tablicę terms w kolejności CBOR; zmiana kolejności łamie semantykę prozy.
Kontakt strony przeciwnej. Gdy contact Strony B to prawidłowy adres email, usługa uploadu qub automatycznie wysyła email z zaproszeniem do recenzji / współpodpisu w czasie etapu i wiąże ewentualny współpodpis z weryfikacją tego samego adresu (§9.7). Pakty, w których kontakt Strony B jest nieobecny, mogą być nadal współpodpisane, ale tylko przez kanał poza pasmem — usługa odmawia żądań współpodpisu, które nie mogą wyprodukować pasującego 15-minutowego markera weryfikacji email.
6.2 Ciało werdyktu (content_type = 0x04)
Ciało werdyktu to kanoniczne kodowanie CBOR wartości VerdictBody:
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
}
Kanoniczna kolejność kluczy CBOR:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
Całkowity serializowany CBOR werdyktu NIE MOŻE przekraczać 8 KB (zgodnie z wierszem rejestru powyżej).
Enum rozstrzygnięcia. Bajt na drucie jest neutralny względem intencji; cztery kategorie Right / Partial / Wrong / Unfalsifiable pokrywają przestrzeń rozstrzygnięć każdego intentu niosącego werdykt. Etykiety zależne od intencji ("Trafione" / "Dotrzymane" / "Wydane" / "Potwierdzona" dla Right itd.) są kwestią renderowania po stronie widza, rozstrzyganą względem intencji rodzicielskiego quba — drut pozostaje neutralny językowo i intencyjnie. Wartości spoza zakresu 1..=4 MUSZĄ być odrzucane przy dekodowaniu.
Powiązanie z rodzicem. W swoim ciele qub werdyktu NIE niesie referencji do rodzica. Identyfikator transakcji Arweave rodzicielskiego quba jest emitowany jako tag magazynowy Parent-Tx-Id w czasie uploadu (warstwa tagów magazynowych §7). Utrzymuje to ciało jako samowystarczalne podpisane oświadczenie samooceny; łańcuch audytu („racja co do czego?") jest ustanawiany przez wyszukiwanie tagu Arweave.
Bezpieczeństwo URL dowodu (normatywne). Gdy evidence_url jest obecny, walidatory (po stronie kompozycji, po stronie drutu, na krawędzi Workera) MUSZĄ wymusić:
- Wyłącznie HTTPS. Łańcuch MUSI zaczynać się od sekwencji bajtów
https://. Każdy inny schemat —http,ftp,javascript,data,fileitd. — jest odrzucany. - Ograniczenie długości. ≤ 2 048 bajtów (praktyczny limit URL w przeglądarce).
- NFC + sprawdzenie wrogich punktów kodowych. Ta sama reguła co dla
titleireflection— punkty kodowe bidi-override / zero-width / tag-block / BOM / C0 / C1 są odrzucane. Definicja zgodna z Rustcrate::handle::contains_hostile_text_codepointi TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(utrzymywać w zgodzie). - Bez białych znaków, bez znaków sterujących ASCII. Białe znaki / DEL / bajty poniżej
0x20w dowolnym miejscu URL są odrzucane — zamyka wektor wstrzyknięć\n/\t, którego reguła bidi nie pokrywa. - Niepusty segment hosta. Wszystko między
https://a pierwszym/,?lub#MUSI być niepuste.
Brak pobierania po stronie serwera. Worker NIE MOŻE proxować, pobierać ani podglądać URL. Protokół przechowuje łańcuch; renderowanie odbywa się po stronie widza z rel="nofollow noopener noreferrer" target="_blank" i widocznym hostem wyświetlanym obok tekstu linku.
Refleksja. Opcjonalny tekst refleksji napisany przez twórcę („co się zmieniło, czego można się nauczyć"). Ta sama walidacja NFC + wrogich punktów kodowych co dla title. Wejście puste / złożone wyłącznie z białych znaków zwija się do nieobecnego w czasie konstrukcji.
Wersja schematu. v1 wspiera wyłącznie verdict_version = 0x01. Przyszłe rewizje schematu zwiększają ten bajt i lądują wraz z nową wersją protokołu zgodnie z §12.
7. Protokół pieczętowania
Pełna sekwencja pieczętowania. Każdy krok jest normatywny.
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.
Warstwa tagów pamięci (poza pasmem). Usługa przesyłania qub dołącza celowo mały zestaw znaczników transakcji przechowywania wraz z wybranym ładunkiem do przesłania. Content-Type=application/octet-stream jest normatywnie wymagane. Usługa referencyjna dodatkowo dołącza trzy opcjonalne tagi, gdy twórca zdecyduje się je ujawnić: Intent (dozwolona lista-zweryfikowana intencja kompozycji—announcement, thesis, prediction, letter, secret, commitment, proof, lub generowany przez system verdict), Author (odcisk palca klucza publicznego twórcy §9.3 jako 64-znakowy hex w małych literach), oraz Parent-Tx-Id (identyfikator transakcji pamięci podręcznej nadrzędnego qub do łańcuchów odpowiedzi, 43-znakowy base64url).
Ten Author tag jest zapisz się na qub: aplikacja do tworzenia odniesień dołącza ją tylko wtedy, gdy użytkownik wyraźnie włączy publiczne przypisanie w momencie zapieczętowania. Gdy przełącznik jest wyłączony — domyślnie — nic Author tag jest zapisany, a qub jest nieprzypisany na łańcuchu: nic w trwałej pamięci nie łączy przesyłki z nazwą użytkownika twórcy, adresem e-mail ani innymi qubami. Gdy przełącznik jest włączony, Author odcisk palca przypisuje się wybranemu przez twórcę @handle za pośrednictwem łańcucha poświadczeń §9.5. Relacje w łańcuchu odpowiedzi i Intent nieidentyfikujące. W przypadku prywatnej dostawy zewnętrzna osłona (§13) szyfruje rozpoznawalną zawartość wewnętrzną SealedQub artefakt, więc zbieranie przechowywanych wrapperów i uzyskiwanie publicznych podpisów drand nadal nie wystarcza do odzyskania ciała bez K; tagi przechowywania pozostają celowo publicznymi metadanymi.
Usługa referencyjna celowo NIE dołącza tagów App-Name, App-Version ani Type: każdy taki filtr jednowartościowy zwracałby cały korpus qub na zapytanie GraphQL, co jest niezgodne z zakresem poufności tylko-ciała opakowania.
Zgodny weryfikator NIE MOŻE polegać na żadnym tagu magazynu dla weryfikacji przez stronę trzecią z §11; hash ciała / qub_id / podpis wiążą się wyłącznie z wewnętrznym CBOR, nigdy ze zbiorem tagów.
8. Protokół odblokowania
Pełna sekwencja odblokowania. Każdy krok jest normatywny.
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. Podpisywanie autorstwa
9.1 Uzasadnienie
Quby są przechowywane w trwałym magazynie. Podpisy autorstwa muszą pozostawać niemożliwe do podrobienia bezterminowo, dlatego v1.0 używa postkwantowego schematu ML-DSA-65 (FIPS 204) zamiast klasycznego schematu, którego bezpieczeństwo może osłabnąć w trwałym okresie życia qub.
9.2 Rejestr algorytmów
sig_alg |
Schemat | Rozmiar klucza | Rozmiar podpisu | Status |
|---|---|---|---|---|
0x00 |
Brak podpisu (niepodpisany) | — | — | Aktywny |
0x01 |
ML-DSA-65 (FIPS 204) | 1 952 bajty | 3 309 bajtów | Aktywny |
0x02 |
Ed25519 | 32 bajty | 64 bajty | Zarezerwowana stała; nieobsługiwana w protokole v1 |
Przeglądarki Protocol-v1 MUSZĄ odrzucać każdą wartość spoza {0x00, 0x01}, w tym
powściągliwy 0x02 wartość. Rezerwacja zapobiega przypadkowemu ponownemu użyciu; nie jest
aktywacja. Jej aktywacja wymaga kontrolowanej zmiany opisanej w §15.
9.3 Konstrukcja podpisanego preimage
Istniały dwie wersje preimage. Wszystkie podpisy MUSZĄ używać V2, a weryfikatory MUSZĄ akceptować wyłącznie V2. Starszy preimage V1 (udokumentowany poniżej w celach historycznych) był akceptowany jako awaryjny wariant wyłącznie do weryfikacji podczas migracji do V2; ten wariant awaryjny został wycofany, a podpis oparty wyłącznie na V1 jest teraz odrzucany.
V2 (aktualna — produkowana przez każde nowe podpisywanie przez autora oraz przez oba podpisy w przepływie etapowania / współpodpisywania paktu):
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 stosuje tę samą konwencję sentinela nieobecności co title_hash (§4.2.1): 32 zerowe bajty nie są prawidłowym wyjściem SHA3-256, więc „nieobecność" nigdy nie może kolidować z obecną etykietą. Wszystkie pola mają stałą szerokość, więc preimage jest jednoznaczny bez prefiksów długości.
V1 (starsza — WYCOFANA; już nieprodukowana i już nieakceptowana przy weryfikacji):
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
Preimage V1 pomijał sender_label i reply_to. Był akceptowany jako awaryjny wariant wyłącznie do weryfikacji podczas migracji do V2; ten wariant awaryjny został od tego czasu wycofany — weryfikatory MUSZĄ akceptować wyłącznie preimage V2. Definicja jest zachowana tutaj w celach historycznych oraz aby wyjaśnić separator domeny poniżej. Podpis, który weryfikuje się wyłącznie względem V1, MUSI być traktowany jako niepowodzenie weryfikacji.
Separatory domeny: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" to po 17 bajtów ASCII każdy ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Bez wypełnienia. Różniący się separator rozdziela domenowo obie konstrukcje, więc podpis nad jednym preimage nigdy nie może zweryfikować się jako drugi.
Bajt org_id_present: bajt następujący po unlock_at MUSI być 0x00. Implementacja referencyjna eksponuje to jako stałą ORG_ID_PRESENT_INDIVIDUAL = 0x00 w crates/qub-core/src/signing.rs; widzowie rekonstruujący sig_input do weryfikacji MUSZĄ emitować ten sam bajt.
Zakres podpisu — co jest, a co nie jest objęte. sig_input V2 wiąże się bezpośrednio z polami version, qub_id, body_hash, unlock_at, sender_label i reply_to (plus stały separator domeny i bajt org_id_present). qub_id sam jest wyprowadzony z version, content_type, created_at, unlock_at, outcome_at, drand_round i body_hash przez preimage z §4.1, więc jakakolwiek zmiana tych pól produkuje inne qub_id i tranzytywnie unieważnia podpis. Bezpośrednio uwierzytelniona powierzchnia jest zatem:
| Pole | Uwierzytelnione podpisem | Jak |
|---|---|---|
version |
✓ | Bezpośrednie wprowadzanie do sig_input |
qub_id |
✓ | Bezpośrednie wprowadzanie |
body_hash |
✓ | Bezpośrednie wprowadzanie |
unlock_at |
✓ | Bezpośrednie wprowadzanie |
sender_label |
✓ | Bezpośrednie wprowadzanie sender_label_hash (preimage V2 — jedyna akceptowana forma) |
reply_to |
✓ | Bezpośrednie wprowadzanie reply_to_or_zero (preimage V2 — jedyna akceptowana forma) |
content_type |
✓ | Przechodnio, przez qub_id przedsobraz |
created_at |
✓ | Przechodnio, przez qub_id przekształcenie wsteczne |
outcome_at |
✓ | Przechodnio, przez qub_id przedsobraz |
drand_round |
✓ | Przechodnio, przez qub_id przeciwdziedzina |
body |
✓ | Przechodnio, przez body_hash = SHA3-256(body) |
author_pubkey |
— (domyślnie) | Klucz, który zweryfikował podpis, jest autorem, z definicji |
cosigner_pubkey / cosigner_signature |
— | Niezależnie podpisane na tym samym sig_input (zob. §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | Zewnętrzny SealedQub pola, nie wewnątrz koperty — objęte własnymi niezmiennikami strukturalnymi (spójność okrągła / łańcuchowa), ale nie przez podpis autora. (drand_round jest teraz związany przechodnio poprzez qub_id preimage — patrz wyżej.) |
Dlaczego V2 jest jedynym akceptowanym preimage.
- W wycofanym preimage V1 strona z dostępem do zapisu przechowywanych bajtów mogła zamienić
sender_label(„Alice" → „Mallory") lub przekierowaćreply_todo innego rodzica — i ponownie zaszyfrować po rundzie — bez unieważniania podpisu autora, ponieważ żadnego z tych pól nie było w podpisanym preimage. V2 obejmuje oba, więc jakakolwiek zmiana któregokolwiek z pól przełącza weryfikację na „niepowodzenie". Ponieważ weryfikatory akceptują teraz wyłącznie V2, ta zamiana jest zamknięta dla każdego podpisu: podpis, który nie wiąże żadnego z tych pól (tzn. weryfikuje się wyłącznie względem V1), jest wprost odrzucany, a nie akceptowany przez obniżenie wersji. author_pubkeywewnątrz koperty pozostaje prawdziwą kotwicą tożsamości — widzowie MUSZĄ wyprowadzać tożsamość wyświetlaną zauthor_pubkey(przez warstwę poświadczeń z §9.5) zamiast ufaćsender_label.
Implementacje wyświetlające sender_label lub reply_to użytkownikom końcowym MUSZĄ wystawiać uwierzytelnioną tożsamość (odcisk klucza publicznego, poświadczenie) jako podstawowy sygnał tożsamości, a nie etykietę.
9.4 Procedura weryfikacji
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."
Weryfikacja podpisu jest najbardziej kosztowną operacją (szczególnie ML-DSA-65). POWINNA być wykonywana po przejściu wszystkich tańszych kontroli (hash, qub_id, unlock_at).
9.5 Poświadczenia tożsamości
Poświadczenia tożsamości — mapowanie author_pubkey na rozpoznawalne przez człowieka roszczenia tożsamości takie jak uchwyt qub, adres email, uchwyt społecznościowy lub poświadczenie passkey — są progresywnym ulepszeniem po stronie widza i nie są wymagane do weryfikacji podpisu. Widzowie, którzy rozwiązują poświadczenia do tożsamości wyświetlanej, MUSZĄ stosować precedencję:
handle > email > social > fingerprint
Fallback odcisku to małymi literami hex z SHA3-256(author_pubkey); jest zawsze dostępny dla dowolnego podpisanego qub. Widzowie MOGĄ go skrócić do wyświetlania — referencyjny widz renderuje qub: z następującymi czterema pierwszymi i ostatnimi bajtami (qub:<8 hex>…<8 hex>).
Zgodny weryfikator może wykonać każdą kontrolę z §9.4 bez kontaktowania się z API qub, bez żadnej sieci poza trwałym magazynem i drand i bez żadnego wyszukiwania po stronie serwera. Rozwiązanie poświadczenia jest osobnym krokiem best-effort wykonywanym tylko po pomyślnej weryfikacji podpisu.
9.6 Wpływ na rozmiar
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| Podpis | 64 bajty | 3 309 bajtów |
| Klucz publiczny | 32 bajty | 1 952 bajty |
| Razem na qub | 96 bajtów | 5 261 bajtów |
| Delta kosztu magazynu (przy ~5 USD/MB) | ~$0,0005 | ~$0,026 |
Dla qub tekstowego 500–2 000 bajtów ML-DSA-65 mniej więcej potraja przechowywany rozmiar. Bezwzględny koszt jest pomijalny.
9.7 Weryfikacja współpodpisującego (umowy bilateralne typu pact)
Dla umów bilateralnych (content_type = 0x03) druga warstwa podpisu dowodzi, że obie strony wyraziły zgodę na te same warunki.
Pola koperty:
cosigner_pubkey: Klucz publiczny ML-DSA-65 współpodpisującego (Strona B).cosigner_signature: Podpis nad tym samymsig_inputco autor (§9.3).
Oba pola MUSZĄ być obecne razem albo oba nieobecne. Jeśli obecne jest dokładnie jedno, widzowie MUSZĄ zgłosić błąd integralności.
Procedura weryfikacji:
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."
Właściwości:
- Współpodpisujący podpisuje identyczny
sig_inputjak autor — obie strony wiążą się z tym samymqub_id,body_hashiunlock_at(a w V2 również z tym samymsender_label_hashireply_to_or_zero). - Aby umożliwić kontrahentowi rekonstrukcję preimage V2 bez dostępu do surowych bajtów koperty, usługa etapowania wymusza w czasie etapowania, że
sender_labelkoperty paktu równa siępact_terms.party_a.label, areply_tojest nieobecne. Oba warunki są spełnione dla każdego paktu klienta referencyjnego; koperty naruszające ten warunek są odrzucane podczas etapowania. - Derywacja
qub_id(§4.1) NIE zawiera pól współpodpisującego. Dodanie współpodpisującego do istniejącej koperty nie zmieniaqub_id. - Pakt może być podpisany tylko przez autora (jednostronne zobowiązanie), tylko przez współpodpisującego (nietypowe) lub przez obu (pełny dowód bilateralny).
Brama wiązania email (operacyjna). Gdy zaetapowany pakt niesie kontakt email Strony B (§6.1), usługa uploadu qub MUSI odmówić żądania współpodpisu, chyba że istnieje krótkotrwały marker weryfikacji email pasujący zarówno do id etapu, jak i hash znormalizowanego email tego kontaktu. Marker jest zapisywany przez /api/v1/auth/verify gdy token magic-link niesie staging_id, a zweryfikowany adres pasuje do SHA-256(normalise_email(party_b.contact)) — gdzie normalise_email(addr) zachowuje wielkość liter części lokalnej i obniża do małych liter tylko część domeny (zgodnie z RFC 5321 §2.3.11), a SHA-256 tutaj to hash NIST FIPS 180-4 (różny od SHA3-256 używanego w derywacjach z §4) — i wygasa 900 sekund (15 minut) po wystawieniu. Jest to operacyjna brama anty-podszywania, NIE część dowodu qub on-chain — weryfikator strony trzeciej odtwarzający §11 potrzebuje tylko trwałego magazynu i drand, bez żadnego wyszukiwania po stronie serwera. Marker istnieje tylko po stronie serwera i nigdy nie jest częścią podpisanego ciała.
Wpływ na rozmiar (ML-DSA-65 autor + współpodpisujący):
| Komponent | Rozmiar |
|---|---|
| Podpis autora | 3 309 bajtów |
| Klucz publiczny autora | 1 952 bajty |
| Podpis współpodpisującego | 3 309 bajtów |
| Klucz publiczny współpodpisującego | 1 952 bajty |
| Całkowity narzut kryptograficzny | 10 522 bajty |
| Delta kosztu magazynu | ~$0,05 |
10. Renderowanie i sanityzacja Markdown
Ta sekcja jest krytyczna dla bezpieczeństwa. Widz renderuje quby tekstowe (content_type = 0x01) używając ograniczonego podzbioru Markdown.
10.1 Dozwolone elementy
- Nagłówki:
#do####(bez#####i######) - Wyróżnienie: pogrubienie (
**), kursywa (*), przekreślenie (~~) - Listy: uporządkowane (
1.) i nieuporządkowane (-,*) - Cytaty blokowe (
>) - Kod: zakresy inline (```) i bloki ogrodzone (`````)
- Linie poziome (
---) - Łamanie wierszy (dwie końcowe spacje lub pusta linia)
- Akapity
10.2 Zakazane elementy
| Element | Obsługa |
|---|---|
Surowy HTML (<div>, <script>, itd.) |
Całkowicie usuwany. Żaden HTML nie przechodzi. |
Obrazy () |
Usuwane. Składnia obrazów jest usuwana z wyjścia. |
Linki ([text](url)) |
URL renderowany jako widoczny tekst jawny. Bez auto-linkowania. Nie klikalny bez wyraźnej akcji użytkownika. |
| Niebezpieczne schematy URL | javascript:, data:, vbscript:, file: — usuwane. |
| Iframe'y, embedy, obiekty | Usuwane. |
| Encje HTML | Dekodowane do znaków wyświetlanych tylko jeśli bezpieczne. |
10.3 Implementacja
Implementacje MUSZĄ używać ścisłego parsera z allowlistą, a nie blocklistą. Zalecane podejście:
- Sparsuj Markdown używając
pulldown-cmark(lub odpowiednika). - Przejdź AST i odrzuć każdy węzeł nieobecny na allowliście (§10.1).
- Dla węzłów linków: emituj URL jako widoczny tekst, a nie jako klikalny element
<a>. - Skonwertuj przefiltrowane AST na typowaną reprezentację pośrednią (np. enum
MarkdownNodez tylko bezpiecznymi wariantami). Surowy HTML jest strukturalnie niereprezentowalny w tym IR. - Renderuj z typowanego IR do docelowej warstwy widoku (np. reaktywnych komponentów widoku, węzłów DOM). Bez konkatenacji łańcuchów HTML ani
innerHTMLw żadnym momencie.
Podejścia blocklist są kruche, ponieważ nowe rozszerzenia Markdown lub osobliwości parsera mogą wprowadzać niefiltrowane elementy. Podejście typowanego AST sprawia, że XSS jest strukturalnie niemożliwy — nie ma wariantu, który mógłby nieść dowolny HTML.
10.4 Limity rozmiaru i struktury
- Maksymalna głębokość renderowanego nagłówka:
####(H4).#####i głębsze są renderowane jako tekst pogrubiony. - Brak limitu liczby akapitów (limity rozmiaru ciała z §6 są ograniczeniem).
- Bloki kodu ogrodzonego: bez podświetlania składni w MVP. Renderowane jako monospace preformatowany tekst.
11. Weryfikacja przez strony trzecie
Każda osoba trzecia posiadająca przechowyte bajty (oraz K dla prywatnego/zawiniętego quba) może zweryfikować artefakt kryptograficzny bez współpracy qub. Niezależnie oznaczony czasem istnienie roszczenie dodatkowo wymaga albo zweryfikowane per-qub włączenie do pamięci stałej lub zweryfikowany dowód z rejestru przejrzystości §16.
1. Obtain the stored bytes. For a private delivery, also obtain K from the
delivery-link fragment.
2. Resolve the delivery shape (§8 step 3a): unwrap OuterWrapper with K, or
accept bare SealedQubCbor. Require shape/visibility agreement.
3. Parse SealedQubCbor → SealedQub; validate protocol version, visibility,
content type, chain identity, and structural bounds.
4. Recompute expected_round from unlock_at (§4.3); require the stored round
(allowing the documented legacy minus-one case) and ciphertext-stanza round
to agree exactly as §8 step 6a specifies.
5. Obtain the drand signature for SealedQub.drand_round and verify its BLS
signature against the pinned chain public key.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify envelope/sealed equality for qub_id, unlock_at, and outcome_at.
10. Recompute qub_id from the decrypted fields, sealed drand_round, and title;
require it to equal the carried qub_id (§4.1).
11. If sig_alg != 0x00, verify the V2 author signature (§9.4). If cosigner
fields are present, verify their pairing, key separation, and signature
(§9.7).
12. For an existence-time claim, independently verify either:
a. the permanent-storage transaction's data-to-id binding, owner, block
inclusion, and block timestamp; or
b. a §16 inclusion proof through its pinned anchor and anchor block time.
13. Report each verified claim separately; do not collapse an absent storage
or anchor proof into a successful timing verdict.
Co potwierdza weryfikacja:
| Dowód wejściowy | Co to ustanawia |
|---|---|
| Ważny pakiet / zapieczętowany artefakt + podpis drand | Odnalezione ciało pasuje body_hash; metadane powiązane z qub_id jest nienaruszony; szyfrogram jest powiązany z zadeklarowaną rundą drand; a ta runda upłynęła. To robi nie ustalić, kiedy zaszyfrowany tekst został utworzony. |
| Ważny podpis autora/współpodpisującego V2 | Posiadacz(e) odpowiadającego klucza(y) tajnego uwierzytelnił(y) podpisaną powierzchnię w §9.3. |
| Niezależnie zweryfikowana transakcja przechowywania per-qub | Dokładny przechowywany szyfrogram istniał nie później niż jego znacznik czasu bloku. |
| Ważny udokumentowany dowód przejrzystości z zakotwiczeniem | Roszczenie specyficzne dla rodzaju liścia w §16.11, w tym czas zobowiązania o górnej granicy od bloku kotwicznego. |
Czego weryfikacja nie udowadnia:
| Niepotwierdzone | Dlaczego |
|---|---|
| Autorstwo | Ten sender_label jest dekoracyjny. Bez sig_alg ≥ 0x01, każdy mógł zamknąć tę treść. |
| Intencja | Artefakt udowadnia bajty i zależności kryptograficzne, a nie to, co twórca subiektywnie miał na myśli. |
Wcześniejsze zobowiązanie od .qub sam |
Twórca może złożyć ważny pakiet po upływie wyznaczonej rundy. Osadzony podpis drand udowadnia, że runda upłynęła, a nie że szyfrogram istniał przed jej rozpoczęciem. |
| Dokładny czas naciśnięcia przycisku pieczęci | Znacznik czasu bloku magazynowego lub kotwicznego jest niezależnie weryfikowalnym górnym ograniczeniem i może opóźniać się w stosunku do lokalnej akcji użytkownika. sealed_at / received_at twierdzenia nie są dowodowe. |
Wdrożony rejestr przejrzystości (§16) rozszerza weryfikację na quby wraz z
zabezpieczony przed manipulacją zamawianie i bez zaufania maksymalny czas zobowiązania (the
czas bloku kotwicy), ograniczony przez rodzaj liścia (§16.11). Nie dodaje autorstwa ani
intencja; dla domyślnej ścieżki przesyłania niewrażliwej na bajty, sama w sobie nie udowadnia
body_hash lub drand_round, które nadal pochodzą z kontroli artefaktów.
12. Wersjonowanie i kontrola wydań
Wydania dokumentów, wewnętrzny protokół przewodów i zewnętrzna powłoka są oddzielne przestrzenie wersji. Dlatego dokument zawierający jedynie wyjaśnienie nie działa cicho zmienić bajty, a przyszła migracja przewodowa nie może udawać redakcji rewizja.
12.1 Wersja wydania dokumentu
Ta specyfikacja korzysta z semantycznych wydań dokumentu (MAJOR.MINOR.PATCH) i
niezmienny tag Git o nazwie protocol-v<release>.
- ŁATKA: dokładność lub poprawka redakcyjna, która nie zmienia zgodnych bajtów ani wymaganego zachowania.
- MNIEJSZY: wstecznie kompatybilne normatywne rozszerzenie, nowy wpis w rejestrze lub nowy niezależnie wersjonowany format towarzyszący.
- GŁÓWNE: niekompatybilna zmiana normatywna, w tym nowa wymagana interpretacja przewodu.
Status wydania jest jednym z Szkic (jeszcze nienormatywne), Bieżący (jedyny
zalecany docelowy sposób wdrożenia), lub Zastąpiony (zachowany ze względów historycznych
weryfikacja). Niewersjonowany /protocol trasa wyświetla Obecną wersję;
znacznik wydania zachowuje swoje dokładne źródło i każdą opublikowaną z nim lokalizację.
Zmiana statusu lub numeru wydania wymaga aktualizacji tej tabeli i wydania
historia w tej samej przeglądanej zmianie.
| Wydanie dokumentu | Data wejścia w życie | Status | Protokół transmisji danych | Opakowanie | Źródło |
|---|---|---|---|---|---|
| 1.0.0 | 23-09-2026 | Bieżący | 0x01 |
0x01 |
protocol-v1.0.0 |
12.2 Wersja protokołu
Ten version pole (u8) w obu SealedQub i QubEnvelope identyfikuje główną wersję protokołu.
- Widzowie MUSZĄ odrzucać nieznane główne wersje z wyraźnym błędem.
- W obrębie znanej głównej wersji, dekodery MUSZĄ odrzucać nieznane klucze mapy (§3.1) — ewolucja schematu zachodzi poprzez wprowadzenie nowego
version, nie poprzez dodawanie kluczy, które istniejące dekodery by pominęły. (Wcześniejsze wersje tej specyfikacji pozwalały na tolerowanie nieznanych opcjonalnych pól; ten zapis został wycofany — powodowałencode(decode(x))nieiniekcyjny i otworzył wektor ukrytej podpisanej zawartości w ładunkach paktu.) - Typy zawartości (
content_type) i schematy podpisu (sig_alg) są ograniczone wersjami: nowe wartości mogą być wprowadzane tylko wraz z nową wersją protokołu lub wyraźną aktualizacją rejestru.
12.3 Historia wersji protokołu
| Wersja | Wartość | Opis |
|---|---|---|
| v1 | 0x01 |
Dostawa prywatna/zapakowana i publiczna/bezpośrednia; tekst (0x01), pakt (0x03), oraz werdykt (0x04) ciała; ML-DSA-65 V2 autor/współpodpisujący podpisujący; drand quicknet tlock; SHA3-256. |
12.4 Wsteczna zgodność
Odbiorca v1 napotykający QubEnvelope z nieznanymi kluczami mapy CBOR (kluczami nie w kanonicznej kolejności §3.2) MUSI odrzucić go z błędem dekodowania (§3.1). Kompatybilność w przód opiera się na version pole, nie na tolerancji klucza: przyszłe dodatki — nawet drobne metadane — są dostarczane pod nowym version wartość, którą przeglądarka v1 odrzuca z wyraźnym błędem „nowszy protokół” zamiast cicho odrzucać zawartość, do której zobowiązują się podpisy.
Osoba korzystająca z przeglądarki v1 natrafiająca sig_alg = 0x01 (ML-DSA-65), ale brak wsparcia weryfikacji ML-DSA-65 POWINIEN wyświetlać zawartość qub z komunikatem „podpis obecny, ale nie do zweryfikowania”, a nie całkowicie odrzucać qub. Obecnie implementacja referencyjna odrzuca każdy sig_alg wartość inną niż 0x00 i 0x01 ponieważ rejestr v1 nie zawiera żadnego innego ważnego algorytmu — surowe odrzucenie i łagodne niepowodzenie są obserwacyjnie identyczne, dopóki nie zostanie zarejestrowany trzeci algorytm. Zachowanie typu łagodne niepowodzenie opisane powyżej staje się istotne, gdy §9.2 dopuszcza nowy wpis, a przeglądarka referencyjna zostanie wówczas zaktualizowana do łagodnego niepowodzenia.
12,5 Wersja Zewnętrznej Powłoki
Zewnętrzna powłoka opisana w §13 posiada własną version bajt, niezależny z SealedQub.version i QubEnvelope.version. Oba przestrzenie wersji rozwijają się osobno: przyszła kwantowo-bezpieczna wymiana symetryczna zwiększa bajt opakowujący, nie zmieniając wewnętrznej wersji protokołu, a przyszłe rozszerzenie warstwy protokołu (np. nowe pole w kopercie) zwiększa wewnętrzną wersję, nie zmieniając bajtu opakowującego.
OUTER_WRAPPER_VERSION_* |
Wartość | Algorytm | Status |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM z 12-bajtowym nonce, 16-bajtowym tagiem uwierzytelniającym, powiązanym AAD qub_id |
Aktywny dla prywatnej dostawy |
| — | 0x02–0xFF |
Zarezerwowany | Przyszłość |
Widzowie MUSZĄ odrzucać nieznane wersje wrappera z wyraźnym błędem. Protokół celowo utrzymuje przestrzeń wersji wrappera wąską, dopóki nie pojawi się konkretny sterownik migracji (np. zalecenia NIST faworyzujące inny AEAD); a 0x02 Miejsce zostanie przydzielone w tej samej rewizji, która wprowadza algorytm.
13. Zewnętrzna koperta szyfrowania
13.1 Uzasadnienie
Warstwy protokołu (QubEnvelope → tlock → SealedQub) sprawiają, że zapieczętowany qub jest zablokowany czasowo: ciało jest nieczytelne do unlock_at i opublikowania podpisu rundy drand. Jednak po odblokowaniu podpis rundy jest publiczny, a kanoniczny kształt CBOR SealedQub jest rozpoznawalny, więc zbieracz, który zaindeksował transakcje trwałego magazynu, mógłby masowo odszyfrować cały korpus qub.
W przypadku prywatnej dostawy, zewnętrzna powłoka szyfrująca zamyka ten kanał, wstawiając dodatkową warstwę symetrycznego AEAD pomiędzy kanoniczną SealedQubCbor i przechowywane bajty. W ścieżce browser-seal, klucz 256-bitowy K życia tylko w fragmencie URL adresu dostawy i na urządzeniach użytkowników; przeglądarki nie przesyłają fragmentów URL do serwerów, więc qub.social, każda brama magazynowa i każda sieć CDN przed którymkolwiek z nich są obserwacyjnie ślepe na K. Prywatna reprezentacja quba przechowywana jest zatem w postaci nieprzejrzystego szyfrogramu, którego tekst jawny jest nie do odzyskania bez adresu URL, który twórca wybrał do udostępnienia. Publiczne dostarczanie celowo pomija tę warstwę (§13.8).
Efekt netto:
- Odporność na wyliczanie dla prywatnej dostawy.
OuterWrapperpozostaje rozpoznawalnym, ustrukturyzowanym CBOR — nie jest dosłownie nieodróżnialny od losowych bajtów — ale jego pole szyfrogramu ukrywa rozpoznawalną zawartość wewnętrznąSealedQubkształt. Udokumentowana strategia zbieracza „Zapytanie GraphQL o nagie przesyłki w kształcie qub, hurtowe odszyfrowanie z użyciem publicznych podpisów drand” nie kończy się na tekście jawnym bez K. - Postawa prywatności związana z niszczeniem danych kryptograficznych dla domyślnego przepływu prywatnej przeglądarki. qub.social nie może odszyfrować tych przechowywanych artefaktów ze swojego domyślnego serwerowego źródła danych. Jawne odzyskiwanie, publiczna dostawa i zaufane serwerowe zabezpieczenie mają różne ujawnione granice zaufania.
- Dwupoziomowa drabina poufności. Domyślnie = dostęp kontrolowany przez link (ta sekcja). Prywatne quby szyfrowane dla odbiorcy (zarezerwowana funkcja Fazy 2, jeszcze niesprecyzowana) nakładają się na to jako drugi poziom.
13.2 Warstwowanie
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
Pieczętowanie i odblokowanie w warstwie protokołu (§7, §8) są niezmienione poniżej granicy opakowania; opakowanie podłącza się w miejscu wywołania seal() i odłącza w miejscu wywołania unlock().
13.3 Struktura danych 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
}
Niezmienniki pól.
versionMUSI być równe0x01dla bajtów wrappera v1.0.qub_idMUSI być równequb_idpole SealedQub odzyskane po odpakowaniu. Obie referencjewrap_sealed_qubiunwrap_sealed_qubanalizuj wewnętrzny CBOR i bezpośrednio wymuszaj tę równość; osobne powiązanie AAD umożliwia naruszenie zewnętrznej ochrony po opakowaniuqub_idnie powiodła się uwierzytelnianie.nonceMUSI mieć 96 bitów (12 bajtów), generowanych na nowo przez CSPRNG dla każdej operacji owijania. Ponowne użycie liczby nonce pod tym samym kluczem umożliwia ataki AEAD polegające na ponownym użyciu nonce, które odzyskują tekst jawny; producenci MUSZĄ traktować (key,nonce) par jako jednorazowe.ciphertextczy wyjście AES-256-GCM to bajty szyfrogramu połączone z 16-bajtowym tagiem uwierzytelniania.ciphertext.len() == SealedQubCbor.len() + 16dokładnie.
Kodowanie CBOR. Kanoniczny CBOR zgodnie z §3, z tą samą regułą uporządkowania kluczy (sortowane rosnąco po długości zakodowanych bajtów, następnie leksykograficznie). Cztery klucze to:
| Klucz | Zakodowane bajty | Kolejność |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
Pierwszy bajt CBOR OuterWrapper to zatem nagłówek mapy o określonej długości dla 4-wpisowej mapy (0xA4).
13.4 Powiązanie AAD z qub_id
Opakowanie wiąże qub_id jako dodatkowe dane uwierzytelnione AEAD. Jest to nośna strukturalna obrona przeciwko trzem klasom ataków:
| Atak | Obrona |
|---|---|
Przenieś szyfrogram pod inny qub_id pole w opakowaniu |
Nieprawidłowe dopasowanie AAD → uwierzytelnianie AEAD nie powiodło się |
| Wymieszaj fragment URL quba A z zapisanymi bajtami quba B | Błędny klucz (i niezależnie powiązany AAD) → uwierzytelnianie AEAD nie powiodło się |
Majstrować przy qub_id pole opakowania po przesłaniu |
Niezgodność AAD → uwierzytelnianie AEAD nie powiodło się |
Niesienie qub_id w tekście jawnym opakowania nie osłabia istotnie odporności na enumerację — qub_id sam jest hashem SHA3-256 preimage z §4.1 bez odzyskiwalnego preimage ze skrótu, a enumerator, który już zebrał bajty opakowania, nie dowiaduje się z widocznego qub_id niczego, czego nie mógłby wywnioskować z samego istnienia uploadu.
13.5 Algorytmy opakowania i rozpakowania
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
Kolaps trybu porażki. Niewłaściwy K, niewłaściwa liczba jednorazowa, niezgodność AAD i sfałszowany tekst zaszyfrowany — wszystkie produkują ten sam błąd DECRYPT_FAILED. Jest to celowa właściwość AEAD: rozróżnianie trybu porażki tworzyłoby kanał boczny, który zdalny atakujący mógłby sondować, wysyłając zniekształcone opakowania i mierząc czas odpowiedzi. Implementacje referencyjne MUSZĄ zwijać wszystkie porażki AEAD do jednego kształtu błędu.
13.6 Materiał klucza i dystrybucja
Klucz opakowujący K to 256-bitowa wartość losowa jednostajna generowana per qub przez CSPRNG. Implementacje referencyjne pozyskują go z:
- Twórca WASM:
getrandom(WebCrypto pod backendemwasm_js). - Wywołujący API pieczętowania po stronie serwera: jego lokalny CSPRNG; wywołujący dostarcza i zachowuje
Kjakowrapper_key_b64url. Worker używaKw pamięci na potrzeby opakowania, ale NIE MOŻE go utrwalać. Pozwala to idempotentnemu ponowieniu odzyskać zredagowaną odpowiedź z użyciem zachowanej przez wywołującego zdolności, zamiast polegać na jednorazowym sekrecie wygenerowanym przez serwer.
Dystrybucja: K MUSI być zakodowany jako URL-bezpieczny base64 (RFC 4648 §5, bez wypełnienia) i dołączony do linku udostępniania jako komponent fragmentu:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragment nigdy nie jest transmitowany do żadnego serwera przez zgodną przeglądarkę. Kanały odzyskiwania (indeks historii po stronie serwera, opcjonalne automatyczne wysyłanie email) trwale zachowujące pełny link udostępniania — wraz z fragmentem — poza urządzeniem użytkownika są wyraźnym kompromisem względem domyślnej postawy krypto-niszczenia i MUSZĄ być bramkowane wyraźną zgodą użytkownika.
Utrata fragmentu. Jeśli użytkownik zgubi fragment URL i nie ma kanału odzyskiwania, qub jest nieczytelny. To jest nośny kompromis projektu i MUSI być ujawniony użytkownikowi w czasie pieczętowania. MVP wzmacnia ujawnienie w czasie pieczętowania wyraźnym tekstem "zachowaj ten URL" i kanałem odzyskiwania z weryfikowanym email dla użytkowników, którzy się zdecydują.
13.7 Poza zakresem dla tej sekcji
- Podpisanie autorstwa (§9) pozostaje bez zmian: podpisy są obliczane wewnątrz wewnętrznego
QubEnvelopei są odzyskiwane po rozpakowaniu → odszyfrowaniu tlock → analizie CBOR. - Szyfrowanie kluczem publicznym odbiorcy (zarezerwowane
recipient_pubkeypole) jest przyszłą funkcją różniącą się od dzisiejszego trybu prywatnego, ograniczonego możliwością linku. - Obecny przepływ współpodpisywania paktu po stronie serwera emituje publiczne/gołe
SealedQubCborz widocznością0x01; nie może spełnić modelu K-secrecy przeznaczonego wyłącznie dla przeglądarek, ponieważ ostateczne uszczelnienie następuje po współpodpisaniu pośredniczonym przez serwer. Przyszły producent prywatnego porozumienia może użyć tego samego opakowania, które jest ślepe na typ zawartości wewnętrznej.
13.8 Publiczne quby (pominięcie opakowania)
Zewnętrzna powłoka jest opcjonalne na warstwie dostarczania. Twórca może zapieczętować qub jako publiczny, w którym przypadku kanoniczny SealedQubCbor wchodzi do rurociągu magazynowego bezpośrednio, bez OuterWrapper warstwa i brak klucza K:
SealedQubCbor bytes ──(public)──▶ stored as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored
Publiczny qub jest zablokowany czasowo, ale nie ograniczony linkiem: pozostaje nieczytelne, dopóki jego rundy drand nie zostaną opublikowane (warstwa tlock pozostaje niezmieniona), ale po odblokowaniu każdy, kto ma identyfikator transakcji przechowywania, może go odszyfrować — nie jest wymagany żaden fragment URL, ponieważ go nie ma K. To jest celowa wymiana na powierzchniach, które serwer musi obsługiwać: e-maile z powiadomieniami o ujawnieniu, linki oEmbed/auto-embed bez fragmentów oraz bogatsze SEO po ujawnieniu wszystkich potrzebują działającego linku bez tajemnicy, której serwer nigdy nie przechowuje (§13.6). Prywatny qub nadal może używać jawnego <qub-embed src="full_delivery_url"> formularz, gdy wydawca dostarcza swoją pełną zdolność do przenoszenia fragmentów.
Konsekwencje, które producent MUSI uwzględnić:
- Brak immunitetu wynikającego z wyliczenia. Public qubs rezygnują z własności immunitetu przed wyliczaniem §13.1 poprzez konstrukcję. Usługa przesyłania odniesień pieczętuje
Visibility: publicoznaczenie stałej pamięci na nich (i tylko na nich), aby były celowo możliwe do wykrycia; prywatne quby nie mają takiego oznaczenia i zachowują swoją nieodróżnialność bajtów. - Tytuł w postaci zwykłego tekstu ujawniony w czasie uszczelniania. §3.2
titlepole jest zwykłym tekstem wewnątrzSealedQubCbor. Pod opakowaniem jest ukryty, dopóki widz go nie odsłoniK; bez opakowania jest czytelne dla świata w pamięci trwałej od momentu przesłania, przed odblokowaniem. Zgodne aplikacje twórców MUSZĄ ujawniać to w momencie zatwierdzenia. - Wykrywanie jest strukturalne i sprawdzane krzyżowo. Zgodny przeglądarka/embed rozróżnia dwa przechowywane kształty przez analizę: bajty, które analizują się jako
OuterWrapperweź rozpakuj-zKścieżka; bajty, które analizują się jako nagieSealedQubCborsą akceptowane bezpośrednio. Odtworzona wartość wewnętrzna MUSI być zgodna (0x00dla opakowanych/prywatnych,0x01dla gołego/publicznego).qub_idnie wiąże widoczności, ale kanonicznySealedQubbajty to przenoszą, więc publiczne i prywatne wewnętrzne kodowania nie są identyczne pod względem bajtów.
Prywatny (opakowany) pozostaje wartością domyślną; publiczny jest wyraźnym wyborem twórcy dla pojedynczego quba.
14. Wektory testowe
14.1 Derywacja qub_id
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
Implementacje MUSZĄ produkować identyczne body_hash i qub_id wartości dla tego wejścia. Ten wektor testowy POWINIEN być pierwszym napisanym testem jednostkowym. Powyższe wartości kanoniczne zostały obliczone przez implementację referencyjną i MUSZĄ zgadzać się bit po bicie. Historyczne prototypowe układy sprzed uruchomienia (żadne działające quby nie zależały od pierwszych dwóch) używały 92 bajtów wcześniej outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) i 100 bajtów po dodaniu outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Obecny układ 108-bajtowy został następnie dodany drand_round i QUB_ID_V2 separator domeny. Wczesny wektor 108-bajtowy używał przestarzałego ceil mapowanie okrągłe (drand_round = 4695445) i wyprodukowany 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—nadal ważne qub_id dla tego zaokrąglonego wejścia, podczas gdy powyższy przykład podąża za mapowaniem bieżącej rundy §4.3.
14.2 Mapowanie odblokowań-rund
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 publikuje się o 1595431050 + (4675286 - 1) * 30 = 1735689600—dokładnie o unlock_at, nigdy wcześniej. (Wczesna wersja dziedzictwa ceil mapowanie dało 4675285, opublikowano w 1735689570—30 sekund wcześniej; weryfikatorzy akceptują tę starszą rundę zgodnie z §4.3.)
14.3 Round-trip kanonicznego CBOR
Implementacje MUSZĄ zweryfikować, że serialize(parse(serialize(qub))) == serialize(qub) dla wszystkich prawidłowych wejść. To jest test właściwości, a nie pojedynczy wektor.
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)
Kanoniczne bajty CBOR i body_hash SHA3-256 są obliczane przez implementację referencyjną. Implementacje MUSZĄ produkować bajtowo identyczny CBOR dla tego wejścia.
Implementacje MUSZĄ także zweryfikować, że serialize(parse(serialize(pact))) == serialize(pact) dla wszystkich prawidłowych wejść PactTerms (test właściwości).
14.5 Wektory wieloryzykowe zewnętrznej koperty
Zewnętrzna koperta (§13) ma osobną kanoniczną fikturę w crates/qub-core/tests/vectors/wrapper_v1.json. Każdy przypadek ustala krotkę (key, nonce, qub_id, sealed_cbor) jako nieprzezroczyste wejścia hex i potwierdza określone wyjście expected_wrapper_hex. Obie implementacje referencyjne konsumują ten sam plik JSON:
- 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).
Obecnie oprawa przypisuje trzy przypadki niskopoziomowych wrapperów. Testują one deterministyczne OuterWrapper kodowanie i interoperacyjność AEAD niezależnie od niezmiennika kształtu dostawy §13.8; w szczególności historyczna nazwa basic-text-public i jego wnętrze visibility = 0x01 do nie spraw, aby powstałe opakowane bajty były zgodną publiczną dostawą. Producent MUSI nadal przechowywać publiczne wewnętrzne bajty bez opakowania i opakowywać tylko prywatne (0x00) wewnętrzne bajty.
| Przypadek | Pokrycie |
|---|---|
basic-text-public |
Historyczna nazwa elementu niskiego poziomu. Najmniejszy realistyczny SealedQub kształt, bez pól opcjonalnych; testuje tylko bajty opakowania i nie jest zgodną z §13.8 dostawą przechowywaną. |
with-recipient-pubkey |
SealedQub z recipient_pubkey ustawić (zarezerwowana przyszła ścieżka). Wykonuje inny wewnętrzny zestaw kluczy CBOR; jego odrębna zawartość wyposażenia samodzielnie daje inny qub_id (recipient_pubkey samo nie znajduje się w odwzorowaniu wstecznym §4.1). |
longer-body |
~4 KiB treści — testuje wielobajtowe prefiksy długości CBOR zarówno wewnątrz wewnętrznej koperty, jak i w zewnętrznym szyfrogramie. |
Implementacje MUSZĄ produkować bajtowo identyczny expected_wrapper_hex dla zapisanych wejść. Regeneracja fikstury wymaga QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors i jest zarezerwowana dla celowych zmian formatu.
15. Zarządzanie profilem kryptograficznym (przyszłość)
Ta sekcja jest informacyjna dla v1 i staje się normatywna przy pierwszym wprowadzeniu drugiego algorytmu do dowolnego z prymitywów kryptograficznych qub.
15.1 Obecna postawa
Protokół v1 wiąże dokładnie jeden algorytm na prymityw:
- Podpis: ML-DSA-65 (
sig_alg = 0x01; 1952-bajtowy klucz publiczny, 3309-bajtowy podpis) i niepodpisany (sig_alg = 0x00). Kod źródłowy rezerwuje0x02dla Ed25519, ale protokół v1 go nie aktywuje; weryfikator v1 MUSI odrzucić każdysig_algna zewnątrz{0x00, 0x01}. - Blokada czasowa: drand quicknet tylko — hash łańcucha, klucz publiczny, czas genesis i okres są ustalonymi parametrami sieci przenoszonymi przez odniesienie
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) iconfig/drand-endpoints.json. - Zewnętrzna powłoka: AES-256-GCM v1 tylko (§13).
Weryfikatory obecnie na stałe kodują długości kluczy i podpisów dla aktywnego prymitywu. The sig_alg A bajty wrapper-version są jawnie określonymi selektorami, ale v1 nie przeprowadza negocjacji w kanale i przyjmuje tylko powyższe aktywne wartości.
15.2 Zamierzony kształt
Gdy drugi algorytm wejdzie do protokołu, weryfikator zostanie skonfigurowany dla nazwanego CryptoProfile (np. ExqubV1) wymieniającego dokładny zbiór dozwolonych wartości na prymityw — sig_algs, łańcuchy drand, wersje opakowania, typy treści. Profil jest ustalony w czasie weryfikacji, nigdy negocjowany w paśmie. Każda wartość poza aktywnym profilem jest odrzucana.
Gwarantuje to, że dodanie ML-DSA-87 lub aktywacja Ed25519 nie może retroaktywnie osłabić istniejących konfiguracji weryfikatora: weryfikator v1 pozostaje weryfikatorem v1 nawet po opublikowaniu profilu v2.
15.3 Warunki wyzwalające
Promuj §15 do statusu normatywnego, gdy zaproponowane jest którekolwiek z poniższych:
- Drugi
sig_algbajt (aktywacja Ed25519, ML-DSA-87 lub jakikolwiek nowy wpis w rejestrze §9). - Drugi łańcuch drand w użyciu produkcyjnym.
- Druga wersja zewnętrznej powłoki.
- Rotacja zaufanego korzenia transparency-log —
LogProfile.anchor_owneradres lub przypisany klucz publiczny paragonu (§16.6).LogProfilełączy powierzchnię profilu §15.2 jako element sterowany: obrót jest oznaczony znakiemLogProfilewzrost wysłany w aktualizacji weryfikatora (planowane rotacje podpisują krzyżowo wychodzące → przychodzące; rotacje wymuszone kompromisem nie mogą, i polegają na tym wzroście z kontrolą poprzedniego kotwicy do ograniczenia tymczasowych szkód). Przestrzenie wersji logu przejrzystości (LOG_VERSION,ANCHOR_FORMAT) rozwijać się jako niezależne rodzeństwo, dokładnie tak, jak wersja wrappera §12.5 jest niezależna od wersji protokołu.
Do tego czasu §15 jest placeholderem, który ustala kształt migracji, aby przyszłe PR-y lądowały przy znanym celu, a nie ponownie spierały się o powierzchnię negocjacji od zera.
16. Dziennik transparentności i poziomy trwałości (projekt — przegląd ukończony)
Status. Ta sekcja jest zaimplementowany (W5/UP-B1, Etapy 1–8), z producentem i zakresem zaufania podanym tutaj. Formaty transmisji, haszowanie oraz ścieżki weryfikatora są aktywne: podstawowe typy Merkle + canonical-CBOR (
qub-core), TypeScript mirror + ANS-104 bundler (workers/api/src/crypto/), jednego autoraLogDO+ przechowywanie węzłów R2 z kluczem współrzędnych/uploadpróba dołączenia dziennika, codzienny anchor + crony bundler-drain,GET /api/v1/qub/:tx_id/proof(włączenie) iGET /api/v1/log/consistency(RFC 9162) punkty końcowe dowodu, typowany dowód włączenia przenoszony w.qubpakiet (§17.5), natywny weryfikator kotwicy ANS-104 (tools/qub-verify), oraz podwójny hak samopublikujących się głów (§16.6). Udany/uploadzawsze jest trwały R2, ale jest pokryty kłodą tylko wtedy, gdyLOG_DOjest skonfigurowany i wstawiane do linii dane zostają pomyślnie dodane; dopiero wtedy jego odpowiedź ma znaczenielog_seq,receipt, ianchor_status. JeśliRECEIPT_SKjest nieobecny lub nieprawidłowy, ten paragonsig_b64urljest pusty i nie zapewnia żadnej niezaprzeczalności. Obecny/seala ścieżki publikacji paktu harmonogramują indywidualne transakcje Arweave, ale nie dodają liścia dziennika. Żaden kod obecnie tego nie wykonuje/uploadKomentarz dotyczący proponowanego późniejszego pojednania po niepowodzeniu dopisania. Zewnętrzna recenzja W5 jest zakończona: §16.15 rejestruje decyzje projektowe i ograniczenia uruchomienia, ale te ograniczenia nie zwiększają wcześniej podanego zasięgu producenta. Pozostały trzy elementy zaufania/deploymnetu zablokowane: (a) dedykowany portfel Anchor (ANCHOR_JWK;LogProfile.anchor_ownerjest nadal[0xAB; 32]placeholder); (b) klucz do podpisywania pokwitowania i dopasowujący klucz publiczny (RECEIPT_SKjest opcjonalne iLogProfile.receipt_pubkeyjest obecnie pusty); oraz (c) repozytorium GitHub self-published-heads + token (§16.6). Do czasu, gdy kotwice/profilowe piny zostaną przydzielone, samodzielny weryfikator raportuje stan dowodu uczciwie, zamiast twierdzić, że weryfikacja jest w pełni zakotwiczona i przypięta. Projekt jest ściśle addytywny i istnieje bez zmian wSealedQub/QubEnvelopeformat przewodowy.
16.1 Uzasadnienie i poziomy trwałości
Obecne ścieżki publikacji rozdzielają potwierdzenie od uznania w Arweave: wyprowadzają i podpisują indywidualną transakcję, przechowują artefakt i dokładny stan zgłoszenia w R2, a następnie publikują asynchronicznie. Dziennik przejrzystości dodaje niezależnie osadzoną warstwę porządkowania dla podzbioru ogólnego /upload wnioski których LogDO append zakończone sukcesem:
| Poziom | Imię | Gwarancja | Kiedy |
|---|---|---|---|
| T1 | R2-pierwsze synchroniczne potwierdzenie | Podstawa trwałości — zamknięte bajty i dokładny stan publikacji są zapisywane w trwałej pamięci przed zwróceniem sukcesu. | Wdrożono we wszystkich obecnych ścieżkach publikacji. |
| T2 | Uwzględnienie dziennika przejrzystości w partiach | Tylko do dopisywania, odporny na manipulacje zapis + całkowite uporządkowanie po uwzględnieniu i zakotwiczeniu. | Obecny producent: odnoszący sukcesy LogDO dopisywać od /upload; odpowiedź zawiera krotkę potwierdzenia. Nie uniwersalne. |
| T3 | Trwałość Per-qub Arweave | Pojedyncza transakcja Arweave dla qub. | Obecnie przygotowywane dla każdej zaakceptowanej publikacji i wysyłane asynchronicznie; dokładna podpisana transakcja pozostaje w opróżnialnej skrzynce wychodzącej do momentu dostarczenia. |
Poziomy opisują różne właściwości dowodowe i trwałości, a nie aktualny plan komercyjny. Obecny kod nadal harmonogramuje indywidualną transakcję Arweave dla każdej zaakceptowanej publikacji; nie udostępnia T3 wyłącznie jako płatnego dodatku. Limity klucza API/konta pozostają odrębnymi kontrolami aplikacji.
Trwałość, uczciwość. Zapis T1 jest synchroniczny, więc pomyślna odpowiedź ustanawia trwałość na poziomie aplikacji bez oczekiwania na bramę Arweave. Sam w sobie nie tworzy niezależnego znacznika czasowego. Potwierdzona pojedyncza transakcja dostarcza swoją górną granicę czasu bloku. Dla odpowiedzi zawierającej kompletną krotkę pokwitowania T2, następny potwierdzony anchor może dostarczyć dowód logu opisany poniżej. Jeśli krotka jest nieobecna, żadna powierzchnia nie może sugerować, że ten qub jest już w dzienniku przejrzystości. Opóźnienie anchor i publikacji nie ma protokołowego numerycznego SLA.
16.2 Struktura LogLeaf (dwa zobowiązane kształty)
Wpis dziennika to LogLeaf, zakodowany jako ręcznie pisany kanoniczny CBOR w profilu §3.1 (określona długość, brak tagów, brak liczb zmiennoprzecinkowych, liczby całkowite w najkrótszej formie, tekst NFC, pola opcjonalne pominięte gdy nieobecne, klucze uporządkowane rosnąco po długości zakodowanych bajtów, następnie leksykograficznie). Strażnik kanoniczny §3.1 parse → re-encode → compare jest stosowany na ścieżce kodowania przed haszowaniem (nie tylko przy dekodowaniu), więc dwie implementacje nie mogą nie zgadzać się co do bajtów liścia przez różnicę w szerokości liczby całkowitej lub kolejności kluczy. Wszystkie liczby całkowite to u8 / u64 / i64; wszystkie skróty to 32-bajtowe ciągi bajtów (bstr[32]). Przechowywany identyfikator transakcji Arweave to surowy 32-bajtowy skrót SHA-256 niesiony jako bstr[32], nigdy jako ciąg tekstowy base64url (zgodnie z §3.3).
Liść ma dwa kształty wybrane przez kind bajt, ponieważ ogólna ścieżka przesyłania jest ślepa na bajty: POST /api/v1/upload celowo traktuje oba akceptowane kształty ładunku jako nieprzezroczyste i odbiera qub_id i unlock_at tylko jako niezaufane twierdzenia klienta. Na domyślnej ścieżce prywatnej, body_hash, drand_round, created_at, i drand_chain_version są dodatkowo ukryte wewnątrz zewnętrznej osłony §13, której klucza Pracownik nigdy nie posiada. System typów definiuje również poświadczony kształt dla producenta, który wywodzi się body_hash / drand_round sam. Bieżący /seal trasa ma te wartości, ale nie wywołuje LogDO, więc produkcja obecnie emituje tylko potwierdzone (0x02) zostaje z udanych ogólnych przesyłek dodanych. Podział utrzymuje każdą zatwierdzoną wartość uczciwą, nie udając, że poświadczony producent jest połączony:
| Klucz | Enc. len | Typ | Obecność | Znaczenie |
|---|---|---|---|---|
seq |
cztery | u64 |
wymagany | Globalny indeks liścia oparty na 0; pozycja, do której dowód włączenia się odnosi. |
kind |
pięć | u8 |
wymagany | 0x01 udokumentowany-możliwy (zdefiniowany, obecnie nie emitowany) lub 0x02 potwierdzono (przesyłanie z pieczęcią klienta / przesyłanie bez wglądu w bajty). |
ref |
cztery | bstr[32] |
wymagany | Identyfikator odniesienia liścia. Poświadczony → surowy qub_id. Twierdzono → the oślepiony identyfikator SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
sześć | bstr[32] |
wymagany | Adres treści SHA3-256(stored_bytes) — jedne zadowalające związanie, które Pracownik zawsze może uczciwie obliczyć, na obu ścieżkach. |
unlock_at |
ten | i64 |
wymagany | Skopiowane (poświadczone) lub stwierdzone (stwierdzone); zweryfikowane > 0 zanim wejdzie do liścia. |
received_at |
dwanaście | i64 |
wymagany | Zegar ścienny pracownika przy R2-ack. Nie-dowodowy (potwierdzone przez operatora; §16.6). Obecne dla autoopisania, nigdy jako dowód. Zweryfikowane > 0. |
body_hash |
ten | bstr[32] |
kind=0x01 tylko |
Pominięto włączone 0x02 — Pracownik nie ma tego na mocy §13. |
drand_round |
dwanaście | u64 |
kind=0x01 tylko |
Pominięto włączone 0x02. |
Liść kind=0x02 celowo nie zobowiązuje ani do body_hash, ani do drand_round: poświadcza zobowiązanie i uporządkowanie nieprzezroczystego tekstu zaszyfrowanego pod adresem treści chash, twierdząc qub_id i unlock_at — a nie jego tekst jawny czy rundę. Człony tekstu jawnego/rundy dla asertowanego quba pochodzą z istniejącej weryfikacji pakietu .qub z §11, a nie z dziennika (§16.11). drand_chain_version nie znajduje się w liściu (na domyślnej ścieżce jest wewnątrz koperty); granularność łańcucha żyje na kotwicy (§16.7). Dyscyplina kodera: odrzuć ref lub chash złożone z samych zer oraz odrzuć niedodatnie unlock_at / received_at, odzwierciedlając strażnik sentinela outcome_at > 0 w cbor.rs.
16.2.1 Zaślepianie prywatnego quba
Dziennik nie może stać się wyrocznią enumeracji, której zapobiega zewnętrzna koperta §13 (§13.1). Dla prywatnego (opakowanego) quba liść asserted zobowiązuje się do zaślepionego identyfikatora SHA3-256(qub_id ‖ log_blind_secret), gdzie log_blind_secret jest sekretem przechowywanym przez serwer, i pomija body_hash. Strona trzecia nie może powiązać takiego liścia z konkretnym qub_id; posiadacz quba, który ma link udostępniania i tym samym qub_id, może ponownie obliczyć zaślepienie, aby potwierdzić własną inkluzję. Publiczny qub (już enumerowalny, już niosący tag Arweave Visibility: public zgodnie z §13.8) zobowiązuje się do surowego qub_id. Jest to jedyne miejsce, w którym samodzielna weryfikowalność celowo ustępuje nośnemu niezmiennikowi prywatności; samodzielnym powiązaniem dla prywatnych qubów jest chash (§16.9).
Kustodia log_blind_secret (rozstrzygnięte — §16.15 Q4). Zaślepienie chroni niełączliwość liści, a nie poufność tekstu jawnego (kopertę §13 trzyma to niezależnie). Przy kompromitacji log_blind_secret dla dowolnego qub_id, który przeciwnik już posiada lub może odtworzyć (każdy qub, którego pakiet/URL posiada, plus każdy qub_id o niskiej entropii lub publiczny), ponownie oblicza ref liścia w jednym haszu i go łączy — jest to bezpośrednie powiązanie znanej populacji, a nie atak siłowy nad nieznaną przestrzenią. Klasyfikuj log_blind_secret jako sekret rangi korelacji/Sybil w tej samej warstwie kustodii co inne sekrety serwera i rotuj wyłącznie w przód (rotacja ponownie zaślepia przyszłe liście; nie może retroaktywnie rozłączyć już zakotwiczonych).
16.3 Haszowanie liści i węzłów
Haszowanie z separacją domen RFC 6962 §2.1 z SHA-256 zastąpionym przez 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
Bajty prefiksu domeny 0x02 (łańcuch wpisów, §16.4) i 0x03 (skrót STH, §16.6) są zarezerwowane i rozłączne z powyższymi. Są pojedynczymi bajtami i tym samym nie mogą kolidować z istniejącymi 10-bajtowymi separatorami domeny ASCII (QUB_ID_V2 itd.). Drzewo to lewostronnie pełne niezrównoważone drzewo RFC 6962 (każdy podział wewnętrzny przy największej potędze dwójki ściśle mniejszej niż liczba liści poddrzewa), co pozwala dowodom inkluzji i spójności współdzielić jeden algorytm ścieżki audytowej. Specyfikacja referencyjna niesie jawny pseudokod derywacji lewo/prawo i ustala wektor testowy o liczbie liści niebędącej potęgą dwójki (5 liści), aby przypadek promocji prawej krawędzi — który wektor 4-liściowy ukrywa — został przećwiczony.
16.4 Łańcuchowanie haszy (wewnętrzne)
LogDO utrzymuje wewnętrzny łańcuch wpisów wyłącznie dla spójności po awarii. Nigdy nie jest publikowany i nigdy nie jest widoczny dla weryfikatora:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
Publikowanym autorytetem tylko-do-dopisywania jest kumulatywny korzeń Merkle + jego kotwica (§16.5–16.6), nigdy surowa kolejność, w jakiej operator akurat serwuje liście: łańcuch przelicza się na nowo dla dowolnej serwowanej kolejności, więc tylko zakotwiczony korzeń ustala kanoniczną pozycję.
16.5 Kumulatywne drzewo Merkle i wsadowanie
Istnieje jedno wiecznie rosnące drzewo RFC 6962 nad wszystkimi liśćmi w kolejności seq — a nie izolowane drzewa per wsad. (Konstrukcja per wsad łańcuchowana liściem przeniesienia została odrzucona: nie jest prawdziwą relacją prefiksu, więc jej „dowody spójności" są niepoprawne.) Kumulatywne drzewo daje autentyczne dowody spójności RFC 9162 i pozwala pojedynczej niedawnej kotwicy udowodnić inkluzję dla dowolnego starszego quba.
Ten LogDO Trwały obiekt jest pojedynczy pisarz (blockConcurrencyWhile, odzwierciedlając QuotaDO / EntitlementDO) — dopisywanie do wspólnego dziennika to operacja odczytu-modyfikacji-zapisu na wspólnym stanie i dlatego MUSI przechodzić przez DO, nigdy KV. Buforuje granicę prawego skraju drzewa (O(log n) hasze), więc zamykanie partii jest O(batch). A partia jest zbiorem liści połączonych razem; jego zaimplementowane wyzwalacze to tree_size postęp co najmniej LOG_BATCH_MAX_LEAVES (domyślnie 4096), wiek osiągający kadencję kotwicy lub wyraźne administracyjne/zadanie cron wymuszające zamknięcie. root_i jest skumulowanym haszem drzewa Merkle nad liśćmi 0 .. tree_size_i.
16.6 Podpisana głowa drzewa przez kotwicę Arweave
Transakcja kotwiczenia Arweave jest podpisaną głową drzewa (Signed Tree Head) i zastępuje podpis operatora dla samej głowy drzewa: dzienna kotwica nie potrzebuje żadnego klucza quba, ponieważ podpisem jest owner transakcji Arweave. Teza fosy się utrzymuje — nośny dla zakotwiczonego korzenia jest niezmienny substrat, a nie sekret trzymany przez qub.
Projekt dziennika przewiduje jeden ciepły udany dopisek klucz paragonu (§16.10), przypięty w LogProfile i współpodpisany przez anchor_owner. Obecna implementacja nie ukończyła tego dostarczania zaufanego rdzenia: RECEIPT_SK jest opcjonalne, brakujący/nieprawidłowy klucz daje sig_b64url: "", oraz skompilowany LogProfile.receipt_pubkey jest pusty. Taki pokwitowanie może opisać dołączony liść, ale jest nie nierozliczalny podpisany pokwitowanie. Silniejsze twierdzenie projektowe ma zastosowanie tylko wtedy, gdy zweryfikator zwalnia piny pasującego klucza publicznego, a właściciel kotwicy podpisuje go krzyżowo. Odpowiedź publikacji bez pełnej krotki pokwitowania nie stanowi żadnego twierdzenia o akceptacji dziennika; taka z pustym podpisem stanowi twierdzenie o pozycji dopisania, ale nie o weryfikacji podpisu.
SignedTreeHead to kanoniczny CBOR (klucze po długości zakodowanej): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (poprzedni sth_hash; genesis = 32 bajty zerowe), log_id:bstr[32], first_seq:u64, anchored_at:i64. Jego skrót to sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Przypięty korzeń zaufania. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Zgodny weryfikator MUSI wymagać anchor_tx.owner == LogProfile.anchor_owner, gdzie anchor_owner (oraz klucz publiczny klucza potwierdzenia) jest wbudowany w qub_core jako LogProfile — obok stałych quicknet już obecnych w DrandTimelockProvider::quicknet() — i dystrybuowany z binarką weryfikatora. Weryfikator MUSI również zweryfikować powiązanie dane transakcji Arweave → tx_id lokalnie, zamiast ufać odpowiedzi /raw/ z bramy. Zamyka to dziurę ekwiwokacji nieuczciwego portfela: „zakotwiczone w Arweave" jest bez znaczenia, dopóki weryfikator nie przypnie, który portfel.
Rotacja jest rozszerzeniem zarządzania §15, a nie ponownym użyciem (rozstrzygnięte — §16.15 Q3). Powierzchnia profilu §15.2 obecnie wymienia tylko sig_algs / łańcuchy drand / wersje opakowania / typy treści, a lista wyzwalaczy §15.3 nie wymienia żadnego z nich — LogProfile / anchor_owner nie jest jeszcze w powierzchni §15. Zarządzanie rotacją musi zatem zostać zbudowane: §15.3 jest rozszerzone (poniżej) o dodanie wyzwalacza LogProfile, a rotacja to podpisany bump LogProfile dostarczony w aktualizacji weryfikatora. Planowana rotacja niesie podpis krzyżowy wychodzący → przychodzący; rotacja wywołana kompromitacją nie może (wychodzący klucz jest niezaufany/niedostępny właśnie wtedy) i wraca do bumpu rządzonego przez §15, przy czym kontrola rozgałęzienia poprzedniej kotwicy (poniżej) ogranicza szkody w międzyczasie.
Okno dwuznaczności (parametr zaufania pierwszej klasy). Liść jest odporny na dwuznaczność tylko wtedy, gdy jego osłonowy kotwica jest Arweave-potwierdzono. Okno jest received_at → anchor confirmation (kadencja + nieodwracalność Arweave, bez gwarancji opóźnienia protokołu). Przed dostarczeniem zaufanego źródła obecna implementacja zapewnia integralność operacyjną qub oraz wszelkie dostępne niepodpisane metadane dopisywania; nie zapewnia zaplanowanej gwarancji niemożliwości zaprzeczenia. Trzy artefakty odpowiedzialności definiują ukończony projekt (model świadka to rozdzielczość §16.15 Q2):
- Potwierdzenie odbioru (zależne od zaopatrzenia) — analog SCT powraca, gdy dodawanie logu przesyłki powiedzie się (§16.10). Staje się nieodrzucalny dopiero wtedy, gdy
sig_b64urlniepusty i odpowiadająca relacja klucza publicznego/właściciela punktu zaczepienia jest przypięta w weryfikatorze. Obecnie pusty pin profilu produkcyjnego nie może obsłużyć tej decyzji. Ta kontrola nie dotyczy pominiętego tupelu potwierdzenia ani niepodpisanego potwierdzenia. - Opublikowana metodologia monitorowania + chodzenie po poprzednim łańcuchu — kotwica
prevłańcuch jest chodzony głowa→geneza; rozwidlenie (dwa kotwice w jednymsizez różnymiroot, lub złamanyprev) jest opublikowalnym dowodem złego zachowania. Wykrywanie dwuznaczności jest deklarowanym zobowiązaniem operacyjnym, a nie cichym założeniem. - Podwójne samodzielnie wydane głowy — każda nowa głowa
{sth_hash, tree_size}jest przesyłany na dedykowany, należący do qub publiczne repozytorium GitHub tylko do dopisywania (nośna, zabezpieczona przed manipulacją, publikowana samodzielnie noga), z postem w mediach społecznościowych wyłącznie jako weryfikacja w miarę możliwości. Nieudane opublikowanie MUSI wywołać stronę (nie powiodło się cicho). Zaimplementowano (Etap 8) jakopublishHeadzaczep na kotwicę cron (workers/api/src/utils/heads-publish.ts): aPUTdo API zawartości bezshajest tylko do dopisywania (a422oznacza, że głowa jest już opublikowana, nigdy nie jest nadpisywana); opcjonalnie / z wdrożeniem zabezpieczonymPUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}i bezczynny, dopóki repozytorium nie zostanie przygotowane. Twarda strona błędu GitHub poprzezhealth_alertkanał i powoduje wzrost trwałego wskaźnika awarii (m:tlog_publish_head_fail); sam kotwica Arweave nigdy nie cofa się w przypadku niepowodzenia publikacji. „Nie zawieść cicho” jest gwarantowane przez ten trwały wskaźnik — na który operacje MUSZĄ mieć alert w dashboardzie — nawet jeśli strona e-mailowa wysyłana na zasadzie najlepszego wysiłku nie może zostać dostarczona. Dwie uczciwe ograniczenia wynikają z „kotwica przy postępie” (cron publikuje tylko wtedy, gdy rozmiar się zwiększa): przejściowa awaria GitHub pozostawia przerwa w sekwencji opublikowanych głów dla tego rozmiaru — ograniczone, nie ciche (stronicuje), i ponieważ każda głowa zatwierdza drzewo nadzbioru, dowód spójności §16.9 wypełnia lukę; co istotne, ten dowód spójności jest obliczany z autorytatywne drzewo zakotwiczone w Arweave, a nie z powierzchni GitHub, więc luka na GitHubie nigdy nie osłabia możliwości weryfikacji. Uzupełnienie zaległości nadrabiające braki w opublikowanych nagłówkach jest odroczonym ulepszeniem.
Więź uczciwości (ograniczenie wiążące). Ponieważ qub kontroluje zarówno planowane powierzchnie do publikacji, to jest samodzielnie wydany, nie jest niezależnie poświadczone. Żaden produkt, marketing ani powierzchnia prawna nie mogą twierdzić, że dziennik jest „niezależnie poświadczony”. Po udostępnieniu bramek odbioru/profilu/głów, dozwolone jest twierdzenie, że dwuznaczność jest wykrywalna, a pomyślnie podpisane dołączenie pozostawia niezaprzeczalny paragon. Przed tym momentem, to roszczenie jest niedostępne. Prawdziwy niezależny świadek zewnętrzny zostaje odroczony do przyszłej aktualizacji zarządzania według §15.
received_at jest asertowane przez operatora i żadne twierdzenie nie może się na nim opierać — nigdy nie jest pokazywane jako dowód ani jako potwierdzenie sporu na żadnej powierzchni produktu / prawnej / API / renderowania dowodu. Czas bloku kotwicy Arweave T jest jedynym beztrwałym znacznikiem czasu wymagającym zaufania (górna granica „zalogowano przez"). Każda kontrola poprawności monitora na received_at MUSI porównywać wobec T, a nie wobec kontrolowanego przez operatora pola STH anchored_at; taka kontrola jest zabezpieczeniem wyłącznie przed błędem zegara uczciwego operatora, a nie kontrolą rozliczalności wobec złośliwego operatora (§16.15 Q5).
16.7 Format transakcji kotwicy i kadencja
AnchorBundle to ciało transakcji Arweave w kanonicznym CBOR, zapisywane przez bundler z §16.8: ver:u8, sth:bstr (kanoniczne bajty SignedTreeHead), prev_anchor:bstr (surowe bajty identyfikatora poprzedniej transakcji kotwicy; pominięte przy genesis), chain_hash:tstr (obowiązujący łańcuch drand — quicknet) oraz strumień CBOR liści wsadu w kolejności seq, aby kotwica była samodzielna: monitor wyprowadza ponownie root z ciała bez żadnej zależności od qub. (Jeśli strumień liści stanie się duży przy wysokim wolumenie, przyszła rewizja może zobowiązywać się tylko do zakresu liści przez referencję; odnotowane, nieprzyjęte w v1.)
Tagi Arweave są celowo enumerowalne — dziennik ma być znajdowany, w przeciwieństwie do prywatnych qubów: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tagi są niezaufanymi wskazówkami; ciało CBOR jest jedynym autorytetem.
Kadencja: codziennie domyślnie, przeanalizowane ponownie z wolumenem (wyzwalacz rozmiaru automatycznie skraca efektywną kadencję pod obciążeniem). Obecny producent nie implementuje haka siłowego kotwicy płatnej pieczęci. The portfel Anchor jest dedykowany i niskoprędkościowy, oddzielony od portfela do przesyłania — MUSI być jego własny JWK (odrębny klucz, a nie logiczna rola w portfelu do przesyłania) więc kompromitacja portfela do przesyłania nie może fałszować kotwic — z twardym dziennym limitem transakcji kotwiczących. Postawa w zakresie powiernictwa jest jasno określona: a wąsko zakrojony klawisz skrótu z ciasnym wyłącznikiem i niskim saldem, nie „zimny” — portfel, który automatycznie podpisuje transakcje codziennie, nie może być zimny, a specyfikacja nie udaje inaczej.
16.8 Bundler ANS-104
Własny enkoder DataItem ANS-104 i podpisujący deep-hash, mniej więcej 300 linii, wyłącznie Web Crypto, zero zależności npm (oba SDK Turbo nie przechodzą bramy łańcucha dostaw npm ci --ignore-scripts). Układ bajtów DataItem:
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
Podpisywanie to Arweave deepHash — rekurencyjny skrót SHA-384 (wymóg na drucie Arweave, crypto.subtle.digest("SHA-384")) nad ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — następnie RSA-PSS nad deep hashem kluczem JWK portfela przez crypto.subtle; id = base64url(SHA-256(signature)). Tutejszy SHA-384 jest odizolowany jako prymityw wyłącznie dla drutu Arweave, nigdy jako prymityw zaufania quba (§15 odnotowuje to ogrodzenie; haszowanie zaufania quba to SHA3-256 wszędzie).
Ścieżka kodu ANS-104 obsługuje mechanizm odroczonego przejścia/zlewania i zapisuje AnchorBundle DataItems. Zwykła ścieżka publikacji najpierw tworzy dokładną podpisaną transakcję Arweave i przechowuje jej JSON w trwałej skrzynce nadawczej; bezpośrednie publikowanie jest optymalizacją opóźnień, a ścieżka odprowadzenia próbuje ponownie tę samą transakcję przed zastosowaniem mechanizmu awaryjnego bundlera. Schemat podpisu (rozwiązane — §16.15 Q8): v1 podpisuje przy użyciu RSA-PSS (typ podpisu 1) ponowne wykorzystanie istniejącego mechanizmu portfela Arweave JWK (brak nowych długożyjących kluczy w zarządzaniu, wspierając tezę „jednego sekretu mniej”); Ed25519 jest odroczony do ścieżki migracji PQ w §15.
Ręcznie napisany deep hash jest kodem o najwyższym ryzyku i najniższym naturalnym pokryciu w W5, więc jego bramkowanie jest nienegocjowalne (§16.15 Q8):
- Wielojęzyczny fixture
tlog_v1.json(Rust + TS, wzorzecwrapper_v1.jsonz §14.5) pokrywa deep-hash, bajty + id DataItem, hasze liści, korzeń 5-liściowy + ścieżkę audytową, skrót STH, dowód inkluzji i dowód spójności — w obu kierunkach: podpisu i weryfikacji (kierunek weryfikacji ma znaczenie, ponieważ lokalna kontrola transakcji → tx_id z §16.6 wciąga deep hash do każdego samodzielnego weryfikatora, a nie tylko piszącego). - Jednorazowy interoperacyjny round-trip przez referencyjny bundler ANS-104, konsumowany wyłącznie jako statyczne dane testowe — nigdy jako zależność uruchomieniowa npm (postawa wyłącznie-Web-Crypto / brak skryptów instalacyjnych pozostaje w mocy).
- Ścieżka deep-hash + RSA-PSS musi wykonać round-trip przez te same prymitywy
crypto.subtle, których używa produkcja, aby własny enkoder był bajtowo kompatybilny. - Ciągły monitor akceptacji po wsadzie potwierdza, że każdy DataItem kotwicy / awaryjny faktycznie osiąga akceptację Arweave, z alarmem + bezpiecznikiem obwodu — ponieważ deep hash obsługuje również awaryjną kolejkę na wypadek niedostępności Arweave, więc cicha regresja wypełniłaby tę kolejkę elementami odrzuconymi przez sieć dokładnie podczas tej awarii, którą ma pokrywać.
16.9 Dowody inkluzji i spójności
Oba są RFC 9162, SHA3-256, serwowane jako kanoniczny CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (dokładny CBOR liścia — weryfikator sam przelicza leaf_hash i nigdy nie ufa dostarczonemu skrótowi), 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. Pojedyncza jednoznaczna lista kluczy, ustalona wektorem testowym.
Samodzielna weryfikacja (brak serwera qub, rozszerza §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).
Magazyn serwujący dowody MUSI być kluczowany współrzędnymi (rozstrzygnięte — §16.15 Q7, blokujący warunek wstępny). Generowanie dowodu dla zimnego liścia jest neutralne dla poprawności tylko jeśli materiał audytowy w R2 jest trwałym magazynem węzłów Merkle kluczowanym absolutną współrzędną drzewa (level, index) — a nie deltami węzłów per batch. Przy magazynie kluczowanym współrzędnymi dowolna ścieżka audytowa (leaf i, size N) to zbiór O(log N) bezpośrednich GET-ów R2 z brakiem przeliczania przez granice wsadów; przy magazynie kluczowanym wsadem tak nie jest, co stanowi lukę układu magazynu, którą to rozstrzygnięcie zamyka. Ciała liści są podobnie adresowalne treścią przez seq. Wektor testowy W5 MUSI udowodnić zimny liść z epoki genesis wobec znacznie późniejszego korzenia używając wyłącznie R2 + Arweave przy wymazanym magazynie LogDO, aby twierdzenie o bezpieczeństwie odzysku z §16.13 było poparte, a nie tylko asertowane. O(log N) sekwencyjnych GET-ów R2 należy wyłącznie na asynchronicznym punkcie końcowym dowodu — nigdy na gorącej ścieżce pieczętowania (§16.10) ani na cronie per tick.
16.10 Uporządkowanie potwierdzenia z R2 na pierwszym miejscu
Zaimplementowany POST /api/v1/upload ciąg jest:
- Bramki przedniego połowy (uwierzytelnianie, weryfikacja, klucz fragmentu niezmienności) — bez zmian.
- Utwórz, oznacz i podpisz dokładną pojedynczą transakcję Arweave. To pochodzi
tx_idlokalnie, chociaż tworzenie transakcji może pobierać metadane nagród/zakotwiczeń z bramy. Niepowodzenie przygotowania nadal powoduje niepowodzenie żądania przed potwierdzeniem. - Synchronicznie zapisz wybrany artefakt w
qub-cache/<tx_id>i zachowuj stabilne rekordy tworzenia-operacji/wyjściowej skrzynki. To są minimum trwałości i ponawiania; niepowodzenia przed rozliczeniem zwracają 503. - Kiedy
LOG_DOjest skonfigurowany, próbować synchronicznieLogDO.append(leaf). Pojedynczy pisarz przydzielaseq, rozszerza łańcuch wpisów i aktualizuje granicę. RPC do dołączania robi tylko to; zamknięcie partii działa poza ścieżką w alarmie. Obecnie awaria transportu/aplikacji podczas dołączania jest awaryjny tryb pracy: odpowiedź nadal może odnieść sukces bezlog_seq,receipt, lubanchor_status. Pomimo komentarza dotyczącego wdrożenia, dziś nie jest przewidziane automatyczne późniejsze uzgadnianie dzienników. - Zwróć potwierdzenie. Dołącz
{ log_seq, anchor_status: "pending", receipt }tylko wtedy, gdy append zwrócił kompletną, udaną krotkę.receipt.sig_b64urljest pusty, gdy podpisujący paragon jest niedostępny; klienci NIE MOGĄ uznawać tej wartości za podpisaną ani niemożliwą do odparcia. Brak tej pary oznacza jedynie trwałą publikację, a nie akceptację w dzienniku przejrzystości. - Użyj jednego odroczonego zadania, aby wysłać dokładną podpisaną transakcję. Sukces usuwa skrzynkę nadawczą; niepowodzenie pozostawia ją dla ograniczonego cron do opróżniania i nie może zmieniać już potwierdzonych danych
tx_id. Tymczasowe metadane i inne dodatkowe elementy przygotowywane według najlepszych starań są również odroczone.
Granica opóźnienia. Ścieżka żądania obejmuje pracę z autorytetem/kwotą przedniej części, przygotowanie/podpisywanie transakcji, trwałe zapisy R2 oraz (jeśli skonfigurowano) LogDO próba. < 300 ms pojawia się w przeglądzie projektu jako cel operacyjny, a nie gwarancja protokołu; obecny etap przygotowania transakcji może wykonywać żądanie metadanych bramki. Alarmy opóźnień i bramy startowe są kontrolami operacyjnymi, a nie dowodami dostępnymi weryfikatorowi.
16.11 Model zaufania — precyzyjne twierdzenie, zakresowane rodzajem liścia
Dla kind=0x01 (poświadczony): „Ta treść — ciało pasujące do body_hash, identyfikowane przez qub_id — została zobowiązana do dziennika tylko-do-dopisywania qub na pozycji seq i istniała nie później niż czas bloku Arweave T; była kryptograficznie nieczytelna do rundy drand R = unlock_round(unlock_at)." Jest to pełna trójka {powiązanie rundy tlock + inkluzja Merkle + zakotwiczony korzeń}.
Dla kind=0x02 (asertowany, domyślny): „Nieprzezroczysty tekst zaszyfrowany o adresie treści chash, twierdzący qub_id i unlock_at, został zobowiązany do dziennika tylko-do-dopisywania na pozycji seq i istniał nie później niż czas bloku Arweave T." Człony rundy i ciała są dostarczane przez istniejącą weryfikację pakietu .qub z §11 (qub_core::unlock), a nie przez dziennik; co dziennik dodaje ponad gołą transakcję per qub, to uporządkowanie odporne na manipulację, beztrwałościowy czas zobowiązania w górnej granicy i odporność na ekwiwokację.
Oba twierdzenia wykluczają, zgodnie z §11: autorstwo bez sig_alg ≥ 0x01, intencję i czas o granularności poniżej kotwicy. Żadne nie pozwala, by jakiekolwiek twierdzenie opierało się na received_at.
Limit roszczeń (wiążące ograniczenie uruchomienia — rozwiązane §16.15 Pytanie 1). Dla stwierdzonego (kind=0x02) liść, powyższe ograniczone roszczenie jest sufit na czymkolwiek produkt, marketing, warunki lub powierzchnia wyświetlająca dowód mogą twierdzić. Żadna powierzchnia nie może stwierdzać ani sugerować, że log dowodzi zawartości lub rundy odblokowania wysyłki byte-blind — log dowodzi kolejności + bezpiecznego w zaufaniu czasu zobowiązania górnej granicy nieprzejrzystego szyfrogramu. Dowód zawartości i rundy pochodzi wyłącznie z istniejącego §11 .qub-weryfikacja pakietu, która jest niezależna od dziennika. Publikacja bez pomyślnego dołączenia/odbioru w ogóle nie ma roszczenia do dziennika.
16.12 Wersjonowanie i koordynacja W3
Jest nie SealedQub wypukłość przewodu i dlatego bez podwyżki wersji protokołu (§12.2): dziennik jest sidecarem, który zapisuje się w istniejących polach i bajtach, więc nie wchodzi do historii wersji protokołu z §12.3. Opcjonalne dla W3 drand_chain_version pozostaje nienaruszony i pozostaje jedyną opcjonalną SealedQub pole. Zamiast tego dziennik wprowadza własne, niezależne przestrzenie wersji — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — odzwierciedlając niezależność wersji wrappera z §12.5 (wrapper zawiera bajt wersji niezależny od wersji protokołu, a wersje logów podążają za tą samą separacją).
Dostarczanie dowodu jest domyślnie pobierane, z opcjonalnym dołączeniem. Dowód nie może istnieć w czasie pieczętowania (kotwica nie została jeszcze zapisana), więc pakiet .qub z czasu pieczętowania pozostaje bez dowodu. Weryfikator z W7 pobiera GET …/proof raz, albo w trybie w pełni offline rekonstruuje dowód z publicznego AnchorBundle przez zapytanie Arweave o Log-Id. Pakiet .qub (W7) rezerwuje opcjonalny człon inclusion_proof — nieobecny przy pieczętowaniu, wypełniany przez ponowny eksport po zakotwiczeniu dla zimnej archiwizacji — podążając za tym samym wzorcem „opcjonalne, domyślnie pominięte, addytywne" co drand_chain_version z W3.
16.13 Retencja
Okna retencji dla otwartego ogona LogDO, podłoża R2 serwującego dowody, liczników bezpiecznika obwodu kotwicy i awaryjnej kolejki bundlera są wyspecyfikowane w docs/DATA-RETENTION.md. Zasada: gorący magazyn dziennika per wpis (LogDO) jest odzyskiwalny po zakotwiczeniu; jego materiał audytowy — magazyn węzłów Merkle kluczowany współrzędnymi (level, index) + ciała liści adresowane przez seq (§16.9) + kotwice Arweave — jest trwały. Odzyskanie zimnego liścia z DO nigdy nie unieważnia wydanego dowodu, ponieważ dowód rozwiązuje się wobec tego trwałego magazynu węzłów R2 i kotwicy Arweave, a nie DO (a wektor testowy wymazanego DO z §16.9 to dowodzi).
16.14 Wektory testowe
W5 dostarcza wielojęzyczny fixture tlog_v1.json (§16.8) plus wypracowane wektory: liść kind=0x01 i liść kind=0x02 → leaf_hash; 5-liściowy kumulatywny korzeń; jeden dowód inkluzji; jeden dowód spójności; jeden AnchorBundle; i jeden id DataItem. Żyją one obok wektorów zewnętrznego opakowania z §14.5 i są przećwiczone zarówno przez implementację Rust (qub-core), jak i TypeScript (Worker).
16.15 Przegląd decyzji (W5 — rozwiązane)
Zewnętrzny przegląd W5 (przegląd projektowy w trybie kontradyktoryjnym + zatwierdzenie właściciela) został zakończony. Każda decyzja poniżej została ustalona i odzwierciedlona w tekście §16 powyżej; wiązanie ograniczeń uruchamiania są powtórzone na końcu. Ich realizacja może być kontynuowana zgodnie z nimi.
- Domyślna ścieżka (
kind=0x02) szczerość liścia — ROZSTRZYGNIĘTE. Wyślij podział dwu-listkowy zgodnie ze specyfikacją:kind=0x02nie popełnia żadnegobody_hashanidrand_round. Nie*_body_hashpole na ścieżce ślepej na bajt (byłoby to najbardziej czytelne fałszywe „zweryfikowane” sygnał dla integratorów i jest udogodnieniem, które §11 już zapewnia z pakietu). Robi nie wymagać server-seal dla log-attested qubs (co zmusiłoby przesyłanie tekstu jawnego przez Workera i zniszczyłoby fosę zniszczenia kryptograficznego). Każdy samoopisujący się skrót należy umieścić w.qubpakiet / koperta dowodowa jako pole weryfikowane przez odtworzenie, nigdy jako pole końcowe. Maksymalny limit roszczeń potwierdzony przez właściciela: §16.11. - Odpowiedzialność za dwuznaczność / pominięcie — PROJEKT ROZWIĄZANY, DOSTARCZENIE NIEKOMPLETNE. Projekt wymaga, aby klucz do plombowania został przypięty
LogProfilei współpodpisany przezanchor_owner, plus metodologia monitorowania, wędrówka prev-chain i podwójne samodzielnie wydane głowy. Skompilowany profil i haki wdrożeniowe nadal są miejscami zastępczymi/opcjonalnymi, jak szczegółowo opisano w §16.6, więc silniejsze wykrywalne + potwierdzone odbiorem roszczenie nie jest aktualne, dopóki te bramki się nie zamkną. Nigdy nie wolno go reklamować jako niezależnie świadczone. Prawdziwy świadek zewnętrzny jest odroczony do bumpu zarządzania §15. - Przypięty zaufany root właściciela kotwicy + rotacja — ROZWIĄZANE. Adoptuj
LogProfilepin (§16.6); weryfikator sprawdzaanchor_tx.owner == anchor_owneri weryfikuje lokalnie powiązanie danych tx → tx_id. Zarządzanie rotacją jest §15 rozszerzenie do budowy (§15.3 wyzwalacz dodany), nie jest to ponowne użycie; zaplanowane rotacje mają wspólne oznaczenie, rotacje oparte na kompromisie wracają do §15 podbicia z kontrolą widelca ograniczającą szkody. - Oślepienie liści Private-qub — ROZWIĄZANE. Kontynuuj oślepianie dla prywatnych qubs (
ref = SHA3-256(qub_id ‖ log_blind_secret)), surowyqub_iddla publicznych qubs (już §16.2.1),chashjako samodzielny krawat.log_blind_secretjest korelacją/sekretem stopnia Sybila, tylko obrót do przodu (§16.2.1). received_at— POSTANOWIONE. Trzymaj to w liściu, zaangażowane, ale wyraźnie nie dowodowe; nigdy nie ujawnione jako dowód ani jako potwierdzenie sporu na żadnej powierzchni. Każda kontrola stanu monitora porównuje to z czasem bloku Arweave.T, nie sterowany przez operatoraanchored_at(§16.6).- Hierarchiczne udowadniane czasy — ROZWIĄZANIE PROJEKTOWE, NIE BIEŻĄCE TRASY. Przeglądany projekt przypisuje czasowanie bloków kotwiczących do warstwy zbiorczej oraz dowód dokładnej godziny do płatnej T3, bez określonego numerycznego SLA dla pierwszej z nich. Obecne trasy nie uwzględniają tego podziału komercyjnego: harmonogramują pojedynczą transakcję dla każdej zaakceptowanej publikacji, a rejestrowanie pokrycia pozostaje warunkowe, jak to określono w §16.1/§16.10. Tekst produktu musi opisywać implementację, a nie ten przyszły podział warstw.
- Skumulowane drzewo dla Pracowników — ROZWIĄZANE. Pojedyncze kumulatywne drzewo RFC 9162 + LogDO z buforem frontowym dla pojedynczego pisarza (komfortowy zapas w porównaniu z ~1k zapisów/sek DO; odłożyć sharding Merkle-of-shard-roots aż do osiągnięcia tej wartości). Kluczowane współrzędnymi
(level, index)Węzeł R2 store + testowy wektor cold-leaf wiped-DO jest zaimplementowany (§16.9).< 300 mspozostaje celem projektowym/operacyjnym, a nie obietnicą protokołu (§16.10). - Schemat podpisu ANS-104 + deep-hash — ROZWIĄZANO. RSA-PSS (typ sygnatury 1, z ponownym użyciem dedykowanego JWK portfela-ankera); Ed25519 odłożono do ścieżki PQ §15. Ręcznie wykonany głęboki skrót SHA-384 jest uzależniony od uchwytu cross-impl w obu kierunkach, statycznego tylko sprawdzenia interoperacyjności reference-bundler, współdzielonego-
crypto.subtlepodróż w obie strony oraz monitor akceptacji Arweave po pakiecie (§16.8).
Wiążące ograniczenia uruchamiania (przenieść do realizacji + przegląd produktu/prawny):
- Sufit roszczenia (Q1/Q6). Żadna powierzchnia nie może mówić pień dowodzi zawartości twierdzonego liścia ani odblokować okręgu; dozwolone roszczenie w przypadku prawidłowo zakotwiczonego
kind=0x02liść jest uporządkowany, w sposób wykazujący manipulacje, z niezaufanym czasowym zobowiązaniem górnej granicy. Odpowiedź bez krotki pokwitowania nie ma roszczenia dziennika. Żadna kopia znaczników czasowych nie niesie gwarancji opóźnienia numerycznego. - Świadek uczciwości (Q2). Rynkowa dwuznaczność jako wykrywalna + potwierdzona kwitem, nigdy niezależnie świadczona.
- Paragon + klucze kotwiczne (Q2/Q8). Przed wysłaniem roszczeń dotyczących niezaprzeczalności/weryfikacji zakotwiczonej, przygotuj i skompiluj klucz publiczny pokwitowania, podpisz go krzyżowo z przygotowanym właścicielem kotwicy i przechowuj portfel kotwicy jako własny JWK, odrębny od portfela przesyłania.
- Bramka głębokiego haszowania (Q8). Brak kotwic ani statków T3 tx, dopóki nie zostanie zaliczony test dwukierunkowej ramy + weryfikacja interoperacyjności; monitor akceptacji generuje stronę w przypadku awarii.
- Warunek wstępny przechowywania (Q7). Sklep węzłów z kluczami współrzędnych + wektor zimnych liści wymazanych-DO są wstępnymi warunkami dla gwarancji „odzyskiwanie nigdy nie unieważnia dowodu”.
17. Przenośny pakiet weryfikacyjny (.qub)
Status. Ta sekcja jest zaimplementowany (W7 / UP-C2):
qub_core::exportprodukuje i analizuje pakiet, oraztools/qub-verifyjest publicznym, samodzielnym CLI, które weryfikuje pojedynczo offline. §11 i §16.9 już odnoszą się do "the".qubpakiet jako jednostka konsumowana przez samodzielnego weryfikatora; ta sekcja określa jego bajty i przebieg weryfikacji. Jest to ściśle additive — pakiet zawiera istniejące wejścia §11 i nie zmienia formatu transmisji w łańcuchu.
17.1 Cel
§11 ustanawia, że każda strona trzecia może zweryfikować kryptograficzny artefakt quba bez współpracy quba. The .qub pakiet dokonuje tej weryfikacji przenośny i offline: pakuje zapieczętowany CBOR oraz podpis rundy drand, który go odblokowuje, w jeden samodzielny artefakt, dzięki czemu odbiorca może zweryfikować integralność treści, powiązanie rundy oraz wszelkie podpisy autorstwa z brak jakiegokolwiek wywołania sieci (bez pobierania ze storage, bez żądania drand na żywo, bez API qub). Sam pakiet nie udowadnia, kiedy jego szyfrogram został utworzony; niezależnie zweryfikowana transakcja w storage lub dowód zakotwiczenia w logu dostarcza odrębne twierdzenie o czasie istnienia (§11, §17.5).
17.2 Format pakietu
A QubBundle jest ręcznie pisany kanoniczny CBOR zgodnie z profilem §3.1 (o ustalonej długości, bez tagów, bez liczb zmiennoprzecinkowych, liczby w najkrótszej formie, tekst w formacie NFC, pola opcjonalne pomijane w przypadku ich braku, klucze uporządkowane według długości zakodowanych bajtów rosnąco, a następnie bajt po bajcie). Trzy 15-znakowe klucze w kolejności d < i < s. Surowy .qub plik to dokładnie te bajty; dla transportu przez URL lub kopiuj-wklej te same bajty są w base64url (bez wypełnienia).
| Klucz | Enc. len | Typ | Obecność | Znaczenie |
|---|---|---|---|---|
version |
osiem | u8 |
wymagany | Wersja formatu pakietu (0x01). |
sealed_at |
ten | i64 |
opcjonalny | Czas pieczęci potwierdzony przez twórcę (sekundy Unix); samopiszący, nie będący dowodem. |
drand_round |
dwanaście | u64 |
wymagany | Okrąg, do którego jest zablokowany qub. Wypustka osadzonego, zapieczętowanego quba. |
arweave_tx_id |
czternaście | tstr |
wymagany | Identyfikator transakcji, pod którym przechowywano zapieczętowane bajty (wskaźnik pochodzenia). |
drand_chain_id |
piętnaście | tstr |
wymagany | Łańcuch drand (szesnastkowy). Projekcja osadzonego zapieczętowanego quba. |
drand_signature |
szesnaście | bstr |
wymagany | Sygnatura sygnału drand dla drand_round — wartość, która odblokowuje szyfrogram. |
inclusion_proof |
szesnaście | bstr |
opcjonalny | Dowód włączenia Merkle w logu przejrzystości §16, po udostępnieniu zakotwiczonego dowodu (§17.5). |
sealed_qub_cbor |
szesnaście | bstr |
wymagany | Wewnętrzny SealedQubCbor bajty (po rozpakowaniu zgodnie z §13), tj. wejście weryfikacyjne zgodnie z §11. |
drand_round i drand_chain_id są projekcjami wygody sealed_qub_cbor, przenoszone tak, aby narzędzia mogły je odczytać bez analizowania wewnętrznego CBOR. Są wyprowadzane podczas konstrukcji i ponownie sprawdzono przy dekodowaniu przeciwko sparsowanemu zapieczętowanemu qub; pakiet, którego pole najwyższego poziomu nie zgadza się z jego zawartością, jest odrzucany. Dyscyplina enkodera odzwierciedla resztę formatu transmisji: odrzucaj pusty drand_signature lub arweave_tx_id, i powiązał każde pole o zmiennej długości.
17.3 Co udowadnia osadzony podpis drand
Pakiet zawiera podpis drand zamiast wymagać od weryfikatora jego pobrania. Odszyfrowanie z blokadą czasową (tlock nad łańcuchem drand, §8) może powieść się tylko przy autentyczny sygnatura beacon dla określonej rundy — wartość, którą łańcuch publikuje tylko raz po zakończeniu tej rundy i która jest prawidłową sygnaturą BLS pod publicznym kluczem łańcucha. Podrobiona lub błędna sygnatura nie przechodzi weryfikacji BLS lub deszyfrowania IBE/AEAD. Pakiet, który można odszyfrować, dowodzi więc: szyfrogram jest związany z rundą R, a runda R minęła. Weryfikator przypina łańcuch (DrandTimelockProvider::quicknet()) i stosuje kontrolę powiązania rundy zgodnie z §11, więc pakiet nie może twierdzić, że runda nie jest powiązana z jego szyfrogramem.
To jest dowód warunku wydania, a nie znacznik czasu utworzenia. Po rundzie R
upłynęło, każdy może stworzyć nowy szyfr dla R i spakować jego już publiczne
podpis. Dlatego sam pakiet NIE MOŻE być opisywany jako dowód, że
szyfrogram lub zawartość istniała przed R, przed unlock_at, lub przed jakimkolwiek wydarzeniem.
17.4 Przegląd weryfikacji offline
qub-verify <file.qub> przeprowadza standardową procedurę §11 całkowicie z pakietu, napędzając qub_core::unlock::unlock z przypiętym 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 wychodzi 0 (zweryfikowany), 1 (weryfikacja nie powiodła się — wciąż zablokowane, niezgodność skrótu ciała, uszkodzone powiązanie rundy/łańcucha lub podpis, który nie przechodzi weryfikacji), lub 2 (użycie / niepoprawny pakiet). A --json raport przynosi te same werdykty dla automatyzacji. Ponieważ pakiet jest samowystarczalny, skrzynka weryfikatora (qub-core) i CLI (qub-verify) są jedynym oprogramowaniem, którego potrzebuje strona trzecia; oba są publiczne i wykorzystują istniejącą ścieżkę weryfikacji protokołu — bez własnej kryptografii.
17.5 Związek z dziennikiem przejrzystości
inclusion_proof jest opcjonalnym polem dla dowodu włączenia Merkle §16. Weryfikacja tylko pakietu (§17.4) jest kompletna dla integralność, wiązanie okrągłe / upłynęło okrąg, oraz opcjonalne autorstwo, ale celowo nie ma samodzielnie oznaczonego czasem roszczenia dotyczącego istnienia. Zapełniony, w pełni zweryfikowany pod względem kotwiczenia inclusion_proof dodaje zobowiązanie specyficzne dla rodzaju liścia oraz górną granicę czasu z §16.11 bez zmiany wersji formatu pakietu. Brak dowodu oznacza tylko „brak załączonego dowodu” — nie „nieprawidłowy” i niekoniecznie „niezakotwiczony”.
W implementacji referencyjnej slot jest teraz wpisany: qub_core::export::QubBundle::inclusion_proof_typed() zwraca Option<InclusionProof> posiadający pełną strukturę §16.9 (liść, ścieżka audytu, zakotwiczony korzeń, oraz AnchorRef) przez to samo nieprzezroczyste pole CBOR — bez podnoszenia wersji formatu pakietu. Samodzielny qub-verify CLI konsumuje to za pomocą jego --anchor noga, i — dopóki portfel kotwicy nie zostanie zaopatrzony (§16, Status) — zgłasza wypełnione, ale z zastępczym właścicielem dowód jako tylko włączenie, a nie w pełni zweryfikowany kotwicą.