qub プロトコル仕様
qub は、暗号学的な時間的コミットメントのためのプロトコルです。未来の日付まで言葉を封印し、後から、何が封印されたか、どの drand ラウンドが公開を制御したか、さらにストレージトランザクションまたは透明性ログ証明が得られる場合には、暗号文がコミットされた時刻について独立したタイムスタンプ付き上限を検証するためのシステムです。
これを成り立たせている要素は 3 つあります。drand は分散型のランダム性ビーコンであり、公開日は qub の善意ではなく暗号学的に強制されます。耐久性ストレージと追記専用の透明性ログ は封印済みバイト列を保存し、バッチ化されたコミットメントを永久公開ストレージへアンカーします。有料の T3 経路では、個別の永久ストレージトランザクションも書き込みます。ML-DSA-65 は耐量子デジタル署名であり、作成者署名が有効な場合、qub は秘密鍵が作成者の端末から決して出ない鍵ペアに結び付けられます。
これらの要素を組み合わせることで、時間ロックされ、改ざんが明らかで、任意に帰属可能であり、独立したタイムスタンプを付けられる発言が生まれます。これは、世界が過去を捏造する能力を高めるほど価値が増す受領証です。
本書の残りは、相互運用可能な実装に必要な規範的仕様です。
qub プロトコル仕様
| 項目 | 値 |
|---|---|
| ドキュメントリリース | 1.0.0 (protocol-v1.0.0) |
| ワイヤプロトコル | 0x01 |
| 外側ラッパー | 0x01 |
| 発効日 | 2026-09-23 |
| ステータス | 現行 |
| レビュー対象範囲 | 2026-09-23 |
本書は qub 時限コミットメントシステムの規範的なプロトコル仕様です。相互運用可能な実装に必要なデータ構造、シリアライズ規則、導出式、検証手順を定義します。
スコープ: プロトコル層は意図的に言語中立です。qub の本文は不透明な平文 / Markdown / 約定バイトであり、ロケールに応じた表示はビューアー(qub.social ウェブアプリ、<qub-embed> iframe、MCP クライアントなど)の責任です。
1. 表記と規約
| 表記 | 意味 |
|---|---|
u8, u64, i64 |
指定ビット幅の符号なし / 符号付き整数 |
[u8; N] |
N バイトの固定長バイト配列 |
Vec<u8> |
可変長バイト配列 |
Option<T> |
型 T の値、または不在 |
String |
NFC 正規化された UTF-8 テキスト文字列 |
| ` | |
SHA3-256(x) |
バイト列 x の NIST SHA3-256 ハッシュ(FIPS 202) |
ceil(x) |
天井関数: x 以上の最小整数 |
| CBOR | Concise Binary Object Representation(RFC 8949) |
| big-endian | 最上位バイトを先頭に配置 |
プリイメージ構成におけるすべての整数は、特に指定がない限り、big-endian の固定幅バイト配列として符号化されます(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, // 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(復号後のペイロード)
正規 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>, // 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
}
ベースライン(署名なしテキスト qub): version = 0x01、content_type = 0x01、sig_alg = 0x00 で、署名フィールドと共同署名者フィールドは不在です。その他の任意メタデータフィールドは存在してもかまいません。
その他の 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)を用いてシリアライズされます。これは内側のワイヤアーティファクトです。公開配布ではこのバイト列を bare のまま保存し、非公開配布では保存前に OuterWrapper でラップします(§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(ビューアーアプリの状態)
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>, // 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. 正規 CBOR プロファイル
すべての SealedQub および QubEnvelope のシリアライゼーションは本プロファイルに準拠しなければなりません(MUST)。同一の論理構造を与えられた二つの実装は、同一のバイト列を生成しなければなりません(MUST)。
3.1 符号化規則
| 規則 | 仕様 |
|---|---|
| 標準 | RFC 8949 §4.2.1(Core Deterministic Encoding Requirements) |
| マップキー順序 | まず 符号化バイト長 でソート(短いものを先)、次に 辞書式(同一長の符号化はバイト単位) |
| 整数符号化 | 最短形式: 0–23 は初期バイトに、24–255 は 2 バイトに、256–65535 は 3 バイトに、以下同様 |
| 長さ符号化 | 確定長のみ。 不定長の配列、マップ、バイト文字列、テキスト文字列を使用してはなりません(追加情報 = 31 は禁止)。 |
| タグ | CBOR タグなし(メジャー型 6 は禁止)。 |
| 浮動小数点 | 浮動小数点なし(メジャー型 7 値 0xF9–0xFB は禁止)。 |
| テキスト文字列 | UTF-8 符号化、NFC 正規化済み(Unicode 正規化形式 C)。 |
| バイト文字列 | 生バイト。CBOR レイヤでは base64 符号化しません。 |
| 重複キー | エラーで拒否。 パーサは重複するマップキーを黙って受け入れてはなりません(MUST NOT)。 |
| 不明なキー | エラーで拒否。 パーサは型の正規キー集合の外にあるマップキーを許容してはなりません(MUST NOT) — 2 つの異なる正規バイト列が同じ値にデコードされることは決してあってはならず(encode(decode(x)) == x)、署名付きペイロードでは余分なキーは両方の署名がコミットする隠れたコンテンツになります。スキーマの進化は version を通じて行い、余分なキーによっては決して行いません。 |
| 単純値 | true(0xF5)、false(0xF4)、null(0xF6)のみ許可されます。 |
| オプションフィールド | 不在のオプションフィールドは CBOR マップから完全に 省略 されます(null として符号化しません)。存在するオプションフィールドはソート済みキー順に含めます。 |
3.2 検証済み正規キー順序
これらのキー順序は規範です。実装はキーを正確にこの順序で出力しなければなりません(MUST)。デバッグアサーションは非リリースビルドで順序を検証することが望ましい(SHOULD)。
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 (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 (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(約定本文、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(1 バイト) |
|
| コンテンツタイプ(u8、値 1) | 0x01(1 バイト) |
|
| sig_alg(u8、値 0) | 0x00(1 バイト) |
|
| 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 です。整列のため 1 バイトの 0x00 パディングを付加して 10 バイトにします。実装は正確にこの 10 バイトを使用しなければなりません(MUST): [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]。
outcome_at の符号化: リリース前の実装改訂で、任意の outcome_at フィールドを結合へ含めるため、プリイメージを 92 バイトから 100 バイトへ拡張しました。outcome_at が不在の場合は 8 つのゼロバイトとして符号化されます。プロトコル検証器はあらゆる箇所で outcome_at <= 0 を拒否するため、この番兵値が正当な値と衝突することはありません。§3.2(ワイヤ形式)と、このフィールドの動機となる判定メカニズムについては、リポジトリ内の tasks/verdict-uplift-plan.md を参照してください。
drand_round の符号化: その後のリリース前実装改訂で、drand_round(対象の drand ラウンド、§4.3)を結合へ含めるため、プリイメージを 100 バイトから 108 バイトへ拡張し、ドメインセパレータを QUB_ID_V2 に引き上げました。これにより timelock のラウンドが qub の ID に結合されます。ゲートウェイは、表示される unlock_at が示すラウンドとは異なる(例えばすでに過去の)ラウンドに暗号文を再結合できません。解錠手順(§8)はさらに、tlock 暗号文スタンザに焼き込まれたラウンドが unlock_round(unlock_at) と一致することを検証します。したがって、表示される解錠時刻は、復号を実際に制御するラウンドです。
性質:
- プリイメージに結合されたフィールド —
version、content_type、created_at、unlock_at、outcome_at、drand_round、生のbodyバイト列(body_hash経由)、またはtitle(title_hash経由) — のいずれかを変更すると、異なるqub_idが生成されます。 - qub_id は暗号化前に計算されます。QubEnvelope と SealedQub は同じ qub_id を持ちます。ビューアーは復号後に両者が一致することを検証します。
qub_idはsender_label、reply_to、署名バイト列、署名公開鍵には依存しません。ただし現在の V2 署名構成では、署名が存在する場合、sender_labelとreply_toはそれぞれsender_label_hashとreply_to_or_zeroによって直接認証されます(§9.3)。- SealedQub の
titleを変更すると(他のすべてを固定しても)title_hash経由でqub_idが変わります。したがってゲートウェイは、qub のアイデンティティを無効化することなくカウントダウンに表示される平文タイトルを差し替えることはできません。 - SealedQub の
outcome_atを変更すると(他のすべてを固定しても)プリイメージ経由でqub_idが変わります。ゲートウェイは、qub のアイデンティティを無効化することなく、カウントダウンに表示される公開前の verdict-on 日付を差し替えることはできません。 drand_roundを変更すると(他のすべてを固定しても)プリイメージ経由でqub_idが変わります。ゲートウェイは、qub のアイデンティティを無効化することなく timelock 暗号文を別のラウンドに再バインドすることはできません。§8 の解錠時スタンザラウンドチェックと組み合わさることで、表示される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 = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
| パラメータ | ソース | 例 |
|---|---|---|
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 |
これは基準 tlock マッピング(drand の CurrentRound)です。drand はラウンド N を chain_genesis_time + (N - 1) * chain_period_seconds に公開するため、この式は unlock_at 時点で現行のラウンド、すなわち unlock_at に到着したビューアーが最初に利用できる署名のラウンドを選択します。
整列特性(実務上重要な場合): (unlock_at - chain_genesis_time) が chain_period_seconds で割り切れる場合、選択されたラウンドの署名は unlock_at ちょうどに公開され、それより前には決して公開されません。基準デプロイでは常に成立します。quicknet のジェネシス時刻(1692803367)は 3 秒の周期で割り切れ、基準アプリは解錠時刻を分単位に固定するためです。整列していない unlock_at では、選択されたラウンドの署名が unlock_at より 1 周期未満だけ前に公開されます。このコミットメントの時間精度はビーコン 1 周期です。
リリース前の旧マッピングと解錠側の許容: 元のマッピングは ceil((unlock_at - chain_genesis_time) / chain_period_seconds) でした。上記の周期に整列した場合、これは unlock_at の丸 1 周期 前 に公開されるラウンドを選び、暗号文を正確に 1 周期早く復号可能にしていました。デルタが周期で割り切れる場合、2 つのマッピングには正確に +1 の差があり、それ以外では一致します。drand_round は不変の qub_id プリイメージに含まれるため(§4.1)、旧マッピングで封印されたアーティファクトは再導出できません。そのため、§8 ステップ 6a のラウンド交差検査を行う検証者は、保存された drand_round が 導出ラウンドまたは導出ラウンドから 1 を引いた値 のいずれかなら受け入れなければなりません(MUST)。また、tlock スタンザのラウンドが保存されたラウンドと正確に一致することを要求しなければなりません(MUST)。この許容が最も早い制御署名を広げるのは最大 1 周期です。約定ステージングサービスも、ステージ時と共同署名時に、ステージ済み約定の qub_id を再導出するとき同じ許容を適用します。現行マッピングのラウンドでコミット済み qub_id を再現できず、デルタが周期で割り切れる場合は 1 つ前のラウンドで再試行し、qub_id が実際に結合する方のラウンドへ完成済み約定を封印します。再計算したラウンドへ無条件に封印して、アーティファクトを恒久的に導出不能にすることはありません。
検証: unlock_at は封印時点で未来でなければなりません(MUST)。unlock_at は created_at から 10 年を超えてはなりません(MUST NOT)(長期の drand 依存リスクを制限するため。UI は 2 年を超える公開日時について警告することが望ましい(SHOULD))。
5. ワイヤ形式 newtype
ワイヤ形式の newtype は、CBOR バイトを JSON、生平文、その他のバイト符号化と取り違えることに対するコンパイル時の安全性を提供します。
| 型 | 内容 | 生成元 | 消費先 |
|---|---|---|---|
SealedQubCbor |
SealedQub の正規 CBOR | serialize_sealed_qub() |
内側のワイヤアーティファクト。公開配布では bare で保存し、非公開配布ではラップしてから、ビューアーが復元します。 |
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 マップヘッダで始まることを検証することが望ましい(SHOULD)。完全な構造検証は二重解析を避けるため、構築時ではなく解析時に行います。
6. コンテンツタイプレジストリ
| 値 | 型 | 最大本文サイズ | 備考 |
|---|---|---|---|
0x00 |
予約済み(無効) | — | 使用してはなりません(MUST NOT) |
0x01 |
プレーンテキスト(UTF-8、制限付き Markdown) | 50 KB 有料 / 10 KB 無料 | 表示規則は §10 を参照。無料 / 有料の分割はアップロードサービスによって強制されます。プロトコル層の絶対上限は 50 KB です。 |
0x02 |
予約済み(将来) | — | 将来のコンテンツタイプ用に割り当て済み。v1 では無効です。ビューアーは以下の規則に従って拒否しなければなりません(MUST)。 |
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 を参照。 |
ビューアーは、不明なコンテンツタイプを明確なユーザー可視のエラーで拒否しなければなりません(MUST)。ビューアーは不明な型をテキストとして表示しようとしてはなりません(MUST NOT)。
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 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }
3 つのマップすべての正規 CBOR キー順序は §3.2 にあります。約定 CBOR のシリアライズ済み合計は 100 KB を超えてはなりません(MUST NOT)(§6 と一致)。
スキーマ識別子。 structured/v1 約定の terms の最初の行は { key: "pact_schema", value: "structured/v1" } でなければなりません(MUST)。このマーカーを持たない行は「カスタム」約定であり、構造化検証やスキーマ対応の表示は受けません。
固定された承認スロット。 structured/v1 約定は次のキーの下に正確に 4 つの承認行を持ちます。
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
それぞれの value は (role, kind) ペアによって選ばれる 8 つの固定英文字列のいずれかであり、ここで role ∈ { seller, buyer, provider, client }、kind ∈ { standard, capacity } です。これらの文字列自体が 規範的プロトコルデータ であり、両当事者の ML-DSA-65 署名は body_hash を介して正確なバイト列にコミットします。これらはローカライズされません。署名対象の本文は言語中立です。文言を変更するには新しいスキーマバージョン(structured/v2)が必要です。
8 つの文字列、それらのルックアップ(acknowledgement_for(role, kind))、および各々の根拠はリファレンス実装で固定されています。準拠する実装はバイト単位で同一の承認値を出力しなければならず(MUST)、4 つの役割組み合わせをすべてカバーするゴールデンフィクスチャの SHA3-256 body-hash テストがあらゆる差異を捕捉します。
ビューアーの表示順序。 承認文字列には「described above」のようなフレーズが含まれており、これは説明 / スコープ行が承認の前に表示されることを前提としています。ビューアーは terms 配列を CBOR の順序で表示しなければなりません(MUST)。並び替えは本文の意味を壊します。
カウンターパーティの連絡先。 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 を超えてはなりません(MUST NOT)(上記レジストリ行と一致)。
結果列挙。 ワイヤバイトはインテント中立です。4 つのバケット Right / Partial / Wrong / Unfalsifiable は、判定を持つすべてのインテントの結果空間を網羅します。インテント別ラベル(Right の場合「Called it」/「Kept it」/「Shipped」/「Confirmed」など)はビューアー側の描画上の関心事であり、親 qub のインテントに対して解決されます。ワイヤは言語およびインテントに中立を保ちます。1..=4 の範囲外の値は復号時に拒否しなければなりません(MUST)。
親リンク。 判定 qub は本文中に親への参照を持ちません。親 qub の Arweave トランザクション ID はアップロード時に Parent-Tx-Id ストレージタグとして発行されます(§7 ストレージタグ層)。これにより本文は自己完結した自己採点の署名済み宣言として保たれ、監査チェーン(「何について正しかったのか」)は Arweave タグのルックアップを介して確立されます。
根拠 URL の安全性(規範的)。 evidence_url が存在する場合、検証側(構成側、ワイヤ側、Worker エッジ)は以下を強制しなければなりません(MUST):
- HTTPS のみ。 文字列はバイト列
https://で始まらなければなりません(MUST)。他のスキーム —http、ftp、javascript、data、fileなど — は拒否します。 - 長さ上限。 ≤ 2,048 バイト(ブラウザ URL の実用上限)。
- NFC + 敵対的コードポイントチェック。
titleおよびreflectionと同じ規則 — 双方向制御 / ゼロ幅 / タグブロック / BOM / C0 / C1 のコードポイントは拒否します。定義は Rust のcrate::handle::contains_hostile_text_codepointおよび TS のworkers/api/src/utils/unicode.ts::isHostileCodepointと一致します(同期を保つこと)。 - 空白なし、ASCII 制御文字なし。 URL 内のどこかに空白 / DEL /
0x20未満のバイトがある場合は拒否します — 双方向規則が捕捉しない\n/\t注入ベクターを閉じます。 - 空でないホストセグメント。
https://と最初の/、?、または#の間のすべては空であってはなりません(MUST)。
サーバー側フェッチなし。 Worker は URL をプロキシ、フェッチ、プレビューしてはなりません(MUST NOT)。プロトコルは文字列を保存するだけで、描画はビューアー側で rel="nofollow noopener noreferrer" target="_blank" 付きでリンクテキストとともにホスト名を可視表示して行います。
振り返り。 任意の作成者記述の振り返りテキスト(「何が変わったか、何を学んだか」)。title と同じ NFC + 敵対的コードポイントの検証を行います。空 / 空白のみの入力は構成時に不在へ折りたたまれます。
スキーマバージョン。 v1 は verdict_version = 0x01 のみをサポートします。将来のスキーマ改訂はこのバイトを上げ、§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.
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.
ストレージタグレイヤ(帯域外)。 qub アップロードサービスは、選択されたアップロードペイロードと共に、意図的に小さなストレージトランザクションタグのセットを付加します。Content-Type=application/octet-stream は規範的に必須です。リファレンスサービスはさらに、作成者がそれらを表示するよう選択した場合に 3 つの任意タグを付加します。Intent(許可リストで検証された作成意図 — announcement、thesis、prediction、letter、secret、commitment、proof、またはシステムが発行する verdict のいずれか)、Author(64 文字の小文字 16 進数による作成者の §9.3 公開鍵フィンガープリント)、および Parent-Tx-Id(返信チェーン用の親 qub のストレージトランザクション ID、43 文字の base64url)。
Author タグは qub ごとにオプトイン です。リファレンス作成者アプリは、ユーザーが封印時に明示的に公開帰属を有効にした場合にのみこれを付加します。トグルがオフのとき(既定)、Author タグは書き込まれず、qub はチェーン上で帰属表示されません。永久ストレージ上のいかなる情報も、そのアップロードを作成者のハンドル、メール、その他の qub にリンクしません。トグルがオンのとき、Author フィンガープリントは §9.5 のアテステーションチェーンを介して作成者が選んだ @handle に解決されます。返信チェーンの関係性および Intent は識別性を持ちません。非公開配布では、外側のラッパー(§13)が認識可能な内側の SealedQub アーティファクトを暗号化するため、保存済みラッパーを収集して公開 drand 署名を取得しても、K なしに本文を回復することはできません。ストレージタグは、意図的に公開メタデータのままです。
リファレンスサービスは意図的に App-Name、App-Version、Type タグを付加しません。そのような単一値フィルタは GraphQL クエリに qub コーパス全体を返してしまい、ラッパーの本文限定の秘匿性スコープと矛盾するためです。
準拠する検証者は、§11 の第三者検証のためにいかなるストレージタグにも依存してはなりません(MUST NOT)。本文ハッシュ / qub_id / 署名は内側の CBOR にのみコミットし、タグセットにはコミットしません。
8. 解錠プロトコル
完全な解錠シーケンスです。各ステップは規範的です。
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. 作成者署名
9.1 根拠
qub は永久ストレージに保存されます。作成者の署名は無期限に偽造不可能でなければならず、そのため v1.0 では、qub の永久的な寿命の間にセキュリティが劣化する可能性のある古典スキームではなく、耐量子の ML-DSA-65 スキーム(FIPS 204)を使用します。
9.2 アルゴリズムレジストリ
sig_alg |
スキーム | 鍵サイズ | 署名サイズ | 状態 |
|---|---|---|---|---|
0x00 |
署名なし(未署名) | — | — | 有効 |
0x01 |
ML-DSA-65(FIPS 204) | 1,952 バイト | 3,309 バイト | 有効 |
0x02 |
Ed25519 | 32 バイト | 64 バイト | 予約済み定数。プロトコル v1 では未対応 |
プロトコル v1 のビューアーは、予約済みの 0x02 を含め、{0x00, 0x01} 以外のすべての値を拒否しなければなりません(MUST)。予約は誤った再利用を防ぐためのものであり、有効化ではありません。有効化には §15 の統治された変更が必要です。
9.3 署名プリイメージの構成
プリイメージには 2 つのバージョンが存在してきました。すべての署名は V2 を使用しなければならず(MUST)、検証者は V2 のみを受け入れなければなりません(MUST)。 レガシーの 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 プリイメージのみを受け入れなければなりません(MUST)。この定義は、歴史的参照のため、および下記のドメインセパレータを説明するために、ここに保持されています。V1 に対してのみ検証される署名は、検証失敗として扱わなければなりません(MUST)。
ドメインセパレータ: "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])。パディングなし。セパレータが異なることで 2 つの構成がドメイン分離されるため、一方のプリイメージに対する署名がもう一方として検証されることは決してありません。
org_id_present バイト: unlock_at に続くバイトは 0x00 でなければなりません(MUST)。リファレンス実装はこれを crates/qub-core/src/signing.rs の定数 ORG_ID_PRESENT_INDIVIDUAL = 0x00 として公開しています。検証用に sig_input を再構築するビューアーは同じバイトを出力しなければなりません(MUST)。
署名スコープ — 何がカバーされ何がカバーされないか。 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 プリイメージ経由 |
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 に対してのみ検証される署名)は、V1 へダウングレードして受け入れられるのではなく、そのまま拒否されます。 - エンベロープ内の
author_pubkeyは真のアイデンティティアンカーであり続けます — ビューアーはsender_labelを信頼するのではなく、author_pubkeyから(§9.5 アテステーション層を介して)表示アイデンティティを導出しなければなりません(MUST)。
sender_label または reply_to をエンドユーザーに表示する実装は、ラベルではなく認証されたアイデンティティ(公開鍵フィンガープリント、アテステーション)を主要なアイデンティティシグナルとして表示しなければなりません(MUST)。
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)がすべてパスした後に実行することが望ましい(SHOULD)。
9.5 アイデンティティアテステーション
アイデンティティアテステーション — author_pubkey を qub ハンドル、メールアドレス、ソーシャルハンドル、パスキー資格情報などの人間が認識可能なアイデンティティクレームにマッピングするもの — は ビューアー側のプログレッシブエンハンスメント であり、署名検証には 必須ではありません。アテステーションを表示アイデンティティに解決するビューアーは、次の優先順位を適用しなければなりません(MUST):
handle > email > social > fingerprint
フィンガープリントフォールバックは SHA3-256(author_pubkey) の小文字 16 進数であり、署名付きの qub であれば常に利用可能です。ビューアーは表示のためにこれを省略してもかまいません(MAY) — リファレンスビューアーは qub: に続けて最初と最後の 4 バイトを表示します(qub:<8 hex>…<8 hex>)。
準拠する検証者は §9.4 のすべてのチェックを、qub API に接続せず、永久ストレージと drand を超えるネットワークを使用せず、サーバー側のルックアップなしで完了できます。アテステーション解決は、署名検証が成功した後にのみ実行される、別個のベストエフォートステップです。
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 は保存サイズをおおよそ 3 倍にします。絶対コストは無視できるほど小さいです。
9.7 共同署名者検証(約定の双方向合意)
双方向合意(content_type = 0x03)では、両当事者が同じ条件に同意したことを証明するために 2 つ目の署名層が用いられます。
エンベロープフィールド:
cosigner_pubkey: 共同署名者(Party B)の ML-DSA-65 公開鍵。cosigner_signature: 作成者と同じsig_inputに対する署名(§9.3)。
両方のフィールドは共に存在するか、共に不在でなければなりません(MUST)。正確に 1 つのみが存在する場合、ビューアーは完全性エラーを報告しなければなりません(MUST)。
検証手順:
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 とその連絡先の正規化メールハッシュの両方に一致する短期間のメール検証マーカーが存在しない限り、共同署名リクエストを拒否しなければなりません(MUST)。マーカーは、マジックリンクトークンが staging_id を含み、検証されたアドレスが SHA-256(normalise_email(party_b.contact)) に一致する場合に /api/v1/auth/verify によって書き込まれます。ここで 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 表示とサニタイゼーション
このセクションはセキュリティ上重要です。ビューアーはテキスト qub(content_type = 0x01)を制限付きの Markdown サブセットを用いて表示します。
10.1 許可された要素
- 見出し:
#から####まで(#####および######はなし) - 強調: 太字(
**)、斜体(*)、取り消し線(~~) - リスト: 順序付き(
1.)および順序なし(-、*) - 引用ブロック(
>) - コード: インラインスパン(```)およびフェンスブロック(`````)
- 水平線(
---) - 改行(末尾の 2 つの空白または空行)
- 段落
10.2 禁止された要素
| 要素 | 処理 |
|---|---|
生 HTML(<div>、<script> など) |
完全に除去。HTML は通過しません。 |
画像() |
除去。画像構文は出力から削除されます。 |
リンク([text](url)) |
URL は可視のプレーンテキストとして表示。自動リンク化なし。明示的なユーザー操作なしにはクリック不可。 |
| 危険な URL スキーム | javascript:、data:、vbscript:、file: — 除去。 |
| iframe、エンベッド、オブジェクト | 除去。 |
| HTML エンティティ | 安全な場合にのみ表示文字へデコード。 |
10.3 実装
実装は、ブロックリストではなく 厳密な許可リストパーサ を使用しなければなりません(MUST)。推奨アプローチ:
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. 第三者検証
保存済みバイト列(非公開 / wrapped の qub では K も)を持つ第三者は、qub の協力なしに暗号学的アーティファクトを検証できます。独立してタイムスタンプされた 存在 の主張には、これに加えて、検証済みの qub 単位の永久ストレージ包含、または検証済みの §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.
検証が証明するもの:
| 証明入力 | それが確立するもの |
|---|---|
| 有効なバンドル / 封印済みアーティファクト + drand 署名 | 復元された本文が body_hash と一致すること、qub_id に結合されたメタデータが完全であること、暗号文が宣言された drand ラウンドに結合されていること、そのラウンドが経過したこと。これは暗号文がいつ作成されたかを証明するものではありません。 |
| 有効な V2 作成者 / 共同署名者署名 | 対応する秘密鍵の保持者が §9.3 の署名対象を認証したこと。 |
| 独立して検証された qub 単位のストレージトランザクション | 正確な保存済み暗号文が遅くともそのブロックタイムスタンプまでに存在したこと。 |
| 有効なアンカー済み透明性ログ証明 | アンカーブロックから得られるコミット時刻の上限を含む、§16.11 のリーフ種別に応じた主張。 |
検証が証明 しない もの:
| 非証明 | 理由 |
|---|---|
| 作成者性 | sender_label は装飾的です。sig_alg ≥ 0x01 がない場合、誰でもこの内容を封印できた可能性があります。 |
| 意図 | アーティファクトが証明するのはバイト列と暗号学的関係であり、作成者が主観的に何を意味したかではありません。 |
.qub 単体からの事前コミットメント |
作成者は結合されたラウンドの経過後に有効なバンドルを組み立てられます。埋め込まれた drand 署名が証明するのはラウンドの経過であり、それ以前に暗号文が存在したことではありません。 |
| 封印ボタンを押した正確な時刻 | ストレージまたはアンカーブロックのタイムスタンプは、独立して検証可能な上限であり、ユーザーのローカル操作より遅れる場合があります。sealed_at / received_at の主張には証拠能力がありません。 |
実装済みの透明性ログ(§16)は、qub 全体にわたる改ざん明示的な 順序 と、リーフ種別ごとに限定された(§16.11)トラストレスな コミット時刻の上限(アンカーブロック時刻)へ検証を拡張します。作成者性や意図を追加するものではありません。既定のバイト盲検アップロード経路では、ログ自体が body_hash や drand_round を証明するわけでもありません。それらは引き続きアーティファクトの検査から得られます。
12. バージョンとリリースの管理
ドキュメントリリース、内側のワイヤプロトコル、外側ラッパーは、それぞれ別個のバージョン空間です。そのため、ドキュメントだけの明確化で暗黙にバイト列が変わることはなく、将来のワイヤ移行が編集上の改訂を装うこともありません。
12.1 ドキュメントリリースバージョン
本仕様はセマンティックなドキュメントリリース(MAJOR.MINOR.PATCH)と、不変の Git タグ protocol-v<release> を使用します。
- PATCH: 準拠するバイト列や要求される動作を変えない精度修正または編集上の訂正。
- MINOR: 後方互換性のある規範的追加、新しいレジストリエントリ、または独立してバージョン管理される新しいサイドカー形式。
- MAJOR: 新たに必須となるワイヤ解釈を含む、互換性のない規範的変更。
リリース状態は、ドラフト(まだ規範的ではない)、現行(唯一の推奨実装対象)、廃止(歴史的検証のため保持)のいずれかです。バージョンなしの /protocol ルートは現行リリースを表示し、リリースタグは正確なソースと、そのリリースで公開されたすべてのロケールを保存します。状態またはリリース番号を変更するには、同じレビュー済み変更内でこの表とリリース履歴を更新する必要があります。
| ドキュメントリリース | 発効日 | 状態 | ワイヤプロトコル | ラッパー | ソース |
|---|---|---|---|---|---|
| 1.0.0 | 2026-09-23 | 現行 | 0x01 |
0x01 |
protocol-v1.0.0 |
12.2 プロトコルバージョン
SealedQub と QubEnvelope の両方にある version フィールド(u8)はメジャープロトコルバージョンを識別します。
- ビューアーは不明なメジャーバージョンを明確なエラーで拒否しなければなりません(MUST)。
- 既知のメジャーバージョン内では、デコーダは不明なマップキーを拒否しなければなりません(MUST)(§3.1)。スキーマの進化は新しい
versionの導入によって行われるのであって、既存のデコーダが読み飛ばすことになるキーの追加によってではありません。(本仕様の以前の改訂では、不明なオプションフィールドの許容を認めていました。その条項は撤回されました —encode(decode(x))を非単射にし、約定ペイロードに対する隠れた署名済みコンテンツの攻撃経路を開いていたためです。) - コンテンツタイプ(
content_type)と署名スキーム(sig_alg)はバージョンゲート化されています。新しい値は、新しいプロトコルバージョンまたは明示的なレジストリ更新と共にのみ導入できます。
12.3 プロトコルバージョン履歴
| バージョン | 値 | 説明 |
|---|---|---|
| v1 | 0x01 |
非公開 / wrapped および公開 / bare の配布。テキスト(0x01)、約定(0x03)、判定(0x04)の本文。ML-DSA-65 V2 作成者 / 共同署名者署名。drand quicknet tlock。SHA3-256。 |
12.4 前方互換性
不明な CBOR マップキー(§3.2 の正規順序にないキー)を持つ QubEnvelope に遭遇した v1 ビューアーは、デコードエラーでそれを拒否しなければなりません(MUST)(§3.1)。前方互換性は、キーの許容ではなく version フィールドが担います。将来の追加は — マイナーなメタデータであっても — 新しい version 値の下で出荷され、v1 ビューアーはそれを、署名がコミットするコンテンツを黙って落とすのではなく、明確な「より新しいプロトコル」エラーで拒否します。
sig_alg = 0x01(ML-DSA-65)に遭遇したが ML-DSA-65 検証サポートを欠く v1 ビューアーは、qub 全体を拒否するのではなく、「署名は存在するが検証できません」という通知と共に qub の内容を表示することが望ましい(SHOULD)。リファレンス実装は今日、0x00 と 0x01 以外のすべての sig_alg 値を拒否します。v1 レジストリには他の有効なアルゴリズムが含まれていないためです。厳格な拒否とソフトフェイルは、第三のアルゴリズムが登録されるまで観測上は同一です。上記のソフトフェイル動作は §9.2 が新エントリを承認した時点で重要となり、リファレンスビューアーはその時点でソフトフェイルに更新されます。
12.5 外側ラッパーバージョン
§13 に記載される OuterWrapper は、SealedQub.version および QubEnvelope.version から 独立した 独自の version バイトを持ちます。2 つのバージョン空間は別個に進化します。将来の耐量子対称置換は内側のプロトコルバージョンに触れずにラッパーバイトを引き上げ、将来のプロトコル層の追加(例: 新しいエンベロープフィールド)はラッパーバイトに触れずに内側のバージョンを引き上げます。
OUTER_WRAPPER_VERSION_* |
値 | アルゴリズム | ステータス |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
12 バイトのナンス、16 バイトの認証タグ、qub_id に結合された AAD を持つ AES-256-GCM |
非公開配布で有効 |
| — | 0x02–0xFF |
予約済み | 将来 |
ビューアーは不明なラッパーバージョンを明確なエラーで拒否しなければなりません(MUST)。プロトコルは、具体的な移行ドライバ(例: 異なる AEAD を支持する NIST のガイダンス)が現れるまでラッパーバージョン空間を意図的に狭く保ちます。0x02 スロットはそのアルゴリズムを導入する同じリビジョンで割り当てられます。
13. 外側暗号ラッパー
13.1 根拠
プロトコル層(QubEnvelope → tlock → SealedQub)は、封印された qub を 時間ロック します。本文は unlock_at および drand ラウンド署名が公開されるまで読み取り不可能です。しかし解錠後、ラウンド署名は公開され、SealedQub の正規 CBOR 形状は認識可能であるため、永久ストレージのトランザクションをインデックス化したハーベスターは qub コーパス全体を一括復号できます。
非公開配布では、外側暗号ラッパーが、正規 SealedQubCbor と保存済みバイト列の間に追加の対称 AEAD 層を挿入することでそのチャネルを閉じます。ブラウザ封印経路では、256 ビットの鍵 K は配布 URL の URL フラグメントとユーザー端末に のみ 存在します。ブラウザは URL フラグメントをサーバーに送信しないため、qub.social、すべてのストレージゲートウェイ、およびそれらの前にあるすべての CDN は、観測上 K を認識しません。したがって非公開 qub の保存表現は、作成者が共有することを選んだ URL なしには平文を回復できない、不透明な暗号文です。公開配布ではこのレイヤーを意図的に省略します(§13.8)。
正味の効果:
- 非公開配布の列挙耐性。
OuterWrapperは認識可能な構造化 CBOR のままであり、文字どおりランダムなバイト列と区別できないわけではありません。しかし、その暗号文フィールドが認識可能な内側のSealedQub形状を隠します。文書化された「bare の qub 形状アップロードを GraphQL で検索し、公開 drand 署名で一括復号する」ハーベスター戦略は、K なしでは平文に到達しません。 - 既定の非公開ブラウザフローにおける暗号シュレッディング・プライバシー姿勢。 qub.social は、既定のサーバーサイドデータだけからそれらの保存済みアーティファクトを復号できません。明示的な復旧、公開配布、および信頼を要するサーバーサイド封印には、それぞれ異なる開示済みの信頼境界があります。
- 2 層の機密性階層。 既定 = リンク制御アクセス(本セクション)。受信者暗号化された非公開 qub(予約済みのフェーズ 2 機能であり、未仕様)が第 2 層として上に重なります。
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
├─ 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
プロトコル層(§7、§8)の封印と解錠はラッパー境界より下では変わりません。ラッパーは seal() の呼び出しサイトで取り付けられ、unlock() の呼び出しサイトで取り外されます。
13.3 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
}
フィールドの不変条件。
versionは v1.0 ラッパーバイトについて0x01でなければなりません(MUST)。qub_idは、アンラップ後に回復される SealedQub のqub_idフィールドと等しくなければなりません(MUST)。リファレンスのwrap_sealed_qubとunwrap_sealed_qubはどちらも内側の CBOR を解析し、この一致を直接強制します。これとは別に、AAD バインディングにより、ラップ後に外側のqub_idを改ざんすると認証に失敗します。nonceは 96 ビット(12 バイト)でなければならず(MUST)、すべてのラップ操作のために CSPRNG によって新たに生成されます。同じ鍵の下でナンスを再利用すると、平文を回復する AEAD ナンス再利用攻撃が許されます。生成者は(key、nonce)ペアをワンショットとして扱わなければなりません(MUST)。ciphertextは AES-256-GCM 出力です。暗号文バイトと 16 バイトの認証タグを連結したものです。ciphertext.len() == SealedQubCbor.len() + 16が正確に成り立ちます。
CBOR 符号化。 §3 に準拠した正規 CBOR、同じキー順序規則(符号化バイト長の昇順、次に辞書式)を使用します。4 つのキーは次のとおりです。
| キー | 符号化バイト | 順序 |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
したがって OuterWrapper CBOR の最初のバイトは 4 エントリのマップの確定長マップヘッダ(0xA4)です。
13.4 qub_id への AAD バインディング
ラッパーは qub_id を AEAD 追加認証データとしてバインドします。これは 3 種類の攻撃に対する負担を担う構造的防御です。
| 攻撃 | 防御 |
|---|---|
暗号文をラッパー内の異なる 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
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
失敗モードの集約。 不正な K、不正なナンス、AAD 不一致、改ざんされた暗号文はすべて同じ DECRYPT_FAILED エラーを生成します。これは意図的な AEAD の性質です。失敗モードを区別すると、リモート攻撃者が不正なラッパーを送信して応答時間を計ることで探れるサイドチャネルが生まれます。リファレンス実装はすべての AEAD 失敗を単一のエラー形状に集約しなければなりません(MUST)。
13.6 鍵素材と配布
ラッピング鍵 K は qub ごとに CSPRNG によって生成される 256 ビットの一様ランダム値です。リファレンス実装は次のソースから取得します。
- WASM 作成者:
getrandom(wasm_jsバックエンド配下の WebCrypto)。 - サーバー側の seal API 呼び出し元: ローカルの CSPRNG。呼び出し元は
Kをwrapper_key_b64urlとして供給し、自ら保持します。Worker はラッパーのためにKをメモリ内で使用しますが、これを永続化してはなりません(MUST NOT)。これにより、ワンショットのサーバー生成シークレットに依存する代わりに、呼び出し元が保持する権限を用いて、冪等な再試行が秘匿化された応答を回復できます。
配布: K は URL セーフ base64(RFC 4648 §5、パディングなし)として符号化されなければならず(MUST)、配信 URL のフラグメント成分として付加されます。
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
フラグメントは準拠ブラウザによっていかなるサーバーにも送信されません。完全な配信 URL(フラグメントを含む)をユーザー端末を超えて永続化する回復チャネル(サーバー側の履歴インデックス、オプトイン式のメール自動送信)は、既定の暗号シュレッディング姿勢に対する明示的なトレードオフであり、明示的なユーザー同意でゲート化されなければなりません(MUST)。
フラグメント喪失。 ユーザーが URL フラグメントを失い、回復チャネルを持たない場合、qub は読み取り不可能です。これは設計の負担を担うトレードオフであり、封印時にユーザーに開示されなければなりません(MUST)。MVP は封印時開示を明示的な「この URL を保存」コピーと、オプトインするユーザー向けの検証済みメール回復チャネルで強化します。
13.7 本セクションの範囲外
- 作成者署名(§9)は変更されません。署名は内側
QubEnvelopeの内部で計算され、アンラップ → tlock 復号 → CBOR 解析の後に回復されます。 - 受信者公開鍵暗号(予約済みの
recipient_pubkeyフィールド)は、現在の非公開なリンク能力でゲートされるラッパーモードとは異なる将来機能です。 - 現行のサーバー側約定共同署名フローは、可視性
0x01の公開 / bare なSealedQubCborを発行します。最終的な封印がサーバーを介した共同署名後に行われるため、ブラウザ専用の K 秘匿モデルは満たせません。将来の非公開約定生成者は、内側のコンテンツタイプを認識しない同じラッパーを利用できます。
13.8 公開 qub(ラッパーの省略)
外側ラッパーは 配信層において任意 です。作成者は qub を 公開 として封印でき、その場合、正規 SealedQubCbor は OuterWrapper 層も鍵 K もなしに 直接 ストレージパイプラインへ入ります。
SealedQubCbor bytes ──(public)──▶ stored as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored
公開 qub は 時間ロックされますが、リンクでゲート化はされません。その drand ラウンドが公開されるまで読み取り不可能なまま(tlock 層は変わりません)ですが、解錠後はストレージトランザクション ID を持つ誰もが復号できます。K が存在しないため、URL フラグメントは不要です。これは、サーバーが駆動しなければならない面のために意図的に選ばれたトレードオフです。公開通知メール、フラグメントなしの oEmbed / 自動埋め込みリンク、より充実した公開後 SEO はいずれも、サーバーが決して保持しないシークレットなしに機能するリンクを必要とします(§13.6)。非公開 qub でも、公開者がフラグメントを含む完全なケイパビリティーを提供する場合は、明示的な <qub-embed src="full_delivery_url"> 形式を使用できます。
生成者が考慮しなければならない帰結:
- 列挙耐性なし。 公開 qub は構造上、§13.1 の列挙耐性プロパティを放棄します。リファレンスアップロードサービスはそれら(およびそれらのみ)に
Visibility: publicの永久ストレージタグを刻印するため、意図的に発見可能です。非公開 qub はそのようなタグを持たず、バイト識別不可能性を保ちます。 - 封印時に平文タイトルが露出。 §3.2 の
titleフィールドはSealedQubCbor内では平文です。ラッパー下では、ビューアーがKを供給するまで隠されています。ラッパーがなければ、解錠前の アップロードの瞬間から 永久ストレージ上で誰でも読み取れます。準拠する作成者アプリは封印時にこれを開示しなければなりません(MUST)。 - 検出は構造的で、交差検査されます。 準拠するビューアー / 埋め込みは、解析によって 2 つの保存形状を区別します。
OuterWrapperとして解析されるバイト列はKによるアンラップ経路を取り、bare のSealedQubCborとして解析されるバイト列は直接受理されます。復元した内側の値は一致しなければなりません(MUST)(wrapped / private は0x00、bare / public は0x01)。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 = 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
実装は、この入力に対して同一の body_hash および qub_id 値を生成しなければなりません(MUST)。このテストベクトルは最初に書く単体テストとすることが望ましい(SHOULD)。上記の正規値はリファレンス実装によって計算されており、ビット単位で一致しなければなりません(MUST)。過去のローンチ前プロトタイプのレイアウト(最初の 2 つに依存する稼働中の qub はありません)では、outcome_at を追加する前に 92 バイト(3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0)、outcome_at_or_zero を追加した後に 100 バイト(b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed)を使用しました。現行の 108 バイトレイアウトでは、その後 drand_round と QUB_ID_V2 ドメインセパレータを追加しました。初期の 108 バイトベクトルは旧 ceil ラウンドマッピング(drand_round = 4695445)を用い、3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4 を生成しました。これはそのラウンド入力に対して引き続き有効な qub_id ですが、上の例は §4.3 の現行ラウンドマッピングに従います。
14.2 公開ラウンドのマッピング
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
ラウンド 4675286 は 1595431050 + (4675286 - 1) * 30 = 1735689600、すなわち unlock_at ちょうどに公開され、それより前には公開されません。(リリース前の旧 ceil マッピングでは 4675285 となり、1735689570、つまり 30 秒早く公開されました。検証者は §4.3 に従ってこの旧ラウンドを受け入れます。)
14.3 正規 CBOR ラウンドトリップ
実装は、すべての有効入力について serialize(parse(serialize(qub))) == serialize(qub) を検証しなければなりません(MUST)。これは単一のベクトルではなくプロパティテストです。
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 を生成しなければなりません(MUST)。
実装はまた、すべての有効な PactTerms 入力について serialize(parse(serialize(pact))) == serialize(pact) を検証しなければなりません(MUST)(プロパティテスト)。
14.5 外側ラッパーのクロス言語ベクトル
外側ラッパー(§13)は crates/qub-core/tests/vectors/wrapper_v1.json に別の正規フィクスチャを持ちます。各ケースは (key, nonce, qub_id, sealed_cbor) のタプルを不透明な 16 進入力として固定し、特定の 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)。
このフィクスチャは現在 3 つの低レベルなラッパーケースを固定しています。これらは §13.8 の配布形状不変条件とは独立に、決定論的な OuterWrapper 符号化と AEAD の相互運用性を検査します。特に、歴史的な名前 basic-text-public とその内側の visibility = 0x01 によって、結果のラップ済みバイト列が準拠する公開配布になるわけでは ありません。生成者は、公開の内側バイト列を引き続き bare で保存し、非公開(0x00)の内側バイト列だけをラップしなければなりません(MUST)。
| ケース | カバレッジ |
|---|---|
basic-text-public |
歴史的な低レベルフィクスチャ名。任意フィールドを持たない、最小の現実的な SealedQub 形状です。ラッパーバイト列だけを検査するものであり、§13.8 に準拠する保存済み配布ではありません。 |
with-recipient-pubkey |
recipient_pubkey が設定された SealedQub(予約済みの将来経路)。異なる内側 CBOR キーセットを行使します。フィクスチャの内容自体が異なるため、独立に異なる qub_id を生成します(recipient_pubkey 自体は §4.1 のプリイメージに含まれません)。 |
longer-body |
約 4 KiB の本文 — 内側エンベロープと外側暗号文の両方の中で複数バイトの CBOR 長さプレフィックスを行使します。 |
実装は記録された入力に対してバイト単位で同一の expected_wrapper_hex を生成しなければなりません(MUST)。フィクスチャの再生成には QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors が必要であり、意図的な形式変更のために予約されています。
15. 暗号プロファイル統治(将来)
このセクションは v1 については情報提供であり、qub の暗号プリミティブのいずれかに 2 つ目のアルゴリズムが入った最初の時点で規範となります。
15.1 現在の姿勢
プロトコル v1 はプリミティブごとに正確に 1 つのアルゴリズムをバインドします。
- 署名: ML-DSA-65(
sig_alg = 0x01、1952 バイトの公開鍵、3309 バイトの署名)および署名なし(sig_alg = 0x00)。コードベースは0x02を Ed25519 用に予約していますが、プロトコル v1 では有効化しません。v1 検証者は{0x00, 0x01}以外のすべてのsig_algを拒否しなければなりません(MUST)。 - タイムロック: drand quicknet のみ — チェーンハッシュ、公開鍵、ジェネシス時刻、および周期は固定のネットワークパラメータであり、リファレンスの
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs)およびconfig/drand-endpoints.jsonによって保持されます。 - 外側ラッパー: AES-256-GCM v1 のみ(§13)。
検証者は現在、有効なプリミティブごとに鍵と署名の長さをハードコーディングしています。sig_alg とラッパーバージョンのバイトは明示的なセレクタですが、v1 は帯域内交渉を行わず、上記の有効な値だけを認めます。
15.2 意図された形状
2 つ目のアルゴリズムがプロトコルに入るとき、検証者は名前付きの CryptoProfile(例: ExqubV1)向けに構成され、プリミティブごとに許可された値の正確なセット(sig_algs、drand チェーン、ラッパーバージョン、コンテンツタイプ)が列挙されます。プロファイルは検証時に固定され、帯域内で交渉されることはありません。アクティブプロファイル外の値はすべて拒否されます。
これにより、ML-DSA-87 の追加や Ed25519 の有効化が既存の検証者構成を遡及的に弱体化できないことが保証されます。v1 検証者は、v2 プロファイルが公開された後も v1 検証者のままです。
15.3 トリガー条件
次のいずれかが提案されたときに §15 を規範ステータスに昇格させます。
- 2 つ目の
sig_algバイト(Ed25519 の有効化、ML-DSA-87、または §9 レジストリへの新エントリ)。 - 本番使用での 2 つ目の drand チェーン。
- 2 つ目の外側ラッパーバージョン。
- 透明性ログの信頼ルート、すなわち
LogProfile.anchor_ownerアドレスまたは固定されたレシート鍵公開鍵のローテーション(§16.6)。LogProfileは統治されるプリミティブとして §15.2 のプロファイル面に加わります。ローテーションは検証者更新で配布される署名済みLogProfileの引き上げです(計画されたローテーションは送出側から着信側へ交差署名し、侵害によるローテーションではそれができないため、この引き上げと直前アンカーのフォーク検査により移行中の被害を限定します)。透明性ログのバージョン空間(LOG_VERSION、ANCHOR_FORMAT)は、§12.5 のラッパーバージョンがプロトコルバージョンから独立しているのと同様に、独立した兄弟として進化します。
それまで §15 は、将来の PR が交渉面を一から再議論するのではなく既知のターゲットに対して着地するように移行形状を固定するプレースホルダーです。
16。透明ログと耐久性ティア(実装済み — レビュー完了)
ステータス。 このセクションは実装されています(W5/UP-B1、ステージ1–8)で、生産者およびトラストルートの範囲はここに記載されています。ワイヤーフォーマット、ハッシュ化、検証パスは稼働しています:コアとなるMerkle + Canonical-CBOR型(
qub-core)、TypeScriptミラー+ANS-104バンドラー(workers/api/src/crypto/)、シングルライターLogDO+座標キー付きR2ノードストア、/uploadログアペンド試み、デイリーアンカー+バンドラードレインクロン、GET /api/v1/qub/:tx_id/proof(包含)およびGET /api/v1/log/consistency(RFC 9162)証明エンドポイント、.qubバンドルに搭載された型付き包含証明(§17.5)。 ネイティブのANS-104アンカー検証器(tools/qub-verify)、およびデュアルセルフパブリッシュヘッドフック(§16.6)。成功した/uploadは常にR2持続的ですが、LOG_DOが設定され、インラインの付加が成功した場合にのみログカバーされます。その時のみ、その応答はlog_seq、receipt、anchor_statusを保持します。RECEIPT_SKが欠如または無効の場合、そのレシートのsig_b64urlは空となり、否認防止は提供されません。現在の/sealおよびパクトの公開パスは個々のArweaveトランザクションをスケジュールしていますが、ログリーフは付加しません。現在、追加失敗後に/uploadコメントの提案された後での照合を実行するコードはありません。W5の外部レビューは完了しました:§16.15は設計決定とローンチ制約を記録しますが、これらの制約は先述のプロデューサーカバレッジを拡大しません。3つの信頼/デプロイメント項目がゲートされたまま:(a) 専用アンカーウォレット(ANCHOR_JWK;LogProfile.anchor_ownerはまだ[0xAB; 32]プレースホルダーです);(b) レシート署名鍵および対応する公開鍵ピン(RECEIPT_SKは任意で現在空LogProfile.receipt_pubkey);および(c) 自己公開のGitHubリポジトリ+トークン(§16.6)。アンカー/プロファイルピンがプロビジョニングされるまでは、スタンドアロン検証者が完全に固定された検証を主張するのではなく、正直に証明状況を報告します。設計は厳密に加算的であり、SealedQub/QubEnvelope線のフォーマットに変更はありません。
16.1 理由と耐久性ティア
現在の出版経路は、確認応答とArweave確認を切り離しています。個別のトランザクションを導き出して署名し、アーティファクトと正確な提出状態をR2で永続化し、非同期でポストします。透過ログは、LogDOが成功した一般的な/uploadリクエストの部分集合に対して独立してアンカーされた順序付けレイヤーを追加します。
| 階級 | 名前 | 保証 | いつ |
|---|---|---|---|
| T1 | R2-最初の同期ACK | 耐久性の下限 — 成功が返される前に、シールドされたバイトと正確な公開状態が耐久性のあるストレージに書き込まれます。 | 現在の公開パス全体で実装されています。 |
| T2 | バッチ化された透明性ログの包含 | 追加専用、不正改ざん検知可能なコミットメント + 一度含まれて固定された後の総順序付け。 | 現在のプロデューサー:成功した LogDO が /upload から追加を行う; レスポンスにはレシートタプルが含まれる。普遍的ではない。 |
| T3 | qubごとのArweaveの永続性 | qub用の個別のArweaveトランザクション。 | 現在、受理されたすべての公開物に対して準備されており、非同期で投稿されます。正確な署名済みトランザクションは配信されるまでドレイン可能なアウトボックスに保持されます。 |
階層は明確な証拠特性や耐久性特性を記述しており、現在の商業計画ではありません。現行コードは依然として、承認された出版物ごとに個別のArweaveトランザクションをスケジュールしています。T3を有料アップセルとしてのみ露出させるわけではありません。APIキー/アカウントのクォータ上限は引き続き別個のアプリケーション管理です。
耐久性の誠実さ。 T1書き込みは同期的であるため、成功した応答はArweaveゲートウェイを待たずにアプリケーションレベルの耐久性を確立します。独立したタイムスタンプを確立するわけではありません。確認された個別トランザクションはブロックタイムの上限を提供します。完全なT2受信タプルを含む応答の場合、次に確認されたアンカーが以下で述べるログ証明を提供できます。タプルが欠如している場合、どの表面もこのqubが透過ログにすでに含まれていることを示すことはできません。アンカーおよびパブリケーションレイテンシにはプロトコルレベルの数値SLAはありません。
16.2 ログリーフ構造(2つのコミットされた形状)
ログエントリは手書きの標準的なCBORとしてLogLeafであり、§3.1プロファイル(定長、タグなし、浮動小数点なし、最短形整数、NFCテキスト、省略時のオプションフィールド、符号化されたバイト長の順に順に並べられたキー)でエンコードされています。§3.1 parse → re-encode → compareの標準ガードは、ハッシュ化前のエンコードパスに適用されます(デコード時だけでなく)ため、整数幅やキー順序の違いによって葉のバイトに関して2つの実装が意見の合うことはありません。すべての整数はu8 / u64 / i64です。すべてのダイジェストは32バイトバイト文字列(bstr[32])です。保存されたArweaveトランザクションIDは、生の32バイトのSHA-256ダイジェストで、bstr[32]として運ばれます。**決して*base64urlのテキスト文字列(§3.3に一致)として扱われます。
リーフは2つの形状を1 kind 6バイトで選択します。これは一般的なアップロードパスがバイトブラインドであるためです。POST /api/v1/uploadは両方の受理されたペイロード形状を意図的に不透明として扱い、qub_idとunlock_atのみを信頼できないクライアントの主張として受け取ります。デフォルトのプライベートパスでは、body_hash、drand_round、created_at、drand_chain_versionも§13の外側ラッパー内に隠されており、ワーカーはそのキーを保持しません。型システムはまた、body_hash / drand_round自身を導き出すプロデューサーの証明された形状も定義しています。現在の/sealルートはこれらの値を持ちますがLogDOを呼び出さないため、プロダクションは現在、成功した一般アップロードアペンドからアサートされた(0x02)リーブのみを出力します。スプリットは、認証されたプロデューサーが有線化されているふりをせずに、すべてのコミットされた値を正直に保つために使います:
| キー | エンコーディング長 | 種類 | 存在 | 意味 |
|---|---|---|---|---|
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) — 作業者が両方の経路で常に正直に計算できる唯一のコンテンツ結びつき。 |
unlock_at |
10 | i64 |
必須 | コピー済み(証明済み)または主張済み(主張された);葉に入る前に> 0を検証済み。 |
received_at |
12 | i64 |
必須 | R2-ackでのワーカーの実時計。証拠にはならない(オペレーターが主張; §16.6)。自己記述のために存在し、決して証明にはならない。> 0 が検証済み。 |
body_hash |
10 | bstr[32] |
kind=0x01 のみ |
0x02 では省略 — 労働者は §13 に基づきそれを欠く。 |
drand_round |
12 | u64 |
kind=0x01 のみ |
0x02 では省略されました。 |
kind=0x02リーフは意図的にbody_hashもdrand_roundもコミットしません。内容アドレスchashでの不透明暗号文のコミットメントと順序付けを証明し、qub_idとunlock_atを主張します*を証明します。平文やラウンドではありません。アサートされたqubの平文/ラウンドレッグはログ(§16.11)ではなく、既存の§11の.qub束検証から来ています。drand_chain_versionリーフには含まれていません(デフォルトパスのラッパー内にあります)。チェーンの粒度はアンカー上にあります(§16.7)。エンコーダーの規律:全ゼロのrefまたはchashを拒否し、非正のunlock_at/received_atを拒否し、cbor.rsのセンチネルガードを反映するようなものoutcome_at > 0。
16.2.1 プライベート・クーブ・ブラインド
ログは、§13の外側ラッパーが存在する列挙オラクル(§13.1)になってはなりません。プライベート(ラップされた)qubの場合、assertedリーフはブラインド識別子SHA3-256(qub_id ‖ log_blind_secret)をコミットします。ここで、log_blind_secretはサーバー保有の秘密であり、body_hashは省略されます。第三者はそのようなリーフを特定のqub_idに結びつけることはできません。qubの保持者(配信URLを持ち、したがってqub_id)は、ブラインドを再計算して自分の包含を確認できます。公開のqub(すでに列挙可能で、§13.8でVisibility: public Arweaveタグをすでに保持している)は生の qub_id をコミットします。ここで、単独検証可能性が意図的に負荷を支えるプライバシー不変に譲るのはこの点だけです。プライベート qubs の単独タイはchash(§16.9)です。
log_blind_secret custody(解決済み — §16.15 Q4)。 ブラインドはリーフのリンク不可性を保護し、平文の機密性(§13のラッパーが独立して保持)を守りません。log_blind_secretの妥協では、敵対者が既に保持または再構築可能な任意のqub_id(すべてのバンドル/URLに加え、低エントロピーやパブリックqub_id)に対して、1ハッシュでリーフrefを再計算しリンクします。これは既知の集団の直接的なリンクであり、未知空間でのブルートフォースではありません。log_blind_secretを他のサーバー秘密と同じ管理階層の相関/シビル級秘密として分類し、順方向に回転させるだけです(回転は将来のリーブを再ブラインドしますが、すでにアンカーされているリーブは遡ってリンク解除できません)。
16.3 リーフおよびノードハッシュ
RFC 6962 §2.1 ドメイン分離ハッシュ(SHA-256を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
ドメインプレフィックスバイト0x02(エントリチェーン、§16.4)および0x03(STHハッシュ、§16.6)はこれらから保留され、互いに素いています。これらは単一バイトであるため、既存の10バイトASCIIドメインセパレータ(QUB_ID_V2など)と衝突しません。この木はRFC 6962のleft-fullアンバランス木(各内部分割は部分木の葉の割数より厳密に小さい2の乗で分割)で、包含証明と整合性証明が1つの監査パスアルゴリズムを共有します。リファレンス仕様は明示的な左右導出擬似コードを搭載し、非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")
公表されている付録のみの権威は、累積メルクル根+そのアンカー(§16.5–16.6)であり、演算子が葉に送る生の順序は決して含まれません。鎖は任意の順序に対して再計算するため、固定された根のみが正準位置に固定されます。
16.5 累積マークルツリーとバッチング
すべての葉にseq順に1つずつ成長し続けるRFC 6962木**が存在する — 単独のバッチごとの木ではありません。(キャリーリーフチェーンのバッチごとの構成は却下されました。これは真の接頭辞関係ではないため、「整合性証明」は健全ではありません。)累積木は真のRFC 9162整合性証明を提供し、単一の最近のアンカーで古いqubの包含を証明できます。
LogDO持続オブジェクトは単一書き込み者(blockConcurrencyWhile、ミラーリングQuotaDO/EntitlementDO)です。共有ログへの追加は共有状態上で読み取り・修正・書き込みを行うため、絶対にDOを通過し、KVを通ってはいけません。これは木の右端フロンティア(O(log n)ハッシュ)をキャッシュするため、バッチを閉じるのがO(batch)です。バッチは、一連のリーブをアンカーしたものです。その実装されたトリガーは、少なくともLOG_BATCH_MAX_LEAVES(デフォルト4096)、アンカーのケーデンスに達する年齢、または明示的な管理者/クローズの強制閉鎖の1 tree_size 3進みです。root_iは累積のMerkle Tree Hash over leaves 0 .. tree_size_iです。
16.6 Arweave Anchor経由Tree Head署名
Arweaveのアンカートランザクションは、署名されたツリーヘッドであり、ツリーヘッド自体の演算子署名を置き換えます。デイリーアンカーはQubキーを必要としません。なぜならArweaveのトランザクションownerが署名だからです。堀の仮説は成り立つ — 不変の基底はqub保持の秘密ではなく、アンカーされた根に対して荷重を支えるものです。
ログ設計では、1つのウォーム成功付加受領キー(§16.10)をLogProfileにピン留めし、anchor_ownerによって相互署名されることを求めています。現在の実装では、その信頼ルートプロビジョニングは未完成で、RECEIPT_SKは任意で、キーが欠如または無効になるとsig_b64url: ""が得られ、コンパイルされたLogProfile.receipt_pubkeyは空です。このようなレシートは付録リーフを記述できますが、否定不能な署名レシートではありません。より強力な設計クレームは、検証者リリースが対応する公開鍵をピン留めし、アンカー所有者がクロスサインした後にのみ適用されます。完全なレシーバータプルのない出版応答はログ受諾の主張をしません。署名が空のものは付加位置の主張はしますが、署名検証の主張はありません。
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()にあるクイックネット定数とともに、検証バイナリと共に配布されます。検証者はまた、Arweaveのトランザクションデータ→ tx_idローカルでバインディングされているも検証しなければなりません。ゲートウェイ/raw/応答を信頼してはいけません。これにより、ローグウォレットの曖昧さの穴が閉じます。「Arweaveにアンカーされている」は、検証者がどのウォレットをピン留めするまで意味を持ちません。
ローテーションは§15のガバナンス拡張であり、再利用ではありません(解決済み — §16.15 Q3) §15.2のプロファイル表面は現在、sig_algs / drand chain/ラッパーバージョン/content typeのみを列挙しており、§15.3のトリガーにはこれらが記載されていません — LogProfile / anchor_ownerはまだ§15の表面には**ありません*。したがってローテーションガバナンスは構築されなければなりません:§15.3は(以下)拡張されてLogProfileトリガーが追加され、ローテーションは検証ツールの更新で送信される署名LogProfileバンプです。計画されたローテーションは送信→入力のクロスシグネチャを伴います。妥協主導の回転はできず(送信鍵は正確には信頼不可または利用不可)、§15で制御されるバンプに戻り、その間の前アンカーフォークチェック(下記)はバウンドダメージとなります。
二重解釈ウィンドウ(第一級信頼パラメータ)。リーフは、そのカバリングアンカーがArweaveで確認されて初めて二重解釈耐性を持ちます。ウィンドウは received_at → anchor confirmation(ケイデンス+Arweave最終確定性、プロトコルの遅延保証なし)です。トラストルートのプロビジョニング前に、現在の実装は qub の運用整合性および存在する未署名の追加メタデータを提供しますが、計画されている否認防止保証は提供しません。完成設計を定義する三つのアカウンタビリティアーティファクトがあります(ウィットネスモデルは §16.15 Q2 の決議に従います):
- 封印レシート(プロビジョニング依存) — アップロードのログ追加が成功した際に返されるSCTの類似形式(§16.10)。無効化不可となるのは
sig_b64urlが空でない場合のみです。かつマッチする公開鍵/アンカー-所有者関係が検証器にピン留めされている場合のみ。現在空のプロダクションプロファイルピンではその判定を支持できません。この制御は、省略されたレシートタプルや署名のないレシートには適用されません。 - 公開されたモニター手法 + 前鎖のwalk — チェーン
prevアンカーはhead→genesisで歩きます;フォーク(異なるrootを持つ2つのアンカーsize、または壊れたprev)は不正行為の公開可能な証拠です。曖昧な検出は、静かな前提ではなく、明示された運用上のコミットメントです。 - デュアルセルフパブリッシュヘッド — 新しいヘッド
{sth_hash, tree_size}は専用のQB所有の公開、追加専用のGitHubリポジトリ(荷重を支える改ざん防止の自己公開の脚)に投稿され、ソーシャル投稿は最善の検証としてのみ行われます。投稿失敗はMUST PAGE(失敗サイレントではありません)。アンカーcron(workers/api/src/utils/heads-publish.ts)のpublishHeadフックとして実装(ステージ8):shaなしの内容APIへのPUTは付録のみ(422はヘッドがすでに公開されていることを意味し、上書きはされません);PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}ではオプトイン/デプロイゲートされ、リポジトリがプロビジョニングされるまでは無効化されます。GitHubの失敗がhealth_alertチャネル経由でページ化され、持続的な失敗メトリクス(m:tlog_publish_head_fail)が上がります。Arweaveアンカー自体は公開失敗時にロールバックしません。「失敗サイレントしない」は、その持続性指標によって保証されます。これは運営者が必ずダッシュボードでアラートしなければなりません。たとえ最善のメールページが届かなくてもです。「アンカーオンアドバンス」からは2つの正直な制限があります(クロンはサイズが大きくなるまで公開しません):一時的なGitHub障害は、そのサイズで公開ヘッドのシーケンスにギャップを残します。これは静かではなく、境界付き(ページ化)でなく、各ヘッドがスーパーセットツリーをコミットするため、§16.9の整合性証明がそのギャップを埋めます。重要なのは、その整合性証明は権威あるArweaveアンカーツリーから計算され、GitHubの表面からではないなので、GitHubギャップが検証可能性を弱めることはありません。出版された頭の空白を埋める追いつきの補填は、延期強化です。
正直さの制約(拘束条件)。 qubが両方の予定された投稿面を制御しているため、これは自己公開であり、独立した目撃証言はありません。製品、マーケティング、または法的な面でログが「独立して目撃された」と主張することはできません。受領/プロフィール/ヘッドゲートが提供されると、許可される主張は、曖昧さが検出可能であり、署名が成功した追加記録が否認できない受領証を残すということです。それまでは、その主張は利用できません。真の独立した第三者の証人は、将来の§15ガバナンスの更新に延期されます。
received_atはオペレーターによって主張されており、それに依存した主張はできません — 製品/法的/API/証明生成のどの面でも証拠や紛争補強として公開されることはありません。Arweaveアンカーブロック時刻Tが唯一の信頼不要なタイムスタンプ(「記録された時」の上限)です。received_atの監視サニティチェックは、オペレーター制御のanchored_at STHフィールドではなくTと比較する必要があります。このチェックは、正直なオペレーターの時計バグに対する防御であり、悪意のあるオペレーターに対する責任管理ではありません(§16.15 Q5)。
16.7 アンカートランザクションの形式と間隔
AnchorBundleは、§16.8バンドラーを通して書かれた正準CBOR Arweaveトランザクション本体であり、ver:u8、sth:bstr(正準SignedTreeHeadバイト)、prev_anchor:bstr(以前のアンカーtx idの生バイト; ジェネシス時は省略)、chain_hash:tstr(有効なdrandチェーン — quicknet)、およびバッチのleaf-CBORストリームをseq順に含み、アンカーが自己完結するようにしています。監視者は本体からrootを、qub依存ゼロで再導出可能です。(大量のleafストリームが大きくなる場合、将来的な改訂で参照によるleaf範囲のみをコミットする可能性があります。v1では採用せず、指摘のみ。)
Arweaveタグは意図的に列挙可能です — ログは見つけられることを意図しており、プライベート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本体が唯一の権威です。
Cadence: デフォルトで毎日、ボリュームで再確認されます(サイズトリガーは負荷時に有効なケイデンスを自動短縮します)。現在のプロデューサーは、有料シールの強制アンカーフックを実装していません。アンカーウォレットは専用かつ低速度で、アップロードウォレットとは別物です — 必ず独自のJWK**(独立した鍵で、アップロードウォレットの論理的な役割ではありません)でなければなりません。したがって、アップロードウォレットの侵害はアンカーを偽造できません — 1日あたりのアンカー取引予算が厳格です。カストディの姿勢は明確に示されています:狭い範囲のホットキーで、厳密なサーキットブレーカーと低い残高であり、「コールド」ではない — 毎日自動署名するウォレットはコールドであってはならず、仕様もそれを否定しません。
16.8 ANS-104 バンブラー
社内製のANS-104 DataItemエンコーダーおよびディープハッシュ署名器、約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
署名はArweave deepHashで行われます。これは再帰的なSHA-384ダイジェスト(Arweaveのワイヤー要件、crypto.subtle.digest("SHA-384"))で、["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data]上からRSA-PSSでウォレットJW crypto.subtle Kを使ったディープハッシュで行われます。id = base64url(SHA-256(signature))。ここでのSHA-384は、Arweaveワイヤーのみのプリミティブとして隔離されており、qubトラストプリミティブにはなりません(§15はフェンスを記録し、qubトラストハッシュは全体的にSHA3-256です)。
ANS-104のコードパスは、ディファード・フォールバック/ドレイン機構を担当し、AnchorBundle DataItemsを書き込みます。通常のパブリケーションパスは、まず正確に署名されたArweaveトランザクションを作成し、そのJSONを耐久的なアウトボックスに保持します。ダイレクトポストはレイテンシ最適化であり、ドレインパスは同じトランザクションを再試行してからバンドラーのフォールバックを適用します。**署名スキーム(解決済み — §16.15 Q8):v1署名はRSA-PSS(署名タイプ1)**で既存のArweaveウォレットJWKメカニズムを再利用(新規の長期キー保管はゼロ、「秘密が1つ少ない」という仮説に役立っています);Ed25519は§15のPQ移行パスに延期されます。
手作業でロールされたディープハッシュはW5で最もリスクが高く、自然カバレッジも最も低いコードであるため、そのゲーティングは交渉不可です(§16.15 Q8):
- 言語間フィクスチャー
tlog_v1.json(Rust + TS、§14.5wrapper_v1.jsonパターン)は、ディープハッシュ、DataItemバイト+id、リーフハッシュ、5葉ルート+監査パス、STHハッシュ、包含証明、整合証明をカバーします。これらは符号と検証の両方の方向で行われます(検証方向は重要です。なぜなら、§16.6のローカルTX→ tx_idチェックはディープハッシュをすべてのスタンドアロン検証器に引き込むためであり、書き込み者だけでなく)。 - 参照ANS-104束帯を通じた一度限りの相互運用往復(**静的テストデータのみとして消費され、npmランタイム依存関係は一切ありません)(Web-Cryptoのみ/インストールスクリプトなしの姿勢が維持)。
- ディープハッシュ+RSA-PSSパスは、プロダクションで使用される同じ
crypto.subtleプリミティブを往復で通過しなければならず、社内エンコーダはバイト互換性があります。 - 継続的なバンドル後受理モニターは、各アンカー/フォールバックDataItemが実際にArweave受理を達成していることを確認し、アラーム+回路ブレーカーを付与します。これは、ディープハッシュがArweaveの不可用フォールバックキューにもサービスを提供するため、サイレントリグレッションはその障害をカバーする正確な期間中にネットワーク拒否されたアイテムでそのキューを埋めることになります。
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ノードストアである場合のみ、ノードごbatchのデルタではありません。座標キー付きストアでは、任意の(leaf i, size N)監査パスはO(log N)直接R2 GETの集合であり、バッチ境界を越えた再計算なしです。バッチキードストアではそうではなく、この解像度が埋めるストレージレイアウトのギャップとなります。葉の本体も同様に内容アドレス指定が可能ですseq。W5テストベクターは、LogDOストレージを消去したR2 + Arweaveのみを使用し**創世時代のコールドリーフをはるかに後の根に対して証明しなければなりません。したがって、§16.13のリクレイメーション安全性主張は主張されるのではなく支持されています。O(log N)連続R2 GETは非同期証明エンドポイントにのみ属し、シールホットパス(§16.10)やピーターティッククロンには属しません。
16.10 R2-First Ack 順序付け
実装されたPOST /api/v1/upload列は以下の通りです:
- フロントハーフゲート(認証、検証、冪等性シャードキー)— 変更なし。
- Arweaveの個別トランザクションを正確に作成、タグ付け、署名します。これはローカルで導出
tx_id、トランザクション作成はゲートウェイから報酬やアンカーメタデータを取得することがあります。準備失敗でも、確認応答前にリクエストは失敗します。 - 同期的に選択されたアーティファクトを
qub-cache/<tx_id>に書き込み、安定した作成操作/アウトボックスレコードを永続化します。これらは耐久性および再試行のフロアです;決済が戻る前の失敗は503。 LOG_DOが設定されたら、同期的に試みLogDO.append(leaf)。単一のライターがseqを割り当て、エントリチェーンを拡張し、フロンティアを更新します。Append RPCはそれだけを行います。バッチクローズはアラーム上でオフパスで実行されます。Appendトランスポート/アプリケーションの失敗は現在fail-softです。応答はlog_seq、receipt、anchor_statusなくても成功可能です。実装コメントにもかかわらず、現在では自動的な後でログ照合はされていません。- 確認応答を返す。付録が完全な成功したタプルを返した場合にのみ
{ log_seq, anchor_status: "pending", receipt }を含める。receipt.sig_b64urlは受領署名者が不在の時に空である;クライアントはその値を署名済みまたは否定不能と呼んではならない。タプルが欠けている場合は、耐久性のある出版のみを意味し、透過ログの受け入れにはならない。 - 1つの遅延タスクを使って、正確な署名済みトランザクションを投稿します。成功するとアウトボックスが削除されます。失敗すると、すでに確認済みの
tx_idドレインクロンに割り当てられ、すでに確認済みの取引を変更してはなりません。仮メタデータやその他のベストエフォートサイドカーも遅延されます。
遅延境界。 リクエストパスにはフロントハーフ権限/クォータ作業、トランザクション準備/署名、耐久的なR2書き込み、そして(設定時)LogDO試行が含まれます。< 300 msは設計レビューで運用目標として現れ、プロトコルの保証ではありません。現在のトランザクション準備ステップではゲートウェイメタデータ要求を行うことがあります。レイテンシアラームやローンチゲートは運用上の制御であり、検証者が利用できる証拠ではありません。
16.11 信頼モデル — 葉の種類別に限定された正確な主張
kind=0x01(証明済み): 「この内容—ボディマッチングbody_hash、qub_idで識別—はQubの付録専用ログの位置seqにコミットされ、Arweaveブロック時間Tまで存在していました;R = unlock_round(unlock_at)ラウンドドランドまで暗号的に読み取れませんでした。」 これは完全な{tlock round binding + Merkle inclusion + anchored root}トリプルです。
kind=0x02(主張され、デフォルト): 「内容アドレスchashを持つ不透明暗号文が、qub_idとunlock_atを主張し、位置seqの付録のみログにコミットされ、Arweaveブロック時間Tまでに存在した。」 ラウンドおよび本体の脚は既存の§11 .qub束検証(qub_core::unlock)によって提供され、ログでは提供されていない。ログが量子単位の単純なトランザクションに対して追加するのは、改ざん防止順序、信頼不要の上限コミット時間、そして曖昧さ抵抗である。
いずれの主張も、§11に基づき、sig_alg ≥ 0x01なしの著者権、意図、サブアンカーのタイミングを除外しています。いずれもいかなる主張も、いかなる主張もreceived_atに依存させません。
請求上限(拘束力のあるローンチ制約 — §16.15 Q1で解決)。 主張された(kind=0x02)リーフの場合、上記の範囲付きの請求は、いかなる製品、マーケティング、用語、または証明表示面が主張しうる上限です。いかなる表面も、ログがバイトブラインドアップロードの内容やアンロックラウンドを証明していると明言または示唆してはなりません。ログは順序付け+不透明暗号文の信頼なし上界コミットメント時間を証明します。内容およびラウンド証明は、ログに依存しない既存の§11 .qubバンドル検証のみから得られます。成功した付録や受受が認められない出版物は、ログクレームを一切持たない。
16.12 バージョン管理とW3の調整
SealedQubワイヤーバンプは存在せず、したがってプロトコルバージョンバンプもありません(§12.2):ログは既存のフィールドやバイトにコミットするサイドカーであり、§12.3プロトコルバージョン履歴には入りません。W3のオプションdrand_chain_versionはそのままで、唯一のオプションSealedQubフィールドのままです。ログは代わりに独自の独立したバージョン空間(LOG_VERSION_1、ANCHOR_FORMAT_1、InclusionProof.ver)を導入し、§12.5のラッパーとバージョンの独立性を反映しています(ラッパーはプロトコルバージョンとは独立したバージョンバイトを持ち、ログバージョンも同じ区切りを踏みます)。
プルーフの配信はデフォルトで取得され、オプションで同行します。 証明は封印時に存在できません(アンカーがまだ書かれていないため)、封印時間.qubバンドルは証明フリーのままです。W7の検証ツールは一度だけGET …/proofを取得するか、完全オフラインモードではArweaveクエリで公開AnchorBundleから証明を再構築しますLog-Id。.qubバンドル(W7)はオプションのinclusion_proofメンバーを予約しており、シール時に欠如し、アンカー後の再エクスポートでコールドアーカイブ用に埋められます。これはW3のdrand_chain_versionと同様に「オプションでデフォルトで省略、加算」パターンに従います。
16.13 保持
LogDOのオープンテイル、R2プルーフサービング基板、アンカー回路ブレーカーカウンター、バンブラーのフォールバックキューの保持ウィンドウは、docs/DATA-RETENTION.mdで規定されています。原則:ログのホットパーエントリストレージ(LogDO)はアンカー後に回収可能である。その監査資料—座標キー付き(level, index)マークノードストア+seqアドレスリーフボディ(§16.9)+Arweaveアンカー—は恒久的である。DOからコールドリーフを回収しても、発行された証明が無効になることはない。なぜなら証明はDOではなく、その永続的なR2ノードストアとArweaveアンカーに対して解決されるからであり(§16.9のワイプDOテストベクトルがそれを証明しているからである)。
16.14 テストベクター
W5は言語間接具tlog_v1.json(§16.8)と作業済みベクトルを納品します:kind=0x01とkind=0x02葉→ leaf_hash;5葉累積根;包含証明が1つ;整合証明が1つ;一つのAnchorBundle;そして1つのDataItem idです。これらは§14.5の外側ラッパーベクトルと共存し、Rust(qub-core)およびTypeScript(ワーカー)実装の両方で処理されます。
16.15 審査決定(W5 — 解決済み)
W5外部審査(対抗設計パス+オーナー承認)は完了しました。以下の各決定は決定され、上記の§16の文に反映されています。拘束力のあるローンチ制約は最後に再述されています。実装はそれらの下で進めることができます。
- デフォルトパス(
kind=0x02)リーフの誠実性 — 解決済み。 指定された通りに2つのリーフタイプの分割を出荷する:kind=0x02はbody_hashもdrand_roundもコミットしない。バイトブラインドパスに*_body_hashフィールドは含めない(積分器にとって最も読みやすい偽の「検証済み」信号であり、§11がバンドルから既に提供している利便性である)。ログ認証キューブスに対してサーバーシールは**必要ではないのか?(これによりワーカーに平文を強制し、暗号シュレッダーの堀を破壊する)自己記述のショートサーキットは.qubバンドル/証明封筒に検証再計算フィールドとして属し、リーフフィールドには属しない。所有者確認請求上限:§16.11。 - 曖昧さ/省略責任 — 設計解決、プロビジョニング不完全 設計では、封印・受領キーを
LogProfileにピン留めし、anchor_ownerによる相互署名、さらに監視方法論、事前チェーンウォーク、デュアルセルフパブリッシュヘッドが必要です。コンパイルされたプロファイルと展開フックは、§16.6に詳述されているように、依然としてプレースホルダー/オプションであり、より強力な検出可能+受領の主張は、そのゲートが閉じるまで有効ではありません。それは決して「独立して証人」としてマーケティングされてはなりません。真の第三者証人は、§15のガバナンス強化に先延期されます。 - ピンされたアンカー所有者の信頼ルート+回転 — 解決済み。
LogProfileピンを採用する(§16.6);検証者はローカルでバインディングされている取引データをanchor_tx.owner == anchor_ownerチェックし、検証→ tx_id。ローテーションガバナンスは§15 **ビルドの拡張(§15.3トリガー追加)であり、再利用ではない。計画されたローテーションはクロスサインや妥協主導のローテーションにフォールされ、フォークチェックがバウンディングダメージを与えるため、§15のバンプに戻る。 - プライベートQBの葉のブラインド — 解決済み。 プライベートQubsにはブラインドを続け(
ref = SHA3-256(qub_id ‖ log_blind_secret))、パブリックQubsには生qub_id(すでに§16.2.1)、chash単独のタイとして。log_blind_secret相関/Sybilグレードの秘密で、回転のみで前進できます(§16.2.1)。 received_at— 解決済み。 リーフに含めて、コミットしたものの明示的に証拠なしにしてください。いかなる表面でも証拠や異議の裏付けとして現れてはなりません。モニターの合理性チェックは、オペレーター制御のanchored_atではなく、Arweaveのブロック時間Tと比較してください(§16.6)。- 階層証明可能タイミング — 設計解像度、現在のルーティングではありません。 レビューされた設計では、バッチ化された階層にはアンカーブロックタイミングを、有料T3には正確な時間証明を割り当てており、前者には数値SLAはありません。現行ルートでは商業的な区別が設定されていません。承認された出版物ごとに個別のトランザクションをスケジューリングし、ログカバレッジは§16.1/§16.10に記載されている条件付きです。製品コピーは実装を記述しなければならず、将来のティア分割については説明されません。
- Workers 上の累積ツリー — 解決済み。 単一の累積 RFC 9162 ツリー + フロンティアキャッシュされたシングルライター LogDO (約1k 書き込み/秒の DO 上限に対して十分な余裕を確保;シャードルートの Merkle 分割はその近くまで延期)。座標キー付き
(level, index)R2 ノードストア + ワイプ済み DO コールドリーフ テストベクターは実装済み (§16.9)。< 300 msは設計/運用上のターゲットであり、プロトコル上の約束ではない (§16.10)。 - ANS-104 署名スキーム + ディープハッシュ — 解決済み。 RSA-PSS(署名タイプ 1、専用アンカーワレット JWK を再利用); Ed25519 は §15 PQ パスに延期。手作りの SHA-384 ディープハッシュは、双方向のクロス実装フィクスチャ、静的のみのリファレンスバンドラー相互運用チェック、共有
crypto.subtleラウンドトリップ、およびバンドル後の Arweave 受け入れモニターに依存 (§16.8)。
起動時の制約条件のバインディング(実装および製品/法務レビューに持ち込む):
- 請求上限(Q1/Q6)。表面上はいかなる箇所も、ログが主張されたリーフの内容やアンロックラウンドを証明するとは言えません。
kind=0x02リーフが正常にアンカーされた場合に許可される請求は、順序付けられ、改ざん検知可能で、信頼不要の上限コミットメント時間と共に です。レシートタプルのない応答にはログ請求はありません。タイムスタンプのコピーには数値的な遅延保証はありません。 - 証人の誠実性(Q2)。市場における二枚舌は、検出可能 + レシート付き としてのみ扱われ、独立して目撃された とは見なされません。
- レシートおよびアンカーロジックキー(Q2/Q8)。非否認/アンカー検証済み請求が出荷される前に、レシート公開鍵を準備・コンパイルピンし、提供済みアンカー所有者とクロス署名を行い、アンカーウォレットをアップロードウォレットとは別の独自のJWKとして保持してください。
- ディープハッシュゲート(Q8)。両方向フィクスチャおよび相互運用チェックが通過するまで、いかなるアンカーやT3トランザクションも出荷されません。受け入れモニターは失敗時にページを表示します。
- ストレージ前提条件(Q7)。「回収は証明を無効化しない」という保証の前提条件として、コーディネートキー付きノードストアおよびワイプ済DOコールドリーフベクターが必要です。
17. ポータブル検証バンドル(.qub)
ステータス。 本セクションは 実装済み です(W7 / UP-C2)。
qub_core::exportがバンドルを生成・解析し、tools/qub-verifyはバンドル 1 つをオフラインで検証する、公開された自己完結型 CLI です。§11 と §16.9 はすでに、単独検証器が使用する単位として「.qubバンドル」を参照しています。本セクションは、そのバイト列と検証手順を規定します。これは厳密に追加的です。バンドルは既存の §11 入力をまとめるものであり、オンチェーンのワイヤ形式は変更しません。
17.1 目的
§11 は、第三者が qub の協力なしに qub の暗号学的アーティファクトを検証できることを定めています。.qub バンドルは、その検証を 可搬かつオフライン にします。封印済み CBOR と、それを解錠する drand ラウンド署名を単一の自己完結型アーティファクトにまとめるため、受領者はネットワーク呼び出しを一切行わず(ストレージ取得、稼働中の drand 要求、qub API のいずれも不要)、コンテンツの完全性、ラウンド結合、作成者署名があればその署名を検証できます。バンドル単体は、暗号文がいつ作成されたかを証明しません。独立して検証されたストレージトランザクションまたはアンカー済みログ証明が、その別個の存在時刻の主張を与えます(§11、§17.5)。
17.2 バンドル形式
QubBundle は、§3.1 のプロファイル(確定長、タグなし、float なし、最短形式の整数、NFC テキスト、不在時に任意フィールドを省略、符号化バイト長の昇順に続いてバイト単位でキーを順序付け)に従う手書きの正規 CBOR です。15 文字の 3 キーは d < i < s の順です。生の .qub ファイルは正確にこのバイト列です。URL またはコピー&ペーストで運ぶ場合は、同じバイト列を base64url(パディングなし)にします。
| キー | 符号化長 | 型 | 存在 | 意味 |
|---|---|---|---|---|
version |
8 | u8 |
必須 | バンドル形式バージョン(0x01)。 |
sealed_at |
10 | i64 |
任意 | 作成者が主張する封印時刻(Unix 秒)。自己記述用であり、証拠能力はありません。 |
drand_round |
12 | u64 |
必須 | qub がロックされたラウンド。埋め込まれた封印済み qub の投影。 |
arweave_tx_id |
14 | tstr |
必須 | 封印済みバイト列が保存されたトランザクション ID(来歴ポインタ)。 |
drand_chain_id |
15 | tstr |
必須 | drand チェーン(16 進数)。埋め込まれた封印済み qub の投影。 |
drand_signature |
16 | bstr |
必須 | drand_round の drand ビーコン署名。暗号文を解錠する値。 |
inclusion_proof |
16 | bstr |
任意 | ログ実装後の §16 透明性ログのマークル包含証明(§17.5)。 |
sealed_qub_cbor |
16 | bstr |
必須 | 内側の SealedQubCbor バイト列(§13 のアンラップ後)、すなわち §11 の検証入力。 |
drand_round と drand_chain_id は sealed_qub_cbor の便宜的な投影であり、ツールが内側の CBOR を解析せずに読み取れるよう運ばれます。構築時に導出され、デコード時には解析済みの封印済み qub に対して 再検査 されます。トップレベルのフィールドとペイロードが一致しないバンドルは拒否されます。エンコーダの規律はワイヤ形式の他の部分と同じです。空の drand_signature または arweave_tx_id を拒否し、すべての可変長フィールドに上限を設けます。
17.3 埋め込まれた drand 署名が証明するもの
バンドルは、検証者に取得させる代わりに drand 署名を格納します。タイムロック復号(drand チェーン上の tlock、§8)は、結合されたラウンドの 真正な ビーコン署名でしか成功しません。その値は、ラウンドが経過した後にだけチェーンが公開し、チェーン公開鍵の下で有効な BLS 署名です。偽造または誤った署名は、BLS 検証または IBE / AEAD 復号に失敗します。したがって復号に成功するバンドルは、暗号文がラウンド R に結合され、ラウンド R が経過したことを証明します。検証者はチェーン(DrandTimelockProvider::quicknet())を固定し、§11 のラウンド結合検査を適用するため、バンドルが暗号文の結合先でないラウンドを主張することはできません。
これは公開条件の証明であり、作成時刻のタイムスタンプではありません。ラウンド R の経過後は、誰でも R 向けの新しい暗号文を作成し、すでに公開された署名とともに格納できます。したがって、バンドル単体を、暗号文またはコンテンツが R より前、unlock_at より前、または何らかの出来事より前に存在したことの証明と表現してはなりません(MUST NOT)。
17.4 オフライン検証手順
qub-verify <file.qub> は、固定された DrandTimelockProvider で qub_core::unlock::unlock を駆動し、標準の §11 手順をバンドルだけから実行します。
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 は 0(検証済み)、1(検証失敗 — まだロック中、ボディハッシュ不一致、ラウンド / チェーン結合の破損、署名検証失敗)、2(使用方法 / 不正な形式のバンドル)で終了します。--json レポートは、自動化向けに同じ判定を運びます。バンドルは自己完結型であるため、第三者に必要なソフトウェアは検証クレート(qub-core)と CLI(qub-verify)だけです。どちらも公開され、プロトコルの既存検証経路を再利用します。独自の暗号処理はありません。
17.5 透明性ログとの関係
inclusion_proof は、§16 のマークル包含証明を格納する任意のスロットです。バンドル単体の検証(§17.4)だけで 完全性、ラウンド結合 / ラウンド経過、任意の作成者性 の検証は完結しますが、独立したタイムスタンプ付きの存在主張は意図的に含みません。値が入り、アンカーまで完全に検証された inclusion_proof は、バンドル形式のバージョンを変えずに、§16.11 のリーフ種別に応じたコミットメントと時刻上限を追加します。証明が不在であることは「証明が含まれていない」ことだけを意味し、「無効」または必ずしも「アンカーされていない」ことを意味しません。
リファレンス実装では、このスロットは現在 型付き です。qub_core::export::QubBundle::inclusion_proof_typed() は、同じ不透明な CBOR フィールドを通じて、§16.9 の完全な構造(リーフ、監査経路、アンカー済みルート、AnchorRef)を運ぶ Option<InclusionProof> を返します。バンドル形式のバージョン引き上げはありません。単独の qub-verify CLI は --anchor 経路でこれを使用し、アンカーウォレットが設定されるまで(§16 のステータス)、値が入っていても所有者がプレースホルダーの証明を、完全な アンカー検証済み ではなく 包含のみ と報告します。