qub 協議規範
qub 是一個用於加密時間承諾的協定:一個將文字封印到未來某個日期、並在該日期到來時精確證明所說內容與時間的系統。
它由三種原語共同支撐。drand 是一個去中心化隨機性信標——公開日期由物理規律強制執行,而非任何一方的善意。永久公開儲存 是一個防篡改的公共儲存——qub 一旦被封印,任何一方都無法編輯或刪除。ML-DSA-65 是一種後量子數位簽章——每個 qub 都繫結到一組金鑰對,其私鑰永遠不會離開作者的裝置。
這些原語共同構成了一種時間鎖定、防竄改且可歸屬的聲明——一份其價值隨著世界偽造過去的能力提升而增長的憑證。
本文件其餘部分是可互通實作所需的規範性技術規格。
qub 協定規範
| 欄位 | 值 |
|---|---|
| 版本 | 1.0(協定版本 0x01,外層封裝版本 0x01) |
| 日期 | 2026-05-01 |
| 狀態 | 草案 |
| 審閱至 | 2026-05-01 |
本文件是 qub 定時承諾系統的規範性協定規範。它定義了可互通實作所需的資料結構、序列化規則、推導公式與驗證流程。
範圍:協定層有意保持語言中立——qub 主體是不透明的明文 / markdown / 約定位元組,區域感知的渲染由查看者負責(qub.social 網頁應用、<qub-embed> iframe、MCP 客戶端等)。
1. 記法與約定
| 記法 | 含義 |
|---|---|
u8、u64、i64 |
指定位寬的無號 / 有號整數 |
[u8; N] |
長度為 N 位元組的定長位元組陣列 |
Vec<u8> |
變長位元組陣列 |
Option<T> |
T 型別的值,或不存在 |
String |
UTF-8 文字字串,已 NFC 正規化 |
| ` | |
SHA3-256(x) |
位元組串 x 的 NIST SHA3-256 雜湊(FIPS 202) |
ceil(x) |
向上取整函式:不小於 x 的最小整數 |
| CBOR | 簡明二進位物件表示(RFC 8949) |
| 大端 | 最高有效位元組在前 |
前映像構造中的所有整數均編碼為大端定寬位元組陣列(i64 → 8 位元組,u8 → 1 位元組),除非另有說明。
所有時間戳均為 UTC Unix 秒。
2. 資料結構
2.1 ComposeQub(創作者記憶體狀態)
不序列化為 CBOR。不寫入永久儲存。僅在創作者應用本地存在。
ComposeQub {
draft_id: [u8; 16], // Random, generated locally
created_at: i64, // Unix seconds UTC
unlock_at: Option<i64>, // Unix seconds UTC; None while composing
visibility: u8, // 0x01 = public (only value in MVP)
content_type: u8, // 0x01 = text (only value in MVP)
plaintext: Vec<u8>, // UTF-8 qub body
sender_label: Option<String>, // Decorative display name; not authenticated
status: DraftStatus, // Composing | Sealed | Uploaded | Failed
}
2.2 QubEnvelope(解密後的負載)
使用規範 CBOR 序列化(§3)。被加密後置於 SealedQub 內部。這是在解密後用於證明內容完整性的結構。
QubEnvelope {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Derived (see §4.1)
content_type: u8, // Content type registry (see §6)
created_at: i64, // Unix seconds UTC
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
sender_label: Option<String>, // Decorative; not authenticated in MVP
reply_to: Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
body: Vec<u8>, // Content payload (UTF-8 for text, CBOR for pact)
body_hash: [u8; 32], // SHA3-256(body) (see §4.2)
sig_alg: u8, // Signature algorithm (see §9.2)
author_signature: Option<Vec<u8>>, // Set when sig_alg != 0x00
author_pubkey: Option<Vec<u8>>, // Set when sig_alg != 0x00
cosigner_pubkey: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
cosigner_signature: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}
基線(未簽署的文字 qub): version = 0x01,content_type = 0x01,sig_alg = 0x00,所有 Option 欄位均不存在。
其他 v1 配置: content_type = 0x03(約定主體,見 §6.1);sig_alg = 0x01(ML-DSA-65),同時存在 author_signature 與 author_pubkey(見 §9.3);針對共同簽名的約定,cosigner_pubkey 與 cosigner_signature 同時出現(見 §9.7);針對回覆鏈 qub,將 reply_to 設為父 qub 的 qub_id(關於簽章作用域的影響見 §9.3)。
2.3 SealedQub(規範線路格式)
使用規範 CBOR 序列化(§3)。寫入永久儲存。這是鏈上工件。
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x01 = public; v1 viewers reject other values
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — surfaced on the verdict-watch CTA
// before reveal; mirrors QubEnvelope.outcome_at;
// bound to qub_id via the §4.1 preimage.
drand_chain_id: String, // drand chain hash (hex string)
drand_round: u64, // Target drand round number
tlock_ciphertext: Vec<u8>, // tlock-encrypted QubEnvelope CBOR bytes
recipient_pubkey: Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
// but not interpreted by the v1 reference viewer
title: Option<String>, // Plaintext title surfaced on the viewer
// countdown before reveal. Bound to qub_id
// via title_hash (§4.1). 1..=100 NFC code
// points, no control characters.
}
2.4 RevealedQub(查看者應用狀態)
不序列化為 CBOR。僅在查看者應用本地存在。在成功解密並驗證後構造。
RevealedQub {
qub_id: [u8; 32],
arweave_tx_id: String,
visibility: u8,
content_type: u8,
created_at: i64,
unlock_at: i64,
outcome_at: Option<i64>, // V1.1 — 由 QubEnvelope.outcome_at / SealedQub.outcome_at 承接而來;驅動公開頁的判定等待區塊(verdict-uplift-plan §5.1)
drand_chain_id: String,
drand_round: u64,
sender_label: Option<String>,
title: Option<String>, // Carried forward from SealedQub.title
reply_to: Option<[u8; 32]>,
body: Vec<u8>,
body_hash: [u8; 32],
body_hash_verified: bool,
author_signature: Option<Vec<u8>>,
author_pubkey: Option<Vec<u8>>,
signature_verified: Option<bool>,
cosigner_pubkey: Option<Vec<u8>>,
cosigner_signature: Option<Vec<u8>>,
cosigner_verified: Option<bool>,
}
3. 規範 CBOR 配置
所有 SealedQub 與 QubEnvelope 序列化必須符合本配置。對於相同的邏輯結構,兩個實作必須產出相同的位元組。
3.1 編碼規則
| 規則 | 規範 |
|---|---|
| 標準 | RFC 8949 §4.2.1(核心確定性編碼要求) |
| 對映鍵排序 | 先按編碼後的位元組長度排序(短在前長在後),再按字典序排序(長度相同時按位元組逐位元組比較) |
| 整數編碼 | 最短形式:0–23 嵌入初始位元組;24–255 用 2 位元組;256–65535 用 3 位元組;以此類推。 |
| 長度編碼 | 僅使用定長。 不允許不定長陣列、對映、位元組串或文字串(禁止附加資訊 = 31)。 |
| 標籤 | 不允許 CBOR 標籤(禁止主類型 6)。 |
| 浮點數 | 不允許浮點(禁止主類型 7 中值為 0xF9–0xFB 的項)。 |
| 文字串 | UTF-8 編碼,已 NFC 正規化(Unicode Normalization Form C)。 |
| 位元組串 | 原始位元組。CBOR 層不進行 base64 編碼。 |
| 重複鍵 | 報錯拒絕。 解析器禁止靜默接受重複的對映鍵。 |
| 未知鍵 | 報錯拒絕。 解析器禁止容忍該類型規範鍵集之外的對映鍵——兩個相異的規範位元組串決不可解碼為同一個值(encode(decode(x)) == x),且對已簽署的負載而言,多出的鍵會成為兩份簽章共同承諾到的隱藏內容。Schema 演進一律透過 version 進行,決不透過額外的鍵。 |
| 簡單值 | 僅允許 true(0xF5)、false(0xF4)與 null(0xF6)。 |
| 可選欄位 | 不存在的可選欄位從 CBOR 對映中完全省略(而非編碼為 null)。存在的可選欄位按排序後的鍵序加入。 |
3.2 已驗證的規範鍵序
以下鍵序是規範性的。實作必須嚴格按照此順序輸出鍵。非發布建置中除錯斷言應驗證該順序。
QubEnvelope(版本 0x01,未簽署,所有可選欄位不存在):
"body" (5 encoded bytes)
"qub_id" (7 encoded bytes)
"sig_alg" (8 encoded bytes)
"version" (8 encoded bytes)
"reply_to" (9 encoded bytes) ← only if present (reply chains)
"body_hash" (10 encoded bytes)
"unlock_at" (10 encoded bytes)
"created_at" (11 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (V1.1 verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
QubEnvelope 鍵序推導: 每個鍵都是 CBOR 文字串。編碼長度 = 1 位元組頭 + 字串長度(對於長度小於 24 位元組的字串)。先按總編碼長度排序,長度相同時按字典序排序。
SealedQub(版本 0x01,公開,無接收者):
"title" (6 encoded bytes) ← only if present
"qub_id" (7 encoded bytes)
"version" (8 encoded bytes)
"unlock_at" (10 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (V1.1 verdict mechanic)
"visibility" (11 encoded bytes)
"drand_round" (12 encoded bytes)
"drand_chain_id" (15 encoded bytes)
"recipient_pubkey" (17 encoded bytes) ← only if present
"tlock_ciphertext" (17 encoded bytes)
PactTerms(約定主體,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(terms 陣列的一列):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier(party_a / party_b 對映):
"label" (6 encoded bytes)
"contact" (8 encoded bytes) ← only if present
3.3 位元組編碼參考
| 類型 | CBOR 編碼 | 範例 |
|---|---|---|
| SHA3-256 雜湊(32 位元組) | 0x58 0x20 + 32 位元組 |
body_hash、qub_id |
| 時間戳(i64) | 主類型 0(正)或 1(負),最短編碼 | Unix 秒 |
| 版本(u8,值 1) | 0x01(單位元組) |
|
| 內容類型(u8,值 1) | 0x01(單位元組) |
|
| sig_alg(u8,值 0) | 0x00(單位元組) |
|
| ML-DSA-65 簽章(3,309 位元組) | 0x59 0x0C 0xED + 3,309 位元組 |
author_signature、cosigner_signature |
| ML-DSA-65 公鑰(1,952 位元組) | 0x59 0x07 0xA0 + 1,952 位元組 |
author_pubkey、cosigner_pubkey |
4. 規範性推導
4.1 qub_id
qub_id 唯一標識一個 qub,並將 QubEnvelope 與 SealedQub 繫結。它由信封內容確定性地推導而來。
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
域分隔符編碼: 字串 "QUB_ID_V2" 為 9 個 ASCII 位元組。附加單個 0x00 填充位元組以達到 10 位元組對齊。實作必須嚴格使用以下 10 位元組:[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]。
outcome_at 編碼: V1.1 將原像從 92 位元組擴展為 100 位元組,以將可選欄位 outcome_at 折入此繫結。outcome_at 不存在時編碼為 8 個零位元組;協議驗證器在任何地方都拒絕 outcome_at <= 0,因此此標記不會與合法值衝突。見 §3.2(線路格式)與庫內 tasks/verdict-uplift-plan.md 中觸發此欄位的判決機制。
drand_round 編碼: V1.2 將原像從 100 位元組擴展為 108 位元組,以將 drand_round(目標 drand 輪,§4.3)折入此繫結,並將域分隔符提升為 QUB_ID_V2。這將時間鎖的輪繫結進 qub 身分:閘道無法將密文重新繫結到與所顯示 unlock_at 所隱含者不同(例如已過期)的輪。公開流程(§8)另外驗證烘焙進 tlock 密文 stanza 的輪與 unlock_round(unlock_at) 相符,因此所顯示的公開時間可被證明就是門控解密的那一輪。
特性:
- 改變 QubEnvelope 中任一欄位(主體、時間戳、內容類型、版本)都會得到不同的 qub_id。
- qub_id 在加密之前計算。QubEnvelope 與 SealedQub 攜帶相同的 qub_id。查看者在解密後驗證二者一致。
- qub_id 不依賴於
sender_label、author_signature或author_pubkey。這意味著在相同時刻封印的相同內容,無論由誰簽署,都會得到相同的 qub_id。 - 改變 SealedQub 的
title(其他欄位不變)會經由title_hash改變qub_id。因此閘道無法在不讓 qub 身分失效的情況下替換倒數計時上顯示的明文標題。 - 改變 SealedQub 的
outcome_at(其他欄位不變)會經由原像改變qub_id。閘道無法在不讓 qub 身分失效的情況下替換倒數計時上顯示的公開前判決日期。 - 改變
drand_round(其他欄位不變)會經由原像改變qub_id。閘道無法在不讓 qub 身分失效的情況下將時間鎖密文重新繫結到不同的輪;結合 §8 公開時的 stanza 輪檢查,所顯示的unlock_at就是實際門控解密的那一輪。
4.2 body_hash
body_hash = SHA3-256(body)
其中 body 是原始 Vec<u8> 內容負載。對於文字 qub,這是 UTF-8 編碼的 qub 主體。
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
其中 title 是在公開前查看者倒數計時上展示的可選明文標題(見 §3.2)。NFC 正規化在雜湊時執行,使摘要在視覺上等價的碼點序列之間保持穩定。全零標記保留給不存在的情形;空字串在規範 CBOR 邊界上作為「不存在」的非規範編碼而被拒絕(規範編碼會完全省略該欄位)。
4.3 公開輪映射
drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| 參數 | 來源 | 範例 |
|---|---|---|
unlock_at |
使用者選擇的 UTC Unix 秒 | 1735689600(2025-01-01 00:00:00 UTC) |
chain_genesis_time |
drand 鏈資訊(genesis_time) |
1595431050 |
chain_period_seconds |
drand 鏈資訊(period) |
30 |
ceil() 操作選擇公開時間 ≥ unlock_at 的第一個 drand 輪。這確保 qub 不會在所選公開時間之前變得可解密。
邊界情形: 若 (unlock_at - chain_genesis_time) 可被 chain_period_seconds 整除,則結果即為該精確輪——qub 恰好在該輪的公開時間解鎖。
校驗: 封印時 unlock_at 必須位於未來。unlock_at 距 created_at 不得超過 10 年(以限制長期 drand 相依風險;UI 應對超過 2 年的公開日期發出警告)。
5. 線路格式 Newtype
線路格式 newtype 在編譯期防止將 CBOR 位元組與 JSON、原始明文或其他位元組編碼混淆。
| 類型 | 內容 | 由誰產生 | 由誰消費 |
|---|---|---|---|
SealedQubCbor |
SealedQub 的規範 CBOR | serialize_sealed_qub() |
永久儲存上傳、查看者擷取 |
QubEnvelopeCbor |
QubEnvelope 的規範 CBOR | serialize_qub_envelope() |
tlock 加密輸入、tlock 解密輸出 |
5.1 構造規則
// 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 構造時的校驗
from_encoded() 應校驗輸入以有效的 CBOR 對映頭開始。完整的結構校驗在解析時進行,而非構造時,以避免重複解析。
6. 內容類型註冊表
| 值 | 類型 | 最大主體大小 | 備註 |
|---|---|---|---|
0x00 |
保留(無效) | — | 禁止使用 |
0x01 |
純文字(UTF-8,受限 Markdown) | 50 KB 付費 / 10 KB 免費 | 渲染規則見 §10。免費 / 付費切分由上傳服務強制執行;協定層硬性上限為 50 KB。 |
0x02 |
保留(未來) | — | 為未來的內容類型保留;在 v1 中無效。查看者必須按下文規則拒絕。 |
0x03 |
約定(雙邊協議,CBOR 主體) | 100 KB | 主體為規範 CBOR 的 PactTerms(§6.1)。共同簽名見 §9.7。 |
0x04 |
判定(創作者自我評分,CBOR 主體) | 8 KB | 主體為規範 CBOR 的 VerdictBody(§6.2)。僅由系統端的 verdict 意圖發行。親子關係位於 Parent-Tx-Id Arweave 標籤上,而非主體中。見 verdict-uplift-plan §3.4。 |
查看者必須以清晰的使用者可見錯誤拒絕未知內容類型。查看者禁止嘗試將未知類型作為文字渲染。
6.1 約定主體(content_type = 0x03)
約定主體是 PactTerms 值的規範 CBOR 編碼:
PactTerms {
pact_version: u8, // 0x01 for structured/v1
title: String, // ≤ 200 bytes, NFC
terms: Vec<PactTerm>, // ≤ 20 rows
party_a: PartyIdentifier, // initiator
party_b: PartyIdentifier, // counter-signer
notes: Option<String>, // ≤ 5,000 bytes, NFC; absent key if none
}
PactTerm { key: String (≤ 100), value: String (≤ 2,000) } // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }
三個對映的規範 CBOR 鍵序見 §3.2。序列化後的約定 CBOR 總大小不得超過 100 KB(與 §6 一致)。
Schema 鑑別欄位。 對 structured/v1 約定,terms 中的第一列必須為 { key: "pact_schema", value: "structured/v1" }。沒有此標記的列屬於「自訂」約定,不接受結構化校驗或 schema 感知的渲染。
凍結的確認槽位。 structured/v1 約定恰好在以下鍵下攜帶四列確認列:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
每列的 value 是按 (role, kind) 對所選定的八個凍結英文字串之一,其中 role ∈ { seller, buyer, provider, client },kind ∈ { standard, capacity }。這些字串本身是規範性協定資料——雙方的 ML-DSA-65 簽章透過 body_hash 承諾到這些精確位元組。它們不會被在地化;簽章所涵蓋的主體是語言中立的。任何措辭變更都需要新的 schema 版本(structured/v2)。
這八個字串、它們的查找方式(acknowledgement_for(role, kind))以及每條的理由由參考實作固定。符合規範的實作必須輸出位元組相同的確認值;涵蓋全部四種角色組合的黃金固件 SHA3-256 body-hash 測試可捕獲任何漂移。
查看者展示順序。 這些確認字串包含諸如 "described above" 等措辭,前提是描述 / 範圍列先於確認列渲染。查看者必須按 CBOR 順序渲染 terms 陣列;重新排序會破壞文字語義。
對方聯絡方式。 當 Party B 的 contact 為有效的電子郵件地址時,qub 上傳服務在暫存階段會自動發出審閱 / 共同簽名邀請信件,並將最終的共同簽名繫結到對同一地址的驗證(§9.7)。Party B 聯絡方式缺失的約定仍可被共同簽名,但只能經由帶外管道完成——服務會拒絕無法產出匹配的 15 分鐘信箱驗證標記的共同簽名請求。
6.2 判定主體(content_type = 0x04)
判定主體是 VerdictBody 值的規範 CBOR 編碼:
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
}
規範 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)
序列化後的判定 CBOR 總大小不得超過 8 KB(與上方註冊表列一致)。
結果列舉。 線路位元組是意圖中立的;四個分類 Right / Partial / Wrong / Unfalsifiable 涵蓋每一種帶判定意圖的結果空間。各意圖專屬的標籤(如 Right 對應「說中了」/「做到了」/「如期推出」/「經事實證實」等)是查看者端的渲染議題,會對照父 qub 的意圖解析——線路本身保持語言與意圖中立。1..=4 範圍外的值必須在解碼時拒絕。
親連結。 判定 qub 不會在其主體中攜帶對親的參照。親 qub 的 Arweave 交易 ID 在上傳時作為 Parent-Tx-Id 儲存標籤發出(§7 儲存標籤層)。如此一來,主體保持為自我評估的自包含已簽名陳述;稽核鏈(「對什麼為對?」)透過 Arweave 標籤查詢建立。
證據 URL 安全性(規範性)。 當 evidence_url 存在時,驗證者(撰寫端、線路端、Worker 邊緣)必須執行:
- 僅限 HTTPS。 字串必須以位元組序列
https://開頭。任何其他協定——http、ftp、javascript、data、file等——皆拒絕。 - 長度上限。 ≤ 2,048 位元組(瀏覽器 URL 實務上限)。
- NFC + 敵意碼點檢查。 與
title和reflection相同的規則——拒絕雙向覆寫 / 零寬 / 標籤區塊 / BOM / C0 / C1 碼點。定義與 Rustcrate::handle::contains_hostile_text_codepoint及 TSworkers/api/src/utils/unicode.ts::isHostileCodepoint一致(保持同步)。 - 無空白、無 ASCII 控制字元。 URL 任何位置出現空白 / DEL / 低於
0x20的位元組皆拒絕——封堵雙向規則未涵蓋的\n/\t注入向量。 - 非空的主機段。
https://與第一個/、?或#之間的所有內容必須非空。
伺服器端不抓取。 Worker 禁止代理、抓取或預覽該 URL。協定僅儲存字串;渲染在查看者端進行,使用 rel="nofollow noopener noreferrer" target="_blank" 並在連結文字旁可見顯示主機名稱。
反思。 創作者自行撰寫的反思文字(「有什麼改變、學到了什麼」),為選填。與 title 採用相同的 NFC + 敵意碼點驗證。空字串 / 僅含空白的輸入會在建構時收合為不存在。
Schema 版本。 v1 僅支援 verdict_version = 0x01。未來的 schema 修訂將遞增此位元組,並依 §12 隨新協定版本一同上線。
7. 封印協定
完整的封印流程。每一步都是規範性的。
1. User composes plaintext and metadata in ComposeQub.
2. Validate:
a. body is non-empty.
b. body size ≤ max for content_type and user tier (see §6).
c. unlock_at is in the future.
d. unlock_at ≤ created_at + 10 years.
e. content_type is a known, supported value.
3. Compute body_hash = SHA3-256(body).
4. Set created_at = current Unix seconds UTC.
5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
(Computed here, before qub_id, because drand_round is bound into the qub_id
preimage — §4.1, V1.2.)
6. Compute qub_id (see §4.1), folding in drand_round from step 5.
7. Construct QubEnvelope with all fields.
8. Serialise QubEnvelope using canonical CBOR → bytes B.
Assert: serialised output matches canonical profile (§3).
9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C, and matching qub_id, version,
unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
never the bare SealedQubCbor. K leaves the device only as the URL
fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
signs and uploads to permanent storage. The service is byte-blind to the inner
SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
when a short code is allocated). Browsers do not transmit URL fragments
to servers, so K is never observed by qub.social or any storage gateway.
儲存標籤層(帶外)。 qub 上傳服務在已封裝負載之外附加一組刻意精簡的儲存交易標籤。Content-Type=application/octet-stream 是規範性必需的。參考服務在創作者選擇展現時額外附加三個可選標籤:Intent(經過白名單校驗的撰寫意圖——例如 quote、reply、commitment)、Author(創作者的 §9.3 公鑰指紋,64 字元小寫十六進位),以及 Parent-Tx-Id(回覆鏈中父 qub 的儲存交易 ID,43 字元 base64url)。
Author 標籤是按 qub 選擇加入的:參考創作者應用僅在使用者在封印時顯式啟用公開歸屬時附加它。當此開關關閉——這是預設值——不寫入 Author 標籤,且 qub 在鏈上是非歸屬的:永久儲存中沒有任何東西將該次上傳與創作者的 handle、信箱或其他 qub 關聯起來。當開關開啟時,Author 指紋經由 §9.5 的認證鏈解析為創作者所選的 @handle。回覆鏈關係與 Intent 是非身分性的。外層封裝(§13)保護內部主體不被密文相關性識別——防止採集者識別並批次解密在 drand 輪發布後呈 qub 形態的上傳。
參考服務有意不附加 App-Name、App-Version 或 Type 標籤:任何此類單值篩選都會讓 GraphQL 查詢返回整個 qub 語料庫,這與封裝的「僅主體保密」作用域不一致。
符合規範的驗證者在執行 §11 的第三方驗證時禁止依賴任何儲存標籤;主體雜湊 / qub_id / 簽章只承諾到內部 CBOR,決不承諾到標籤集。
8. 公開協定
完整的公開流程。每一步都是規範性的。
1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
K = base64url_decode(fragment) from the URL fragment. If the fragment
is absent or malformed → display "this URL is missing its decryption
key" and stop; the viewer MUST NOT contact the storage gateway
without K, since fetching wrapped bytes the viewer cannot decrypt
serves no purpose and only leaks the access attempt.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
`version` byte is `0x01`, and compute SealedQubCbor =
unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
swapped nonce) → display "this URL's decryption key does not match
the stored qub" and stop. Authentication failures are
indistinguishable to the viewer per §13.5.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check (V1.2). Recompute expected_round =
ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
Reject unless SealedQub.drand_round == expected_round AND the round baked
into the tlock ciphertext stanza (read via the age/tlock header, no signature
required) == expected_round. The stanza round is the one that actually gates
decryption; without this check a malicious creator could bind the ciphertext
to an already-past round while displaying a future countdown, so anyone
reading the stored bytes could decrypt before unlock_at. Implementations with
no chain identity (test mocks) skip this check.
7. Once current time ≥ SealedQub.unlock_at:
a. Fetch drand round signature for SealedQub.drand_round from drand network.
b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
8. Parse B → QubEnvelope.
9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
Fail → integrity error.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact). Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.
9. 作者簽章
9.1 理由
qub 永久儲存於永久儲存。作者簽章必須長期保持不可偽造,這就是 v1.0 採用後量子的 ML-DSA-65 方案(FIPS 204)而非某個其安全性可能在 qub 的永久生命週期內退化的傳統方案的原因。
9.2 演算法註冊表
sig_alg |
方案 | 公鑰大小 | 簽章大小 |
|---|---|---|---|
0x00 |
無簽章(未簽署) | — | — |
0x01 |
ML-DSA-65(FIPS 204) | 1,952 位元組 | 3,309 位元組 |
查看者必須拒絕未知的 sig_alg 值。
9.3 簽章前映像構造
前映像曾存在兩個版本。所有簽章都必須使用 V2,而驗證者也必須僅接受 V2。 舊版 V1 前映像(下方保留供歷史參考)在遷移至 V2 期間曾被接受為僅供驗證的回退;該回退現已淘汰,僅能對 V1 驗證通過的簽章如今會被拒絕。
V2(目前版本——由所有新的作者簽署,以及約定暫存 / 共同簽名流程的兩個簽章共同產生):
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 遵循與 title_hash(§4.2.1)相同的缺失標記慣例:32 個零位元組並非有效的 SHA3-256 輸出,因此「不存在」永遠不會與實際存在的標籤相碰撞。所有欄位皆為定寬,因此前映像無需長度前綴即無歧義。
V1(舊版——已淘汰;不再產生,驗證時亦不再接受):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V1" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 // u8 (1 byte): MUST be 0x00 in v1.0
)
// Total preimage: 91 bytes → 32-byte hash
V1 前映像省略了 sender_label 與 reply_to。它在遷移至 V2 期間曾被接受為僅供驗證的回退;該回退此後已淘汰——驗證者必須僅接受 V2 前映像。此處保留其定義供歷史參考,並用以說明下方的域分隔符。僅能對 V1 驗證通過的簽章必須視為驗證失敗。
域分隔符: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" 各為 17 個 ASCII 位元組([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32])。不進行填充。兩者相異的分隔符對這兩種構造進行域分隔,因此在其中一個前映像上產生的簽章絕不會對另一個驗證通過。
org_id_present 位元組: 緊接在 unlock_at 之後的位元組必須為 0x00。參考實作將其作為常數 ORG_ID_PRESENT_INDIVIDUAL = 0x00 暴露於 crates/qub-core/src/signing.rs;為驗證而重建 sig_input 的查看者必須輸出相同的位元組。
簽章作用域——涵蓋與未涵蓋的內容。 V2 的 sig_input 直接承諾到 version、qub_id、body_hash、unlock_at、sender_label 與 reply_to(外加固定的域分隔符與 org_id_present 位元組)。qub_id 本身經由 §4.1 的前映像從 version、content_type、created_at、unlock_at、outcome_at、drand_round 與 body_hash 推導而來,因此對上述任一欄位的任何改動都會產生不同的 qub_id,並以遞移方式使簽章失效。因此被認證的範圍為:
| 欄位 | 被簽章認證 | 方式 |
|---|---|---|
version |
✓ | sig_input 的直接輸入 |
qub_id |
✓ | 直接輸入 |
body_hash |
✓ | 直接輸入 |
unlock_at |
✓ | 直接輸入 |
sender_label |
✓ | 經由 sender_label_hash 的直接輸入(V2 前映像——唯一被接受的形式) |
reply_to |
✓ | 經由 reply_to_or_zero 的直接輸入(V2 前映像——唯一被接受的形式) |
content_type |
✓ | 經 qub_id 前映像遞移 |
created_at |
✓ | 經 qub_id 前映像遞移 |
outcome_at |
✓ | 經 qub_id 前映像遞移 |
drand_round |
✓ | 經 qub_id 前映像遞移(V1.2) |
body |
✓ | 經 body_hash = SHA3-256(body) 遞移 |
author_pubkey |
—(隱式) | 按定義,驗證簽章通過的金鑰即為作者 |
cosigner_pubkey / cosigner_signature |
— | 在相同 sig_input 上獨立簽署(見 §9.7) |
drand_chain_id、tlock_ciphertext、visibility |
— | 外層 SealedQub 欄位,不在信封內部——由它們自身的結構性不變量(輪 / 鏈一致性)涵蓋,但不受作者簽章涵蓋。(drand_round 現已經由 qub_id 前映像遞移繫結——見上文。) |
為何 V2 是唯一被接受的前映像。
- 在已淘汰的 V1 前映像下,擁有儲存位元組寫入權限的一方可以替換
sender_label("Alice" → "Mallory")或重新掛載reply_to——並在公開輪之後重新加密——而不讓作者簽章失效,因為這兩個欄位都不在已簽署的前映像中。V2 對兩者皆涵蓋,因此對任一欄位的任何改動都會使驗證翻轉為「失敗」。由於驗證者如今僅接受 V2,此替換手法對每一個簽章皆已封閉:未繫結這兩個欄位(亦即僅能對 V1 驗證通過)的簽章會被直接拒絕,而非退回接受。 - 信封內部的
author_pubkey仍是真正的身分錨——查看者必須從author_pubkey(經由 §9.5 的認證層)推導顯示身分,而非信任sender_label。
向終端使用者展示 sender_label 或 reply_to 的實作必須把已認證的身分(公鑰指紋、認證)作為主要身分訊號呈現,而非標籤。
9.4 驗證流程
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."
簽章驗證是最昂貴的操作(尤其是 ML-DSA-65)。它應在所有更廉價的檢查(雜湊、qub_id、unlock_at)都通過之後再執行。
9.5 身分認證
身分認證——將 author_pubkey 映射到人類可識別的身分聲明(如 qub handle、信箱地址、社交帳號或 passkey 憑證)——是查看者側的漸進增強,對簽章驗證並非必需。解析認證以得出顯示身分的查看者必須按下列優先級使用:
handle > email > social > fingerprint
指紋回退是 SHA3-256(author_pubkey) 的小寫十六進位;對任何已簽署的 qub 始終可用。查看者可以將其縮寫以供顯示——參考查看者渲染 qub: 後跟首尾各四個位元組(qub:<8 hex>…<8 hex>)。
符合規範的驗證者可在不聯絡 qub API、除永久儲存與 drand 外不使用任何網路、也不進行任何伺服器端查找的情況下,完成 §9.4 中的全部檢查。認證解析是僅在簽章驗證成功之後執行的、獨立的盡力而為步驟。
9.6 體積影響
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| 簽章 | 64 位元組 | 3,309 位元組 |
| 公鑰 | 32 位元組 | 1,952 位元組 |
| 每個 qub 總計 | 96 位元組 | 5,261 位元組 |
| 儲存成本差(約 $5/MB) | ~$0.0005 | ~$0.026 |
對於 500–2,000 位元組的文字 qub,ML-DSA-65 大約讓儲存大小翻三倍。絕對成本可忽略不計。
9.7 共同簽名者驗證(約定雙邊協議)
對於雙邊協議(content_type = 0x03),第二層簽章證明雙方對相同條款表示同意。
信封欄位:
cosigner_pubkey:共同簽名者(Party B)的 ML-DSA-65 公鑰。cosigner_signature:在與作者相同的sig_input上的簽章(§9.3)。
兩個欄位必須同時存在或同時缺失。若僅其中一個存在,查看者必須回報完整性錯誤。
驗證流程:
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."
特性:
- 共同簽名者在與作者相同的
sig_input上簽署——雙方均承諾到同一qub_id、body_hash與unlock_at(且在 V2 下,還包括同一sender_label_hash與reply_to_or_zero)。 - 為使共同簽名者無需存取原始信封位元組即可重建 V2 前映像,暫存服務在暫存時強制要求約定信封的
sender_label等於pact_terms.party_a.label,且reply_to不存在。此兩項對每一個參考用戶端的約定皆成立;違反者的信封在暫存階段即被拒絕。 qub_id推導(§4.1)不包含共同簽名者欄位。向已有信封新增共同簽名者不會改變qub_id。- 一個約定可以僅由作者簽署(單邊承諾)、僅由共同簽名者簽署(少見),或兩者皆有(完整雙邊證明)。
信箱繫結門控(運維層面)。 當暫存的約定攜帶 Party B 信箱聯絡方式(§6.1)時,qub 上傳服務必須拒絕共同簽名請求,除非存在與暫存 id 及該聯絡方式正規化後的信箱雜湊同時匹配的短期信箱驗證標記。該標記在 /api/v1/auth/verify 處於魔法連結權杖攜帶 staging_id 且已驗證地址匹配 SHA-256(normalise_email(party_b.contact)) 時寫入——其中 normalise_email(addr) 保留本地部分的大小寫,僅將網域部分小寫化(按 RFC 5321 §2.3.11),且此處的 SHA-256 為 NIST FIPS 180-4 雜湊(與 §4 推導中使用的 SHA3-256 不同)——並在簽發後 900 秒(15 分鐘)失效。這是一個運維層面的反假冒門控,不屬於鏈上 qub 證明的一部分——重放 §11 的第三方驗證者只需永久儲存與 drand,無需任何伺服器端查找。該標記僅存在於伺服器端,決不屬於已簽署主體的一部分。
體積影響(ML-DSA-65 作者 + 共同簽名者):
| 元件 | 大小 |
|---|---|
| 作者簽章 | 3,309 位元組 |
| 作者公鑰 | 1,952 位元組 |
| 共同簽名者簽章 | 3,309 位元組 |
| 共同簽名者公鑰 | 1,952 位元組 |
| 加密開銷總計 | 10,522 位元組 |
| 儲存成本差 | ~$0.05 |
10. Markdown 渲染與清洗
本節是安全關鍵章節。查看者使用受限的 Markdown 子集渲染文字 qub(content_type = 0x01)。
10.1 允許的元素
- 標題:
#至####(不允許#####或######) - 強調:粗體(
**)、斜體(*)、刪除線(~~) - 清單:有序(
1.)與無序(-、*) - 區塊引用(
>) - 程式碼:行內片段(```)與圍欄程式碼區塊(`````)
- 水平線(
---) - 換行(行尾兩個空格或空行)
- 段落
10.2 禁用的元素
| 元素 | 處理方式 |
|---|---|
原始 HTML(<div>、<script> 等) |
完全剝離。HTML 不會通過。 |
圖片() |
剝離。輸出中刪除圖片語法。 |
連結([text](url)) |
URL 渲染為可見的純文字。不自動建立連結。未經使用者顯式操作不可點擊。 |
| 危險 URL 協定 | javascript:、data:、vbscript:、file:——剝離。 |
| iframe、嵌入、物件 | 剝離。 |
| HTML 實體 | 僅在安全時解碼為可顯示字元。 |
10.3 實作
實作必須使用嚴格的白名單解析器,而非黑名單。推薦做法:
- 用
pulldown-cmark(或等價工具)解析 Markdown。 - 遍歷 AST 並丟棄任何不在白名單(§10.1)中的節點。
- 對於連結節點:將 URL 作為可見文字輸出,而非可點擊的
<a>元素。 - 將過濾後的 AST 轉換為型別化中間表示(例如僅含安全變體的
MarkdownNode列舉)。原始 HTML 在該 IR 中結構上不可表達。 - 從型別化 IR 渲染到目標檢視層(例如響應式檢視元件、DOM 節點)。任何階段都不進行 HTML 字串拼接或使用
innerHTML。
黑名單方法是脆弱的,因為新的 Markdown 擴充或解析器怪癖可能引入未被過濾的元素。型別化 AST 方法讓 XSS 在結構上不可能存在——沒有任何變體可以攜帶任意 HTML。
10.4 大小與結構限制
- 最大渲染標題深度:
####(H4)。#####及更深者渲染為粗體文字。 - 段落數量不設上限(§6 中的主體大小限制即是約束)。
- 圍欄程式碼區塊:MVP 中不進行語法高亮。渲染為等寬預格式文字。
11. 第三方驗證
任何第三方都可以在無需 qub 配合的情況下驗證一個公開的 qub。驗證流程為:
1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.
驗證能證明什麼:
| 證明 | 所確立的事實 |
|---|---|
| 承諾 | 該密文在儲存區塊時間戳之前已存在。 |
| 完整性 | 明文主體與已承諾雜湊一致,未被更改。 |
| 時間 | 在 drand 輪(對應所選公開時間)之前內容不可讀(取決於 tlock 與 drand 的安全假設)。 |
驗證不能證明什麼:
| 非證明 | 原因 |
|---|---|
| 作者 | sender_label 僅為裝飾性。若 sig_alg < 0x01,任何人都可能封印此內容。 |
| 意圖 | qub 證明的是內容與時間,而非創作者主觀上想表達什麼。 |
| 事件前時機 | 儲存區塊包含可能比實際上傳延遲若干分鐘。承諾時間戳是區塊時間,並非使用者按下「封印」那一刻。 |
12. 版本控制
12.1 協定版本
SealedQub 與 QubEnvelope 中的 version 欄位(u8)標識協定主版本。
- 查看者必須以清晰錯誤拒絕未知的主版本。
- 在已知主版本內,解碼器必須拒絕未知的對映鍵(§3.1)——schema 演進透過引入新的
version進行,而非透過新增既有解碼器會跳過的鍵。(本規範較早的修訂版曾允許容忍未知的可選欄位;該條款已撤回——它使encode(decode(x))不再是單射,並在約定負載上開啟了隱藏已簽署內容的攻擊向量。) - 內容類型(
content_type)與簽章方案(sig_alg)按版本受控:新值僅可在新協定版本或顯式註冊表更新時引入。
12.2 版本歷程
| 版本 | 值 | 描述 |
|---|---|---|
| v1 | 0x01 |
公開文字 qub(content_type 0x01)、約定雙邊協議(0x03,structured/v1 schema,ML-DSA-65 作者 + 共同簽名者)、tlock、SHA3-256 |
12.3 向前相容
v1 查看者遇到帶有未知 CBOR 對映鍵(不屬於 §3.2 規範順序的鍵)的 QubEnvelope 時,必須以解碼錯誤拒絕它(§3.1)。向前相容依賴 version 欄位,而非鍵的容忍:未來的補充——即便是小幅元資料——都在新的 version 值下發布,v1 查看者會以清晰的「較新協定」錯誤拒絕它,而不是靜默丟棄簽章所承諾的內容。
v1 查看者遇到 sig_alg = 0x01(ML-DSA-65)但缺少 ML-DSA-65 驗證支援時,應以「存在但不可驗證的簽章」提示展示 qub 內容,而不是完全拒絕該 qub。參考實作目前會拒絕除 0x00 與 0x01 之外的任何 sig_alg 值,因為 v1 註冊表中沒有其他有效演算法——在第三個演算法被註冊之前,嚴格拒絕與軟失敗在外觀上是相同的。一旦 §9.2 接納了新條目,上述軟失敗行為就具有實質意義,參考查看者也會在那時更新為軟失敗。
12.4 外層封裝版本
§13 描述的 OuterWrapper 攜帶自己的 version 位元組,與 SealedQub.version 及 QubEnvelope.version 獨立。兩個版本空間各自演進:未來的後量子安全對稱替代方案會升級封裝位元組而不觸及內部協定版本,而未來的協定層新增(例如新的信封欄位)會升級內部版本而不觸及封裝位元組。
OUTER_WRAPPER_VERSION_* |
值 | 演算法 | 狀態 |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM,12 位元組隨機數,16 位元組認證標籤,AAD 繫結到 qub_id |
v1 預設 |
| — | 0x02–0xFF |
保留 | 未來 |
查看者必須以清晰錯誤拒絕未知封裝版本。在出現具體遷移驅動因素(例如 NIST 指引傾向不同 AEAD)之前,協定有意保持封裝版本空間狹窄;在引入演算法的同一修訂中將分配 0x02 槽位。
13. 外層加密封裝
13.1 理由
協定各層(QubEnvelope → tlock → SealedQub)使已封印 qub 變為時間鎖定:主體在 unlock_at 與 drand 輪簽章發布之前不可讀。然而,在解鎖後,輪簽章是公開的,且 SealedQub 的規範 CBOR 形態可被識別,因此索引過永久儲存交易的採集者可批次解密整個 qub 語料庫。
外層加密封裝透過在規範 SealedQubCbor 與上傳到永久儲存的位元組之間插入一層附加的對稱 AEAD 來關閉該管道。256 位元金鑰 K 僅存在於傳遞連結的 URL 片段以及使用者裝置上;瀏覽器不會將 URL 片段傳送至伺服器,因此 qub.social、每個儲存閘道以及它們前面的每個 CDN 在觀測意義上對 K 都是盲的。永久儲存中的每個 qub 因此都是一段不透明密文,沒有創作者選擇分享的 URL 就無法復原其明文。
淨效應:
- 預設免疫列舉。 永久儲存中的封裝位元組在位元組層面與任意密文不可區分。「GraphQL 查詢出 qub 形態的上傳,使用公開 drand 簽章批次解密」的採集策略不會在明文處終止。
- 加密粉碎式隱私姿態。 qub.social 字面意義上無法解密自己的語料庫。傳票觸及密文,而非明文。
- 兩級保密階梯。 預設 = 連結受控存取(本節)。接收者加密的私有 qub(保留的第二階段功能,尚未規範)在其之上作為第二級疊加。
13.2 分層
plaintext body ← QubEnvelope.body (§2.2)
↓ canonical CBOR (§3)
envelope CBOR
↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
↓ canonical CBOR (§3)
SealedQubCbor bytes ← inner wire artifact
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes ← uploaded to permanent storage (§7 step 15)
協定層的封印與公開(§7、§8)在封裝邊界以下保持不變;封裝在 seal() 呼叫點附加,在 unlock() 呼叫點解除。
13.3 OuterWrapper 資料結構
struct OuterWrapper {
version: u8, // 0x01, see §12.4
qub_id: [u8; 32], // copied from inner SealedQub; AEAD AAD
nonce: [u8; 12], // 96-bit AEAD nonce
ciphertext: Vec<u8>, // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}
欄位不變量。
version對於 v1.0 封裝位元組必須等於0x01。qub_id必須等於解封後所復原 SealedQub 的qub_id欄位。解封步驟並不直接強制這一點(AEAD AAD 繫結使位元組層級的竄改不可能),但解鎖層透過遞移關係進行檢查:若創作者封裝一個內部qub_id與封裝qub_id不匹配的SealedQubCbor,§8 第 11 步會失敗。nonce必須為 96 位元(12 位元組),由 CSPRNG 為每次封裝操作新生成。在相同金鑰下重用隨機數會帶來允許復原明文的 AEAD 隨機數重用攻擊;生產者必須將(key、nonce)對視為一次性。ciphertext是 AES-256-GCM 的輸出:密文位元組與 16 位元組認證標籤的拼接。ciphertext.len() == SealedQubCbor.len() + 16恰好成立。
CBOR 編碼。 按 §3 規範 CBOR,使用相同的鍵排序規則(先按編碼位元組長度升序,再按字典序)。四個鍵為:
| 鍵 | 編碼位元組 | 順序 |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
因此 OuterWrapper CBOR 的首位元組是 4 項對映的定長對映頭(0xA4)。
13.4 AAD 與 qub_id 的繫結
封裝將 qub_id 作為 AEAD 附加認證資料繫結。這是抵禦以下三類攻擊的承重結構性防禦:
| 攻擊 | 防禦 |
|---|---|
在封裝中將密文移動到不同 qub_id 欄位之下 |
AAD 不匹配 → AEAD 認證失敗 |
| 將 qub A 的 URL 片段與 qub B 的永久儲存位元組混合 | AAD 不匹配 → AEAD 認證失敗 |
在上傳後竄改封裝的 qub_id 欄位 |
AAD 不匹配 → AEAD 認證失敗 |
在封裝明文中攜帶 qub_id 並不會顯著削弱列舉免疫——qub_id 本身是 §4.1 前映像的 SHA3-256 雜湊,從摘要無法復原其前映像,且已採集到封裝位元組的列舉者從可見的 qub_id 中所學到的,並不多於其從上傳存在這一事實本身可推斷的。
13.5 封裝與解封演算法
wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
require K.len() == 32 and N.len() == 12 and Q.len() == 32
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.4
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
return P // P is the inner SealedQubCbor
失敗模式塌縮。 錯誤的 K、錯誤的隨機數、AAD 不匹配以及被竄改的密文都會產生相同的 DECRYPT_FAILED 錯誤。這是一種有意為之的 AEAD 特性:區分失敗模式會構成遠端攻擊者可透過傳送畸形封裝並對回應計時來探測的旁路。參考實作必須將所有 AEAD 失敗塌縮為單一錯誤形態。
13.6 金鑰材料與分發
封裝金鑰 K 是由 CSPRNG 為每個 qub 生成的 256 位元均勻隨機值。參考實作的來源為:
- WASM 創作者:
getrandom(在wasm_js後端下使用 WebCrypto)。 - 伺服器端封印 API 呼叫方:其本地 CSPRNG;呼叫方以
wrapper_key_b64url提供並保留K。Worker 於記憶體中將K用於包裝,但不得將其持久化。如此一來,冪等重試便可憑藉呼叫方所保留的能力,復原一則已遮蔽的回應,而無需依賴由伺服器端一次性生成的秘密。
分發:K 必須編碼為 URL 安全 base64(RFC 4648 §5,無填充)並作為片段分量附加到傳遞連結:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
符合規範的瀏覽器不會將片段傳送至任何伺服器。在使用者裝置之外持久化完整傳遞連結(包含片段)的恢復管道——例如伺服器端歷程索引、可選的郵件自動傳送——是對預設加密粉碎姿態的明確折衷,必須由使用者顯式同意。
片段遺失。 若使用者遺失 URL 片段且沒有恢復管道,qub 將不可讀。這是設計中的承重折衷,且必須在封印時向使用者揭露。MVP 透過明確的「儲存此 URL」文案以及對選擇啟用的使用者提供的已驗證信箱恢復管道,加強封印時的揭露。
13.7 本節範圍之外
- 作者簽章(§9)保持不變:簽章在內部
QubEnvelope中計算,並在解封 → tlock 解密 → CBOR 解析後復原。 - 接收者加密的私有 qub(保留的第二階段功能,尚未規範)作為第二級保密層在此封裝之上組合;兩級可同時啟用。
- 約定(§6,content_type
0x03)的封裝與文字 qub 完全相同;封裝對內部內容類型位元組盲。
13.8 公開 qub(省略封裝)
外層封裝在傳遞層是可選的。創作者可將 qub 封印為公開,此時規範 SealedQubCbor 會直接寫入永久儲存,既無 OuterWrapper 層,亦無金鑰 K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
公開 qub 是時間鎖定的,但非連結受控的:在其 drand 輪發布之前仍不可讀(tlock 層維持不變),但解鎖之後,任何持有 arweave_tx_id 的人都能將其解密——不需要 URL 片段,因為並無 K。對於必須由伺服器驅動的介面而言,這是經過權衡的有意取捨:公開通知郵件、第三方嵌入,以及更豐富的解鎖後 SEO,全都需要一個無需伺服器永遠不持有之機密即可運作的連結(§13.6)。
生產者必須考量的後果:
- 無列舉免疫。 公開 qub 在構造上即放棄了 §13.1 的列舉免疫特性。參考上傳服務會在其上(且僅在其上)標記一個
Visibility: public永久儲存標籤,使它們有意可被發現;私有 qub 不帶此標籤,並保留其位元組層面不可區分性。 - 封印時暴露明文標題。 §3.2 的
title欄位在SealedQubCbor內部是明文。在封裝之下,它在查看者提供K之前都是隱藏的;沒有封裝時,它自上傳那一刻起——即在解鎖之前——便在永久儲存上對全世界可讀。符合規範的創作者應用程式必須在封印時揭露這一點。 - 偵測是結構性的。 符合規範的查看者/嵌入透過解析來區分這兩種形態:可解析為
OuterWrapper的位元組走以K解封的路徑;可解析為裸SealedQubCbor的位元組則直接被接受。不需要任何線上旗標,且qub_id並不繫結可見性——無論封印為公開或私有,同一內容在SealedQub層都是位元組相同的。
私有(已封裝)仍為預設;公開是每則 qub 由創作者明確做出的選擇。
14. 測試向量
14.1 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 = 4695445 (= (1736294400 - 1595431050) / 30, drand mainnet params §14.2)
body = "Hello, future." (UTF-8, 14 bytes)
title = absent
Intermediate:
body_hash = SHA3-256("Hello, future.")
= 76ab8b3f843c6ed4f2d0fd75b9f457b4
ad49dd4450f9c22723ae430e3af3211d
title_hash = [0u8; 32] (title absent — §4.2.1 sentinel)
Domain separator (10 bytes):
[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
Preimage (108 bytes — V1.2):
domain_separator || // 10 bytes
0x01 || // version
0x01 || // content_type
0x0000000067748580 || // created_at as i64 big-endian (1735689600)
0x00000000677DC000 || // unlock_at as i64 big-endian (1736294400)
0x0000000000000000 || // outcome_at_or_zero (outcome_at absent)
0x000000000047A595 || // drand_round as u64 big-endian (4695445)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 3a9fcb31b750d985c262fada6d4f777f
d6a28be831d941d85c131f5a4bbaf8a4
對此輸入,實作必須產出相同的 body_hash 與 qub_id 值。該測試向量應作為撰寫的第一個單元測試。上述規範值由參考實作計算,必須逐位匹配。歷史原像配置(發佈前 — 沒有任何已上線的 qub 依賴於這些值):92 位元組的 V1.0 qub_id 為 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0;100 位元組的 V1.1 qub_id(折入 outcome_at_or_zero 之後)為 b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed。V1.2 折入 drand_round 並將域分隔符提升為 QUB_ID_V2。
14.2 公開輪映射
Input:
unlock_at = 1735689600
chain_genesis_time = 1595431050
chain_period_seconds = 30
Calculation:
(1735689600 - 1595431050) / 30 = 4675285.0
ceil(4675285.0) = 4675285
drand_round = 4675285
14.3 規範 CBOR 往返
實作必須驗證對所有有效輸入都滿足 serialize(parse(serialize(qub))) == serialize(qub)。這是一項性質測試,而非單一向量。
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)
規範 CBOR 位元組與 SHA3-256 body_hash 由參考實作計算。對此輸入,實作必須產出位元組相同的 CBOR。
實作還必須驗證對所有有效 PactTerms 輸入都滿足 serialize(parse(serialize(pact))) == serialize(pact)(性質測試)。
14.5 外層封裝跨語言向量
外層封裝(§13)的規範固件位於 crates/qub-core/tests/vectors/wrapper_v1.json。每個用例將一個 (key, nonce, qub_id, sealed_cbor) 元組固定為不透明十六進位輸入,並斷言一個特定的 expected_wrapper_hex 輸出。兩套參考實作消費同一個 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)。
該固件當前固定三個用例:
| 用例 | 涵蓋範圍 |
|---|---|
basic-text-public |
最小的真實 SealedQub 形態;無任何可選欄位。確立 v1.0 典型 qub 的規範封裝形態。 |
with-recipient-pubkey |
設定了 recipient_pubkey 的 SealedQub(第 2 階段路徑)。內部 CBOR 鍵集不同、qub_id 不同。 |
longer-body |
約 4 KiB 主體——對內部信封與外部密文中的多位元組 CBOR 長度前綴進行驗證。 |
對已記錄的輸入,實作必須產出位元組相同的 expected_wrapper_hex。重新生成該固件需要 QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors,且僅用於刻意的格式變更。
15. 加密配置治理(未來)
本節對 v1 而言是參考性的,且在 qub 任一加密原語中首次進入第二個演算法時變為規範性。
15.1 當前姿態
協定 v1 對每種原語恰好繫結一個演算法:
- 簽章:ML-DSA-65(
sig_alg = 0x01;1952 位元組公鑰,3309 位元組簽章)與未簽署(sig_alg = 0x00)。§9.2 註冊表未定義其他任何值;v1 驗證者必須拒絕{0x00, 0x01}之外的每個sig_alg。未來預期會有一個 Ed25519 條目(§15.3),但在 v1 中未分配。 - 時間鎖:僅 drand quicknet——鏈雜湊、公鑰、創世時間與週期是固定的網路參數,由參考實作的
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs)與config/drand-endpoints.json攜帶。 - 外層封裝:僅 AES-256-GCM v1(§13)。
驗證者目前對每種原語硬編碼公鑰與簽章長度。線路格式不暴露任何敏捷性介面。
15.2 目標形態
當第二個演算法進入協定時,驗證者將以命名化的 CryptoProfile(例如 ExqubV1)進行配置,列出每種原語所允許的精確取值集合——sig_algs、drand 鏈、封裝版本、內容類型。該配置在驗證時固定,決不在帶內協商。任何不在活動配置內的取值都將被拒絕。
這保證新增 ML-DSA-87 或啟用 Ed25519 不能追溯性地削弱已有的驗證者配置:v1 驗證者在 v2 配置發布之後仍然是 v1 驗證者。
15.3 觸發條件
當出現以下任一情況時,將 §15 提升為規範性狀態:
- 第二個
sig_alg位元組(Ed25519 啟用、ML-DSA-87,或 §9 註冊表中的任何新條目)。 - 生產中使用的第二條 drand 鏈。
- 第二個外層封裝版本。
在此之前,§15 是一個占位章節,用於固定遷移形態,使未來的 PR 朝既定目標落地,而非從零開始重新爭論協商介面。
16. 透明性日誌與持久性層級(設計——審查完成)
狀態。 本節為設計規範。下文的線路格式、雜湊與信任模型對實作而言是規範性的,但尚無任何透明性日誌程式碼上線。W5 外部審查已完成:§16.15 記錄了已解決的決策以及由此產生的具約束力的上線限制。實作可在這些限制下進行。§16 仍與 §15 處於同一意義上的前瞻狀態——它固定目標,使實作落地於一份已底定的設計,而非在程式碼審查時重新推導信任模型。它嚴格為附加性的——每個既有 qub 都保留其各自的永久儲存交易,且
SealedQub/QubEnvelope線路格式不發生任何變更。
16.1 理由與持久性層級
如今一個 qub 的持久性及其時間承諾,兩者皆繫於單筆 per-qub 永久儲存交易(§11)。這使封印延遲與永久儲存的終局性相耦合,使 per-qub 上傳成為一項產品成本上限(ARWEAVE_DAILY_CEILING),且未能在各 qub 之間提供任何防竄改的排序。透明性日誌在該單一層級之下與周圍增加兩層:
| 層級 | 名稱 | 保證 | 時機 |
|---|---|---|---|
| T1 | R2 優先同步確認 | 持久性下限——已封印位元組在封印返回之前已寫入持久儲存(< 300 ms p95)。 |
每個 qub,同步進行(§16.10)。 |
| T2 | 批次化透明性日誌納入 | 通用的僅附加、防竄改承諾 + 全序,錨定至永久儲存。 | 每個 qub,延遲 + 批次進行(§16.5–16.7)。 |
| T3 | per-qub 永久儲存永久性 | 該 qub 的一筆各自的永久儲存交易。 | 付費加購,以及永久儲存不可用時的後援(§16.8)。 |
T2 使 per-qub 永久儲存成為一個選擇(T3),而非唯一的持久性路徑。ARWEAVE_DAILY_CEILING 退役其產品上限角色,降格為僅作用於專用錨定錢包的斷路器(§16.7);使用者封印決不會因超出它而被拒絕。
持久性誠實性(已解決——§16.15 Q6)。 持久性不退化:T1 的 R2 寫入是同步且寫入一次的,因此一個未購買 T3 的免費層 qub 在封印返回的當下即完全持久。變得較粗糙的是可證明的上界承諾時間:對一個免費 qub 而言,它成為錨定區塊時間,而非 per-qub 交易區塊時間。在低流量時——即實際上線初期與離峰狀態——完整的每日節奏是典型下限,而非罕見的邊界情況。因此產品框架是一個上界,不承諾任何數值延遲——「現已封印且持久;下一次日誌錨定(通常為每日)時會加上一個獨立的公開時間戳」——且精確到小時的承諾證明是付費 T3 屬性,在層級比較介面與條款中揭露(§16.11、§16.15 Q6)。任何時間界限僅為內部 SLO,決不作為對外行銷的 SLA。
16.2 LogLeaf 結構(兩種已底定形態)
一筆日誌條目是一個 LogLeaf,以 §3.1 配置下的手寫規範 CBOR 編碼(定長、無標籤、無浮點、最短形態整數、NFC 文字、可選欄位在缺席時省略、鍵先按編碼位元組長度升序再按位元組序排列)。§3.1 的 parse → re-encode → compare 規範守衛在雜湊之前的編碼路徑上套用(而不僅在解碼時),如此兩個實作便不會因整數寬度或鍵序差異而對葉位元組產生分歧。所有整數皆為 u8 / u64 / i64;所有摘要皆為 32 位元組的位元組字串(bstr[32])。已儲存的永久儲存交易 id 是一個原始的 32 位元組 SHA-256 摘要,以 bstr[32] 攜帶,決不是 base64url 文字字串(與 §3.3 一致)。
該葉具有由一個 kind 位元組選定的兩種形態,因為在預設上傳路徑上 Worker 對位元組是盲的:POST /api/v1/upload 僅接收 qub_id 與 unlock_at 作為不受信任的客戶端斷言——body_hash、drand_round、created_at 與 drand_chain_version 全都封印在 §13 外層封裝之內,而 Worker 從不持有其金鑰。只有伺服器封印路徑(POST /api/v1/seal)才從明文推導 body_hash / drand_round。因此,一個攜帶 body_hash + drand_round 的單一葉形態,將為絕大多數真實 qub 承諾營運者從未驗證過的值。這個拆分使每個被承諾的值都保持誠實:
| 鍵 | 編碼長度 | 類型 | 存在性 | 含義 |
|---|---|---|---|---|
seq |
4 | u64 |
必需 | 全域 0 起算葉索引;納入證明所承諾的位置。 |
kind |
5 | u8 |
必需 | 0x01 已認證(伺服器封印)或 0x02 已斷言(客戶端封印 / 位元組盲上傳)。 |
ref |
4 | bstr[32] |
必需 | 葉參照 id。已認證 → 原始 qub_id。已斷言 → 盲化後的 id SHA3-256(qub_id ‖ log_blind_secret)(§16.2.1)。 |
chash |
6 | bstr[32] |
必需 | 內容位址 SHA3-256(stored_bytes)——Worker 在兩條路徑上都能始終誠實計算的唯一內容繫結。 |
unlock_at |
10 | i64 |
必需 | 複製(已認證)或斷言(已斷言);在進入葉之前已校驗 > 0。 |
received_at |
12 | i64 |
必需 | Worker 於 R2 確認時的牆鐘時間。非證據性(營運者斷言;§16.6)。存在僅為自我描述,決不作為證明。已校驗 > 0。 |
body_hash |
10 | bstr[32] |
僅 kind=0x01 |
在 0x02 上省略——Worker 在 §13 之下缺少它。 |
drand_round |
12 | u64 |
僅 kind=0x01 |
在 0x02 上省略。 |
一個 kind=0x02 葉刻意既不承諾 body_hash 也不承諾 drand_round:它認證的是位於內容位址 chash 的一段不透明密文的承諾與排序,並聲稱 qub_id 與 unlock_at——而非其明文或輪。一個已斷言 qub 的明文/輪那兩條腿來自既有的 §11 .qub 套件驗證,而非來自日誌(§16.11)。drand_chain_version 不在葉中(在預設路徑上它位於封裝之內);鏈粒度位於錨定上(§16.7)。編碼器紀律:拒絕全零的 ref 或 chash,並拒絕非正的 unlock_at / received_at,鏡映 cbor.rs 中的 outcome_at > 0 哨兵守衛。
16.2.1 私有 qub 盲化
日誌不得成為 §13 外層封裝意在防止的那種列舉預言機(§13.1)。對一個私有(已封裝)qub,asserted 葉承諾盲化識別符 SHA3-256(qub_id ‖ log_blind_secret),其中 log_blind_secret 是一個伺服器持有的機密,且省略 body_hash。第三方無法將這樣的葉繫結到一個特定 qub_id;而持有傳遞 URL 因而擁有 qub_id 的 qub 持有者,可重新計算該盲化以確認自身的納入。一個公開 qub(本已可列舉,並依 §13.8 已攜帶 Visibility: public 永久儲存標籤)承諾原始 qub_id。這是獨立可驗證性刻意讓位於一項承重隱私不變量的唯一之處;私有 qub 的獨立繫結是 chash(§16.9)。
log_blind_secret 保管(已解決——§16.15 Q4)。 該盲化保護的是葉的不可連結性,而非明文機密性(§13 封裝獨立地持有後者)。在 log_blind_secret 遭洩時,對於對手已持有或能重建的任何 qub_id(凡其擁有套件/URL 的每個 qub,加上任何低熵或公開的 qub_id),它只需一次雜湊即可重新計算葉 ref 並將其連結——這是對一個已知母體的直接連結,而非對一個未知空間的暴力搜尋。將 log_blind_secret 歸類為與其他伺服器機密同一保管層級的關聯/女巫等級機密,且僅向前輪替(一次輪替會重新盲化未來的葉;它無法追溯地解連結已被錨定的葉)。
16.3 葉與節點雜湊
RFC 6962 §2.1 的域分隔雜湊,以 SHA3-256 取代 SHA-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
域前綴位元組 0x02(條目鏈,§16.4)與 0x03(STH 雜湊,§16.6)為保留且與上述不相交。它們是單一位元組,因此不可能與既有的 10 位元組 ASCII 域分隔符(QUB_ID_V2 等)碰撞。該樹為 RFC 6962 的左滿不平衡樹(每個內部分裂位於嚴格小於子樹葉數的最大 2 的冪處),這使納入證明與一致性證明能共用同一套稽核路徑演算法。參考規範攜帶顯式的左/右推導虛擬碼,並固定一個非 2 的冪(5 葉)測試向量,以便演練右緣晉升情形——這是一個 4 葉向量會掩蓋的情形。
16.4 雜湊鏈(內部)
LogDO 維護一條僅用於崩潰一致性的內部條目鏈。它從不發布,亦從不面向驗證者:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
已發布的僅附加權威是累積 Merkle 根 + 其錨定(§16.5–16.6),決不是營運者碰巧供應葉時的原始順序:該鏈對所供應的任何順序都會重新計算,因此只有已錨定的根才釘定規範位置。
16.5 累積 Merkle 樹與批次化
存在一棵恆久增長的、涵蓋所有葉並依 seq 序的 RFC 6962 樹——而非各自獨立的 per-batch 樹。(一種以攜帶葉串接成鏈的 per-batch 構造遭到否決:它不是真正的前綴關係,故其「一致性證明」並不健全。)累積樹給出真正的 RFC 9162 一致性證明,並使單一近期錨定能證明任何較舊 qub 的納入。
LogDO Durable Object 是唯一寫入者(blockConcurrencyWhile,鏡映 QuotaDO / EntitlementDO)——對一條共享日誌的附加是對共享狀態的讀取-修改-寫入,因此必須經由一個 DO,決不經由 KV。它快取該樹的右緣前緣(O(log n) 個雜湊),使關閉一個批次為 O(batch)。一個批次是一同被錨定的葉集合;其觸發條件是可組態的,而非協定凍結的:tree_size 推進至少 LOG_BATCH_MAX_LEAVES(預設 4096),或年齡達到錨定節奏,或當一個付費 T3 封印落地時的強制刷新。root_i 是涵蓋葉 0 .. tree_size_i 的累積 Merkle 樹雜湊。
16.6 經由永久儲存錨定的已簽署樹首
該永久儲存錨定交易即是已簽署樹首,並取代了樹首本身的營運者簽章:每日錨定不需要任何 qub 金鑰,因為永久儲存交易的 owner 即是該簽章。護城河論點成立——承重的是不可變基底,而非某個 qub 持有的機密,才是已錨定根的根本。
該設計中恰好有一把溫態 qub 簽署金鑰,且它是已釘定的:per-seal 的回執金鑰(§16.10)。其公鑰承諾於 LogProfile(與驗證者一同分發)並由 anchor_owner 交叉簽署,因此驗證者依與錨定相同的已釘定根驗證一份回執。這是 §16.15 Q2 的解決方案——一把未釘定的、營運者可輪替的回執金鑰將是可否認的(營運者可否認該金鑰為其所有),這會使回執針對其存在所要嚇阻的營運者層級對手時的問責價值歸零。因此:qub 持有無_未釘定的_日誌簽署金鑰;回執金鑰是已釘定且由 anchor_owner 交叉簽署的。
SignedTreeHead 為規範 CBOR(鍵按編碼長度):size:u64、root:bstr[32]、batch:u64、prev:bstr[32](先前的 sth_hash;創世 = 32 個零位元組)、log_id:bstr[32]、first_seq:u64、anchored_at:i64。其雜湊為 sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead))。
已釘定信任根。 log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address)。一個符合規範的驗證者必須要求 anchor_tx.owner == LogProfile.anchor_owner,其中 anchor_owner(與回執金鑰公鑰)作為 LogProfile 烘焙進 qub_core——與 DrandTimelockProvider::quicknet() 中既有的 quicknet 常數並列——並與驗證者二進位檔一同分發。驗證者還必須在本地驗證該永久儲存交易的 data → tx_id 繫結,而非信任閘道的 /raw/ 回應。這封堵了流氓錢包的模稜兩可漏洞:在驗證者釘定是哪一個錢包之前,「錨定於永久儲存」毫無意義。
輪替是一項 §15 治理擴充,而非重用(已解決——§16.15 Q3)。 §15.2 的配置介面目前僅列舉 sig_algs / drand 鏈 / 封裝版本 / 內容類型,且 §15.3 的觸發條件未列出其中任何一項——LogProfile / anchor_owner 尚未進入 §15 的介面。因此輪替治理必須建立:§15.3 被擴充(如下)以加入 LogProfile 觸發條件,而一次輪替是一個在驗證者更新中發布的、已簽署的 LogProfile 升版。一次有計畫的輪替攜帶一個輸出 → 輸入的交叉簽章;一次因洩而驅動的輪替無法攜帶(恰在其時輸出金鑰是不受信任/不可用的),並回退到 §15 治理下的升版,由(下文的)prev-anchor 分叉檢查在過渡期內限定損害。
模稜兩可窗口(一等信任參數)。 一個葉僅在其覆蓋錨定於永久儲存上已確認之後才具抗模稜兩可性。該窗口為 received_at → 錨定確認(≤ 節奏 + 永久儲存終局性)。在其之內唯一的保證是已釘定的封印回執(§16.10)與 qub 的營運完整性。三項問責工件使這一點誠實而非空泛(見證者模型即 §16.15 Q2 的解決方案):
- 已釘定的已簽署封印回執——上傳回應中返回的 SCT 類比物(§16.10),由已釘定且由
anchor_owner交叉簽署的回執金鑰簽署。一個在其錨定之前被丟棄的葉,留給受害者一份不可否認的回執可供發布,封堵了無聲省略漏洞。 - 已發布的監測方法論 + prev 鏈走訪——錨定的
prev鏈由首端走訪至創世;一個分叉(兩個錨定在同一size卻有不同root,或一條斷裂的prev)即是可發布的失當證明。模稜兩可偵測是一項明示的營運承諾,而非一個無聲假設。 - 雙重自我發布樹首——每個新的樹首
{sth_hash, tree_size}都張貼到一個專用、qub 自有的公開、僅附加的 GitHub 儲存庫(承重的防竄改自我發布之腿),並以一則社群貼文作為僅盡力而為的佐證。一次失敗的張貼必須呼叫待命(不得無聲失敗)。
誠實性界限(具約束力的限制)。 由於 qub 控制兩個張貼介面,這是自我發布,而非獨立見證。任何產品、行銷或法律介面均不得聲稱該日誌是「獨立見證的」;被允許的聲稱是模稜兩可可被偵測且留下一份不可否認的回執。一個真正獨立的第三方見證者延後至一次未來的 §15 治理升版。
received_at 為營運者斷言,且任何聲稱皆不得倚賴它——它決不在任何產品/法律/API/證明渲染介面上被呈現為證明或爭議佐證。永久儲存錨定區塊時間 T 是唯一的免信任時間戳(「記入日誌時間」的上界)。任何對 received_at 的監測健全性檢查必須與 T 比對,而非與營運者控制的 anchored_at STH 欄位比對;這類檢查僅是針對一個誠實營運者時鐘錯誤的防護,而非針對一個惡意營運者的問責控制(§16.15 Q5)。
16.7 錨定交易格式與節奏
AnchorBundle 是經由 §16.8 打包器寫入的規範 CBOR 永久儲存交易主體:ver:u8、sth:bstr(規範 SignedTreeHead 位元組)、prev_anchor:bstr(先前錨定交易 id 的原始位元組;創世時省略)、chain_hash:tstr(生效中的 drand 鏈——quicknet),以及該批次依 seq 序的葉-CBOR 串流,使錨定自我完備:一個監測者零 qub 依賴地從主體重新推導出 root。(若該葉串流在高流量下變得龐大,未來一次修訂可僅以參照承諾一個葉範圍;已記錄,但未在 v1 採納。)
永久儲存標籤刻意可列舉——與私有 qub 不同,日誌意在被找到:App-Name: qub-tlog、Anchor-Format: 1、Log-Id: <hex>、Batch: <n>、Tree-Size: <n>、Root: <hex>、Prev-Anchor: <tx>、Content-Type: application/cbor。標籤是不受信任的提示;CBOR 主體是唯一權威。
節奏: 預設每日,隨流量再行檢視(尺寸觸發條件在負載下自動縮短有效節奏);一個付費 T3 封印會強制一次錨定,使付費客戶決不等待一整天。錨定錢包是專用且低速的,與上傳錢包分開——它必須是其自有的 JWK(一把獨立的金鑰,而非上傳錢包上的一個邏輯角色),如此上傳錢包遭洩亦無法偽造錨定——並帶有一個硬性的每日錨定交易預算(降格後的 ARWEAVE_DAILY_CEILING)。保管姿態被直白陳明:一把窄範圍、帶緊密斷路器且低餘額的熱態金鑰,而非「冷態」——一個每日自動簽署的錢包不可能是冷態,且本規範不假裝它是。
16.8 ANS-104 打包器
一個自製的 ANS-104 DataItem 編碼器與 deep-hash 簽署器,約 300 行,僅用 Web Crypto、零 npm 相依(兩套 Turbo SDK 都通不過 npm ci --ignore-scripts 供應鏈閘)。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
簽署為永久儲存的 deepHash——對 ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] 進行一次遞迴的 SHA-384 摘要(永久儲存的線路要求,crypto.subtle.digest("SHA-384"))——然後以錢包 JWK 透過 crypto.subtle 對該 deep hash 進行 RSA-PSS;id = base64url(SHA-256(signature))。此處的 SHA-384 被隔離為一個永久儲存線路專屬的原語,決不作為 qub 信任原語(§15 記錄了該圍籬;qub 信任雜湊全程為 SHA3-256)。
一條程式碼路徑服務三個消費者:付費 T3 per-qub 永久性、永久儲存不可用時的後援(將 DataItem 入佇,無論如何返回 R2 優先確認——這封堵了當前 ARWEAVE_UNAVAILABLE 503 死路),以及寫入 AnchorBundle。簽章方案(已解決——§16.15 Q8):v1 以 RSA-PSS(簽章類型 1)簽署,重用既有的永久儲存錢包 JWK 機制(零新增長存金鑰保管,服務於「少一個機密」論點);Ed25519 延後至 §15 PQ 遷移路徑。
該手寫 deep hash 是 W5 中最高風險、自然覆蓋率最低的程式碼,故其把關不容妥協(§16.15 Q8):
- 跨語言固件
tlog_v1.json(Rust + TS,即 §14.5 的wrapper_v1.json模式)涵蓋 deep-hash、DataItem 位元組 + id、葉雜湊、一個 5 葉根 + 稽核路徑、一個 STH 雜湊、一個納入證明,以及一個一致性證明——在簽署與驗證兩個方向上(驗證方向之所以重要,是因為 §16.6 的本地 tx → tx_id 檢查把 deep hash 拉進了每個獨立驗證者,而不僅是寫入者)。 - 一次性、透過一個參考 ANS-104 打包器的互通往返,僅作為靜態測試資料消費——決不作為 npm 執行期相依(僅用 Web Crypto/無安裝指令稿的姿態維持不變)。
- deep-hash + RSA-PSS 路徑必須透過生產所用的同一套
crypto.subtle原語往返,使該自製編碼器位元組相容。 - 一個持續的打包後驗收監測器確認每個錨定/後援 DataItem 確實達成永久儲存接受,並帶警報 + 斷路器——因為該 deep hash 也服務於永久儲存不可用時的後援佇列,故一次無聲倒退會在它意在涵蓋的那次中斷期間,以網路拒絕的項目填滿該佇列。
16.9 納入與一致性證明
兩者皆為 RFC 9162、SHA3-256,以規範 CBOR 供應。
InclusionProof——GET /api/v1/qub/:tx_id/proof:ver:u8、leaf:bstr(確切的葉 CBOR——驗證者自行重新計算 leaf_hash,決不信任所提供的雜湊)、index:u64、size:u64、audit:[bstr[32]]、root:bstr[32]、anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }。
ConsistencyProof——GET /api/v1/log/consistency?first=<size_a>&second=<size_b>:ver:u8、first_size:u64、second_size:u64、first_root:bstr[32]、second_root:bstr[32]、nodes:[bstr[32]]、first_anchor、second_anchor。一份單一無歧義的鍵清單,由測試向量釘定。
獨立驗證(無 qub 伺服器,擴充 §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).
證明供應儲存必須以座標為鍵(已解決——§16.15 Q7,阻塞性前提)。 冷葉證明生成在正確性上是中性的,僅當 R2 稽核材料是一個以絕對樹座標 (level, index) 為鍵的持久 Merkle 節點儲存——而非 per-batch 節點增量。在一個以座標為鍵的儲存下,任何 (leaf i, size N) 稽核路徑都是一組 O(log N) 次直接 R2 GET,且跨批次邊界無需重新計算;在一個以批次為鍵的儲存下則否,而這正是本解決方案所封堵的儲存佈局缺口。葉主體同樣以 seq 為內容可定址。一個 W5 測試向量必須證明一個創世時代的冷葉,在 LogDO 儲存被清空的情況下僅以 R2 + 永久儲存對抗一個遠晚得多的根,使 §16.13 中的回收安全性聲稱有所支撐而非僅僅斷言。那些 O(log N) 次循序 R2 GET 只屬於非同步證明端點——決不屬於封印熱路徑(§16.10)或 per-tick cron。
16.10 R2 優先確認排序
POST /api/v1/upload 的序列變為:
- 前半段閘(驗證、校驗、冪等分片鍵)——不變。
- 同步
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)——持久性下限;也封堵了 W1 的預快取競爭(先前是永久儲存提交之後的一個ctx.waitUntil)。 - 同步
await LogDO.append(leaf)——一次同 colo 的 DO RPC;唯一寫入者指派seq、延伸條目鏈,並更新前緣。(對共享狀態的 RMW → DO,決不 KV。)該appendRPC 只做這件事——O(batch)的 Merkle 批次關閉工作在 LogDO 警報上於此 RPC 之外執行,否則 append p95 會在每第LOG_BATCH_MAX_LEAVES次封印時尖峰。 - 立即返回確認——附上封印回執(由已釘定回執金鑰簽署,§16.6)與
{ tx_id, log_seq, anchor_status: "pending" }。多秒的永久儲存扇出已從關鍵路徑移除。 - 一個
ctx.waitUntil將延遲工作入佇:per-qub 永久儲存提交(如今為盡力而為/付費;失敗時改路由至打包器後援佇列,而非對使用者回 503)加上既有的暫定元資料寫入。批次關閉與錨定獨立地從 LogDO 警報與每日錨定 cron 執行。迴圈內無ctx.waitUntil;既有的冪等分片鍵予以保留。
延遲預算(已解決——§16.15 Q7)。 < 300 ms 的 p95 目標是一個實測的上線閘,而非一個假設。誠實的關鍵路徑是前半段的 KV 讀取 + 一次 R2 PUT + 兩個串行的 Durable Object——既有的 QuotaDO 封印配額扣減以及新的 LogDO 附加——因此該預算必須計入兩次同 colo 的 DO 往返,而非一次。發布一個鏡映 QuotaDO 的 LogDO 延遲警報,並將 p95 倒退視為一個發布阻塞項。
16.11 信任模型——精確的聲稱,依葉 kind 限定範圍
對 kind=0x01(已認證): 「此內容——主體與 body_hash 匹配、由 qub_id 識別——已於位置 seq 承諾至 qub 的僅附加日誌,且其存在不晚於永久儲存區塊時間 T;在 drand 輪 R = unlock_round(unlock_at) 之前它在密碼學上不可讀。」 這是完整的 {tlock 輪繫結 + Merkle 納入 + 已錨定根} 三元組。
對 kind=0x02(已斷言,即預設): 「一段內容位址為 chash、聲稱 qub_id 與 unlock_at 的不透明密文,已於位置 seq 承諾至僅附加日誌,且其存在不晚於永久儲存區塊時間 T。」 輪與主體那兩條腿由既有的 §11 .qub 套件驗證(qub_core::unlock)提供,而非由日誌提供;日誌相較於一筆裸的 per-qub 交易所增添的,是防竄改排序、一個免信任的上界承諾時間,以及抗模稜兩可性。
依 §11,兩種聲稱皆排除:無 sig_alg ≥ 0x01 的作者身分、意圖,以及次錨定粒度的時機。兩者皆不讓任何聲稱倚賴 received_at。
聲稱上限(具約束力的上線限制——已解決 §16.15 Q1)。 對免費/預設(kind=0x02)qub,上述限定範圍的 kind=0x02 聲稱是任何產品、行銷、條款或證明渲染介面可主張之內容的上限。任何介面均不得陳述或暗示日誌證明了一個預設 qub 的內容或其解鎖輪——日誌證明的是一段不透明密文的排序 + 一個免信任的上界承諾時間。內容與輪的證明專由既有、且與日誌無關的 §11 .qub 套件驗證提供。這對文案是一個硬性上線阻塞項,而非風格偏好;它是使位元組盲預設路徑保持誠實的那項解決方案。
16.12 版本控制與 W3 協調
不發生 SealedQub 線路升版,因而不發生協定版本升版(§12.1):日誌是一個承諾既有欄位與位元組的旁掛物,故它不進入 §12.2 的協定版本歷程。W3 的可選 drand_chain_version 不受觸動,並仍是唯一的可選 SealedQub 欄位。日誌轉而引入其自有的獨立版本空間——LOG_VERSION_1、ANCHOR_FORMAT_1、InclusionProof.ver——鏡映 §12.4 的封裝版本獨立性(封裝攜帶一個獨立於協定版本的版本位元組,而日誌版本遵循同一分離)。
證明傳遞預設為抓取,並帶一個可選的隨行物。 一個證明在封印時不可能存在(錨定尚未寫入),因此封印時的 .qub 套件保持無證明。W7 的驗證者抓取一次 GET …/proof,或在完全離線模式下,經由對 Log-Id 的一次永久儲存查詢從公開的 AnchorBundle 重建該證明。.qub 套件(W7)保留一個可選 inclusion_proof 成員——封印時缺席,由錨定後的重新匯出為冷封存而填入——遵循與 W3 的 drand_chain_version 相同的「可選、預設省略、附加性」模式。
16.13 保留
LogDO 開放尾段、R2 證明供應基底、錨定斷路器計數器,以及打包器後援佇列的保留窗口,規範於 docs/DATA-RETENTION.md。原則:日誌的熱態 per-entry 儲存(LogDO)在錨定後可回收;其稽核材料——以座標為鍵的 (level, index) Merkle 節點儲存 + 以 seq 定址的葉主體(§16.9)+ 永久儲存錨定——是永久的。從 DO 回收一個冷葉決不會使一個已簽發的證明失效,因為一個證明是對抗那個永久 R2 節點儲存與永久儲存錨定解析的,而非對抗 DO(且 §16.9 的清空 DO 測試向量證明了此點)。
16.14 測試向量
W5 交付跨語言固件 tlog_v1.json(§16.8)外加數個工作向量:一個 kind=0x01 與一個 kind=0x02 葉 → leaf_hash;5 葉累積根;一個納入證明;一個一致性證明;一個 AnchorBundle;以及一個 DataItem id。這些與 §14.5 的外層封裝向量並列,並由 Rust(qub-core)與 TypeScript(Worker)兩套實作演練。
16.15 審查決策(W5——已解決)
W5 外部審查(一次對抗式設計掃描 + 擁有者簽核)已完成。下列每項決策皆已底定,並反映於上文 §16 文本中;具約束力的上線限制於末尾重述。實作可在其下進行。
- 預設路徑(
kind=0x02)葉誠實性——已解決。 依規範交付雙葉-kind 拆分:kind=0x02既不承諾body_hash也不承諾drand_round。位元組盲路徑上無*_body_hash欄位(它將是對整合者最易誤讀的虛假「已驗證」訊號,且是 §11 已從套件提供的便利)。不要求日誌已認證 qub 採用伺服器封印(那將迫使明文通過 Worker 並摧毀密碼粉碎護城河)。任何自我描述的短路皆屬於.qub套件/證明信封中、作為驗證者重新計算的欄位,決不作為葉欄位。擁有者已確認的聲稱上限:§16.11。 - 模稜兩可/省略問責——已解決。 封印回執金鑰在
LogProfile中已釘定 + 由anchor_owner交叉簽署(封堵了先前的「無簽署金鑰」矛盾;§16.6)。上線見證者模型:已釘定回執 + 監測方法論 + prev 鏈走訪 + 雙重自我發布樹首(qub 自有的公開 GitHub 儲存庫,社群盡力而為),以可偵測 + 帶回執為其行銷,決不以獨立見證。一個真正的第三方見證者延後至一次 §15 治理升版。 - 已釘定錨定擁有者信任根 + 輪替——已解決。 採用
LogProfile釘定(§16.6);驗證者檢查anchor_tx.owner == anchor_owner並在本地驗證該交易的 data → tx_id 繫結。輪替治理是一項待建立的 §15 擴充(新增 §15.3 觸發條件),而非重用;有計畫的輪替交叉簽署,因洩而驅動的輪替回退至 §15 升版,由分叉檢查限定損害。 - 私有 qub 葉盲化——已解決。 對私有 qub 保留盲化(
ref = SHA3-256(qub_id ‖ log_blind_secret)),對公開 qub 用原始qub_id(已見 §16.2.1),chash作為獨立繫結。log_blind_secret是一個關聯/女巫等級機密,僅向前輪替(§16.2.1)。 received_at——已解決。 將其保留於葉中,已承諾但明示為非證據性;決不在任何介面上被呈現為證明或爭議佐證。任何監測健全性檢查皆與永久儲存區塊時間T比對,而非與營運者控制的anchored_at比對(§16.6)。- 免費層可證明時機——已解決(擁有者簽核)。 持久性不退化;僅可證明的上界承諾時間粗化為錨定區塊時間。免費層文案不使用任何數值 SLA(「……於下一次日誌錨定時加上,通常為每日」);精確到小時的證明是付費 T3 屬性,在層級比較介面 + 條款中揭露(§16.1)。
- Workers 上的累積樹——已解決。 單一累積 RFC 9162 樹 + 前緣快取的唯一寫入者 LogDO(相對於約 1k writes/sec 的 DO 上限有充裕餘裕;將 Merkle-of-shard-roots 分片延後至接近它時)。阻塞性前提: 以座標為鍵的
(level, index)R2 節點儲存 + 清空 DO 的冷葉測試向量(§16.9);< 300 ms是跨兩個串行 DO 的一個實測上線閘(§16.10)。 - ANS-104 簽章方案 + deep-hash——已解決。 RSA-PSS(簽章類型 1,重用專用錨定錢包 JWK);Ed25519 延後至 §15 PQ 路徑。手寫的 SHA-384 deep hash 把關於:雙方向跨實作固件、僅靜態的參考打包器互通檢查、共享
crypto.subtle往返,以及打包後永久儲存接受監測器(§16.8)。
具約束力的上線限制(帶入實作 + 產品/法律審查):
- 聲稱上限(Q1/Q6)。 任何介面均不得宣稱日誌證明一個預設 qub 的內容或解鎖輪;被允許的聲稱是已排序、防竄改地,並帶一個免信任的上界承諾時間。免費層時間戳文案不攜帶任何數值延遲;精確到小時的證明僅限付費 T3。
- 見證誠實性(Q2)。 將模稜兩可行銷為可偵測 + 帶回執,決不獨立見證。
- 回執與錨定金鑰(Q2/Q8)。 回執金鑰已釘定 + 交叉簽署;錨定錢包是其自有、有別於上傳錢包的 JWK。
- deep-hash 閘(Q8)。 在雙方向固件 + 互通檢查通過之前,不交付任何錨定或 T3 交易;驗收監測器在失敗時呼叫待命。
- 儲存前提(Q7)。 以座標為鍵的節點儲存 + 清空 DO 的冷葉向量,是「回收決不使一個證明失效」保證的先決條件。