Spesifikasi Protokol qub
qub adalah protokol untuk komitmen temporal kriptografis: sebuah sistem untuk menyegel kata-kata ke tanggal masa depan dan membuktikan, ketika tanggal tersebut tiba, secara persis apa yang dikatakan dan kapan.
Tiga primitif membuatnya bekerja. drand adalah beacon keacakan terdesentralisasi — tanggal pengungkapan dapat ditegakkan oleh fisika, bukan oleh niat baik pihak mana pun. Penyimpanan publik permanen adalah penyimpanan publik yang tahan-rusak — tidak ada pihak yang dapat menyunting atau menghapus qub setelah disegel. ML-DSA-65 adalah tanda tangan digital pasca-kuantum — setiap qub terikat pada pasangan kunci yang rahasianya tidak pernah meninggalkan perangkat penulis.
Bersama-sama primitif ini menghasilkan pernyataan yang terkunci waktu, terbukti anti-rusak, dan dapat diatribusikan — sebuah resi yang nilainya tumbuh seiring meningkatnya kemampuan dunia untuk memalsukan masa lalu.
Sisa dokumen ini adalah spesifikasi normatif yang diperlukan untuk implementasi yang dapat saling beroperasi.
Spesifikasi Protokol qub
| Field | Nilai |
|---|---|
| Versi | 1.0 (versi protokol 0x01, versi pembungkus luar 0x01) |
| Tanggal | 2026-05-01 |
| Status | Draf |
| Ditinjau hingga | 2026-05-01 |
Dokumen ini adalah spesifikasi protokol normatif untuk sistem komitmen berwaktu qub. Dokumen ini mendefinisikan struktur data, aturan serialisasi, rumus derivasi, dan prosedur verifikasi yang diperlukan untuk implementasi yang dapat saling beroperasi.
Cakupan: lapisan protokol secara sengaja bersifat netral-bahasa — body qub adalah plaintext / markdown / byte pakta yang opak, dan rendering yang mengenali lokal adalah tanggung jawab pembaca (aplikasi web qub.social, iframe <qub-embed>, klien MCP, dll.).
1. Notasi dan Konvensi
| Notasi | Arti |
|---|---|
u8, u64, i64 |
Bilangan bulat tanpa tanda/bertanda dengan lebar bit tertentu |
[u8; N] |
Larik byte dengan panjang tetap sebesar N byte |
Vec<u8> |
Larik byte dengan panjang variabel |
Option<T> |
Nilai bertipe T, atau tidak ada |
String |
String teks UTF-8, dinormalkan NFC |
| ` | |
SHA3-256(x) |
Hash NIST SHA3-256 dari string byte x (FIPS 202) |
ceil(x) |
Fungsi pembulatan ke atas: bilangan bulat terkecil ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | Byte paling signifikan didahulukan |
Semua bilangan bulat dalam konstruksi preimage dikodekan sebagai larik byte berlebar tetap big-endian (i64 → 8 byte, u8 → 1 byte) kecuali ditentukan lain.
Semua stempel waktu adalah detik Unix dalam UTC.
2. Struktur Data
2.1 ComposeQub (Status In-Memory Pembuat)
Tidak diserialisasikan ke CBOR. Tidak ditulis ke penyimpanan permanen. Lokal pada aplikasi pembuat.
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 (Payload Terdekripsi)
Diserialisasikan menggunakan CBOR kanonis (§3). Terenkripsi di dalam SealedQub. Inilah struktur yang membuktikan integritas konten setelah dekripsi.
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
}
Baseline (qub teks tak bertanda tangan): version = 0x01, content_type = 0x01, sig_alg = 0x00, semua field Option tidak ada.
Konfigurasi v1 lainnya: content_type = 0x03 (body pakta, lihat §6.1); sig_alg = 0x01 (ML-DSA-65) dengan author_signature dan author_pubkey hadir (lihat §9.3); cosigner_pubkey dan cosigner_signature hadir bersama untuk pakta yang ditandatangani bersama (lihat §9.7); reply_to diatur ke qub_id qub induk untuk qub rantai-balasan (lihat §9.3 untuk implikasi cakupan tanda tangan).
2.3 SealedQub (Format Wire Kanonis)
Diserialisasikan menggunakan CBOR kanonis (§3). Ditulis ke penyimpanan permanen. Inilah artefak on-chain.
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 (Status Aplikasi Pembaca)
Tidak diserialisasikan ke CBOR. Lokal pada aplikasi pembaca. Dibangun setelah dekripsi dan verifikasi berhasil.
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 — diteruskan dari QubEnvelope.outcome_at / SealedQub.outcome_at; menggerakkan blok pengawas-putusan di halaman pengungkapan (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. Profil CBOR Kanonis
Semua serialisasi SealedQub dan QubEnvelope HARUS mematuhi profil ini. Dua implementasi yang diberi struktur logis yang sama HARUS menghasilkan byte yang identik.
3.1 Aturan Pengkodean
| Aturan | Spesifikasi |
|---|---|
| Standar | RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements) |
| Urutan key map | Diurutkan menurut panjang byte terenkode terlebih dahulu (yang lebih pendek sebelum yang lebih panjang), kemudian secara leksikografis (byte per byte untuk pengkodean berpanjang sama) |
| Pengkodean integer | Bentuk terpendek: 0–23 di byte awal; 24–255 dalam 2 byte; 256–65535 dalam 3 byte; dst. |
| Pengkodean panjang | Hanya panjang pasti. Tidak ada array, map, byte string, atau text string panjang-tak-tentu (additional info = 31 dilarang). |
| Tag | Tidak ada tag CBOR (major type 6 dilarang). |
| Floating-point | Tidak ada float (major type 7 nilai 0xF9–0xFB dilarang). |
| Text string | Dikodekan UTF-8, dinormalkan NFC (Unicode Normalization Form C). |
| Byte string | Byte mentah. Tidak ada pengkodean base64 di lapisan CBOR. |
| Key duplikat | Tolak dengan error. Parser TIDAK BOLEH secara diam-diam menerima key map duplikat. |
| Nilai simple | Hanya true (0xF5), false (0xF4), dan null (0xF6) yang diperbolehkan. |
| Field opsional | Field opsional yang tidak ada dihilangkan dari map CBOR sepenuhnya (tidak dikodekan sebagai null). Field opsional yang hadir disertakan dalam urutan key terurut. |
3.2 Urutan Key Kanonis yang Diverifikasi
Urutan key ini bersifat normatif. Implementasi HARUS mengeluarkan key dalam urutan yang sama persis ini. Assertion debug SEBAIKNYA memverifikasi urutan dalam build non-release.
QubEnvelope (version 0x01, tak bertanda tangan, semua field opsional tidak ada):
"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)
Derivasi urutan key QubEnvelope: setiap key adalah text string CBOR. Panjang terenkode = 1 byte header + panjang string (untuk string di bawah 24 byte). Diurutkan menurut total panjang terenkode terlebih dahulu, kemudian secara leksikografis untuk key berpanjang sama.
SealedQub (version 0x01, publik, tanpa penerima):
"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 (body pakta, 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 (baris dari larik terms):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier (map party_a / party_b):
"label" (6 encoded bytes)
"contact" (8 encoded bytes) ← only if present
3.3 Referensi Pengkodean Byte
| Tipe | Pengkodean CBOR | Contoh |
|---|---|---|
| Hash SHA3-256 (32 byte) | 0x58 0x20 + 32 byte |
body_hash, qub_id |
| Stempel waktu (i64) | Major type 0 (positif) atau 1 (negatif), pengkodean terpendek | Detik Unix |
| Version (u8, nilai 1) | 0x01 (byte tunggal) |
|
| Content type (u8, nilai 1) | 0x01 (byte tunggal) |
|
| sig_alg (u8, nilai 0) | 0x00 (byte tunggal) |
|
| Tanda tangan ML-DSA-65 (3.309 byte) | 0x59 0x0C 0xED + 3.309 byte |
author_signature, cosigner_signature |
| Kunci publik ML-DSA-65 (1.952 byte) | 0x59 0x07 0xA0 + 1.952 byte |
author_pubkey, cosigner_pubkey |
4. Derivasi Normatif
4.1 qub_id
qub_id secara unik mengidentifikasi sebuah qub dan mengikat QubEnvelope ke SealedQub. Ia diturunkan secara deterministik dari konten envelope.
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
Pengkodean pemisah domain: String "QUB_ID_V2" adalah 9 byte ASCII. Sebuah byte padding 0x00 tunggal ditambahkan untuk mencapai 10 byte demi penyelarasan. Implementasi HARUS menggunakan tepat 10 byte ini: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
Pengkodean outcome_at: V1.1 memperluas preimage dari 92 menjadi 100 byte untuk melipat field outcome_at opsional ke dalam pengikatan. outcome_at yang tidak hadir dikodekan sebagai 8 byte nol; validator protokol menolak outcome_at <= 0 di mana pun sehingga sentinel ini tidak dapat berbenturan dengan nilai yang sah. Lihat §3.2 (format wire) dan tasks/verdict-uplift-plan.md dalam pohon untuk mekanik verdict yang memotivasi field ini.
Pengkodean drand_round: V1.2 memperluas preimage dari 100 menjadi 108 byte untuk melipat drand_round (putaran drand target, §4.3) ke dalam pengikatan, dan menaikkan pemisah domain menjadi QUB_ID_V2. Ini mengikat putaran timelock ke dalam identitas qub: sebuah gateway tidak dapat mengikat ulang ciphertext ke putaran yang berbeda (mis. yang sudah lewat) dari yang diimplikasikan oleh unlock_at yang ditampilkan. Prosedur unlock (§8) selain itu memverifikasi bahwa putaran yang ditanamkan ke dalam stanza ciphertext tlock cocok dengan unlock_round(unlock_at), sehingga waktu unlock yang ditampilkan terbukti merupakan putaran yang menggerbangi dekripsi.
Properti:
- Mengubah field apa pun di QubEnvelope (body, stempel waktu, content type, version) menghasilkan qub_id yang berbeda.
- qub_id dihitung sebelum enkripsi. Baik QubEnvelope maupun SealedQub membawa qub_id yang sama. Pembaca memverifikasi bahwa keduanya cocok setelah dekripsi.
- qub_id tidak bergantung pada
sender_label,author_signature, atauauthor_pubkey. Ini berarti konten yang sama yang disegel pada waktu yang sama menghasilkan qub_id yang sama, terlepas dari siapa yang menandatanganinya. - Mengubah
titleSealedQub (dengan semua hal lain tetap) mengubahqub_idlewattitle_hash. Karena itu sebuah gateway tidak dapat menukar judul plaintext yang ditampilkan pada hitung mundur tanpa membatalkan identitas qub. - Mengubah
outcome_atSealedQub (dengan semua hal lain tetap) mengubahqub_idlewat preimage. Sebuah gateway tidak dapat menukar tanggal verdict-on pra-pengungkapan yang ditampilkan pada hitung mundur tanpa membatalkan identitas qub. - Mengubah
drand_round(dengan semua hal lain tetap) mengubahqub_idlewat preimage. Sebuah gateway tidak dapat mengikat ulang ciphertext timelock ke putaran yang berbeda tanpa membatalkan identitas qub; dikombinasikan dengan pemeriksaan stanza-putaran waktu-unlock §8,unlock_atyang ditampilkan adalah putaran yang sebenarnya menggerbangi dekripsi.
4.2 body_hash
body_hash = SHA3-256(body)
Di mana body adalah payload konten Vec<u8> mentah. Untuk qub teks, ini adalah body qub yang dikodekan UTF-8.
4.2.1 title_hash
title_hash = SHA3-256(NFC(title).utf8_bytes) if title is present
title_hash = [0u8; 32] if title is absent
Di mana title adalah judul plaintext opsional yang dimunculkan pada hitung mundur pembaca sebelum pengungkapan (lihat §3.2). Normalisasi NFC dijalankan pada saat hash sehingga digest stabil di seluruh urutan code-point yang setara secara visual. Sentinel semua-nol dicadangkan untuk kasus tidak-hadir; string kosong ditolak pada batas CBOR kanonis sebagai pengkodean non-kanonis dari "tidak hadir" (pengkodean kanonis menghilangkan field sepenuhnya).
4.3 Pemetaan Unlock-Round
drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| Parameter | Sumber | Contoh |
|---|---|---|
unlock_at |
Detik Unix UTC yang dipilih pengguna | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
Info chain drand (genesis_time) |
1595431050 |
chain_period_seconds |
Info chain drand (period) |
30 |
Operasi ceil() memilih putaran drand pertama yang waktu pengungkapannya ≥ unlock_at. Ini memastikan qub tidak menjadi dapat didekripsi sebelum waktu unlock yang dipilih.
Kasus tepi: jika (unlock_at - chain_genesis_time) habis dibagi tepat oleh chain_period_seconds, hasilnya adalah putaran tepat itu — qub terbuka tepat pada waktu pengungkapan putaran tersebut.
Validasi: unlock_at HARUS berada di masa depan pada saat penyegelan. unlock_at TIDAK BOLEH lebih dari 10 tahun dari created_at (untuk membatasi risiko ketergantungan drand horison-panjang; UI SEBAIKNYA memperingatkan untuk tanggal unlock di luar 2 tahun).
5. Newtypes Format Wire
Newtypes format wire memberikan keamanan saat kompilasi terhadap kebingungan antara byte CBOR dengan JSON, plaintext mentah, atau pengkodean byte lainnya.
| Tipe | Berisi | Diproduksi Oleh | Dikonsumsi Oleh |
|---|---|---|---|
SealedQubCbor |
CBOR kanonis dari SealedQub | serialize_sealed_qub() |
Unggahan penyimpanan permanen, fetch pembaca |
QubEnvelopeCbor |
CBOR kanonis dari QubEnvelope | serialize_qub_envelope() |
Input enkripsi tlock, output dekripsi tlock |
5.1 Aturan Konstruksi
// 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 Validasi pada Konstruksi
from_encoded() SEBAIKNYA memvalidasi bahwa input dimulai dengan header map CBOR yang valid. Validasi struktural penuh terjadi pada waktu parse, bukan waktu konstruksi, untuk menghindari double-parsing.
6. Registri Content Type
| Nilai | Tipe | Ukuran Body Maksimum | Catatan |
|---|---|---|---|
0x00 |
Dicadangkan (tidak valid) | — | TIDAK BOLEH digunakan |
0x01 |
Teks biasa (UTF-8, Markdown terbatas) | 50 KB berbayar / 10 KB gratis | Lihat §10 untuk aturan rendering. Pemisahan gratis / berbayar ditegakkan oleh layanan upload; batas keras lapisan protokol adalah 50 KB. |
0x02 |
Dicadangkan (masa depan) | — | Dialokasikan untuk content type masa depan; tidak valid di v1. Pembaca HARUS menolak sesuai aturan di bawah. |
0x03 |
Pakta (kesepakatan bilateral, body CBOR) | 100 KB | Body adalah PactTerms CBOR kanonis (§6.1). Penandatanganan bersama per §9.7. |
0x04 |
Putusan (penilaian-diri pembuat, body CBOR) | 8 KB | Body adalah VerdictBody CBOR kanonis (§6.2). Dikeluarkan hanya oleh intent verdict sisi-sistem. Hubungan induk berada pada tag Arweave Parent-Tx-Id, bukan pada body. Lihat verdict-uplift-plan §3.4. |
Pembaca HARUS menolak content type yang tidak dikenal dengan pesan error yang jelas terlihat oleh pengguna. Pembaca TIDAK BOLEH mencoba merender tipe tidak dikenal sebagai teks.
6.1 Body Pakta (content_type = 0x03)
Body pakta adalah pengkodean CBOR kanonis dari nilai PactTerms:
PactTerms {
pact_version: u8, // 0x01 for structured/v1
title: String, // ≤ 200 bytes, NFC
terms: Vec<PactTerm>, // ≤ 20 rows
party_a: PartyIdentifier, // initiator
party_b: PartyIdentifier, // counter-signer
notes: Option<String>, // ≤ 5,000 bytes, NFC; absent key if none
}
PactTerm { key: String (≤ 100), value: String (≤ 2,000) } // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }
Urutan key CBOR kanonis untuk ketiga map diberikan di §3.2. Total CBOR pakta terserialisasi TIDAK BOLEH melebihi 100 KB (sesuai §6).
Diskriminator skema. Baris pertama dalam terms untuk pakta structured/v1 HARUS berupa { key: "pact_schema", value: "structured/v1" }. Baris tanpa penanda ini adalah pakta "custom" dan tidak mendapatkan validasi terstruktur atau rendering yang sadar-skema.
Slot pengakuan yang dibekukan. Pakta structured/v1 membawa tepat empat baris pengakuan di bawah key berikut:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value untuk masing-masing adalah salah satu dari delapan string bahasa Inggris yang dibekukan yang dipilih oleh pasangan (role, kind), di mana role ∈ { seller, buyer, provider, client } dan kind ∈ { standard, capacity }. String itu sendiri adalah data protokol normatif — tanda tangan ML-DSA-65 kedua pihak melakukan commit ke byte yang tepat melalui body_hash. Mereka TIDAK dilokalisasi; body yang ditandatangani bersifat netral-bahasa. Setiap perubahan kata mengharuskan versi skema baru (structured/v2).
Delapan string, pencariannya (acknowledgement_for(role, kind)), dan dasar pemikiran untuk masing-masing dipatok oleh implementasi referensi. Implementasi yang mengikuti standar HARUS mengeluarkan nilai pengakuan yang identik byte demi byte; tes body-hash SHA3-256 golden-fixture yang mencakup keempat kombinasi peran menangkap setiap pergeseran.
Urutan tampilan pembaca. String pengakuan berisi frasa seperti "described above", yang mengandaikan baris deskripsi / cakupan dirender sebelum pengakuan. Pembaca HARUS merender array terms dalam urutan CBOR; menyusun ulang merusak semantik prosa.
Kontak rekan-pihak. Ketika contact Pihak B adalah alamat email yang valid, layanan upload qub secara otomatis mengirimkan email undangan tinjau / tanda-tangan-bersama pada saat staging dan mengikat tanda-tangan-bersama yang akhirnya terjadi pada verifikasi alamat yang sama (§9.7). Pakta yang kontak Pihak B-nya tidak ada masih dapat ditandatangani bersama, tetapi hanya melalui saluran di luar jalur — layanan menolak permintaan tanda-tangan-bersama yang tidak dapat menghasilkan penanda verifikasi-email 15-menit yang cocok.
6.2 Body Putusan (content_type = 0x04)
Body putusan adalah pengkodean CBOR kanonis dari nilai VerdictBody:
VerdictBody {
verdict_version: u8, // 0x01 for structured/v1
outcome: u8, // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
reflection: Option<String>, // ≤ 2,000 bytes NFC; "what changed, what did you learn"
evidence_url: Option<String>, // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}
Urutan key CBOR kanonis:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
Total CBOR putusan terserialisasi TIDAK BOLEH melebihi 8 KB (sesuai baris registri di atas).
Enum outcome. Byte wire bersifat netral-intent; empat kategori Right / Partial / Wrong / Unfalsifiable mencakup seluruh ruang hasil dari setiap intent yang membawa putusan. Label per-intent ("Tepat sasaran" / "Saya menepatinya" / "Dirilis sesuai rencana" / "Dikonfirmasi oleh peristiwa" untuk Right, dsb.) adalah urusan rendering sisi-pembaca yang diselesaikan terhadap intent qub induk — wire tetap netral-bahasa dan netral-intent. Nilai di luar 1..=4 HARUS ditolak pada decode.
Tautan induk. qub putusan TIDAK membawa referensi induk di body-nya. ID transaksi Arweave qub induk dikeluarkan sebagai tag penyimpanan Parent-Tx-Id pada waktu upload (lapisan tag penyimpanan §7). Ini menjaga body sebagai pernyataan penilaian-diri yang terikat tanda tangan dan mandiri; rantai audit ("benar tentang apa?") ditetapkan melalui pencarian tag Arweave.
Keamanan evidence URL (normatif). Ketika evidence_url ada, validator (sisi-compose, sisi-wire, edge Worker) HARUS menegakkan:
- HTTPS saja. String HARUS dimulai dengan urutan byte
https://. Skema lain —http,ftp,javascript,data,file, dsb. — ditolak. - Batas panjang. ≤ 2,048 byte (batas praktis URL peramban).
- Pemeriksaan NFC + codepoint-bermusuhan. Aturan yang sama dengan
titledanreflection— codepoint bidi-override / zero-width / tag-block / BOM / C0 / C1 ditolak. Definisi sesuai dengan Rustcrate::handle::contains_hostile_text_codepointdan TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(jaga agar selaras). - Tidak ada whitespace, tidak ada kontrol ASCII. Whitespace / DEL / byte sub-
0x20di mana pun dalam URL ditolak — menutup vektor injeksi\n/\tyang tidak tercakup aturan bidi. - Segmen host non-kosong. Semua antara
https://dan/,?, atau#pertama HARUS non-kosong.
Tidak ada fetch sisi-server. Worker TIDAK BOLEH memproksi, mengambil, atau melakukan preview URL. Protokol menyimpan sebuah string; rendering terjadi sisi-pembaca dengan rel="nofollow noopener noreferrer" target="_blank" dan host yang terlihat ditampilkan di samping teks tautan.
Refleksi. Teks refleksi opsional yang ditulis pembuat ("apa yang berubah, apa yang Anda pelajari"). Validasi NFC + codepoint-bermusuhan yang sama dengan title. Input kosong / hanya-whitespace menyusut menjadi tidak hadir pada saat konstruksi.
Versi skema. v1 hanya mendukung verdict_version = 0x01. Revisi skema masa depan menaikkan byte ini dan dirilis bersamaan dengan versi protokol baru per §12.
7. Protokol Penyegelan
Urutan penyegelan lengkap. Setiap langkah bersifat normatif.
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.
Lapisan tag penyimpanan (di luar jalur). Layanan upload qub melampirkan sehimpunan kecil tag transaksi penyimpanan secara sengaja di samping payload yang dibungkus. Content-Type=application/octet-stream diwajibkan secara normatif. Layanan referensi juga melampirkan tiga tag opsional ketika pembuat memilih untuk memunculkannya: Intent (intent komposisi yang divalidasi-allowlist — mis., quote, reply, commitment), Author (sidik jari pubkey pembuat §9.3 sebagai hex huruf kecil 64 karakter), dan Parent-Tx-Id (ID transaksi penyimpanan qub induk untuk rantai balasan, base64url 43 karakter).
Tag Author bersifat opt-in per qub: aplikasi pembuat referensi melampirkannya hanya ketika pengguna secara eksplisit mengaktifkan atribusi publik pada waktu penyegelan. Ketika toggle dimatikan — default-nya — tidak ada tag Author yang ditulis dan qub tidak dikaitkan di chain: tidak ada apa pun di penyimpanan permanen yang menghubungkan upload tersebut dengan handle pembuat, email, atau qub lainnya. Ketika toggle dinyalakan, sidik jari Author diresolusi ke @handle pilihan pembuat melalui rantai atestasi §9.5. Hubungan rantai-balasan dan Intent tidak mengidentifikasi. Pembungkus luar (§13) melindungi body dalam dari korelasi ciphertext — mencegah pemanen mengenali dan mendekripsi massal upload berbentuk qub setelah putaran drand mereka diterbitkan.
Layanan referensi secara sengaja TIDAK melampirkan tag App-Name, App-Version, atau Type: filter nilai-tunggal seperti itu akan mengembalikan seluruh korpus qub ke kueri GraphQL, yang tidak konsisten dengan cakupan kerahasiaan body-saja dari pembungkus.
Verifier yang mengikuti standar TIDAK BOLEH bergantung pada tag penyimpanan apa pun untuk verifikasi pihak ketiga §11; body hash / qub_id / tanda tangan hanya melakukan commit ke CBOR dalam, tidak pernah ke set tag.
8. Protokol Unlock
Urutan unlock lengkap. Setiap langkah bersifat normatif.
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. Penandatanganan Kepenulisan
9.1 Dasar Pemikiran
qub disimpan di penyimpanan permanen. Tanda tangan kepenulisan harus tetap tidak dapat dipalsukan tanpa batas waktu, itulah sebabnya v1.0 menggunakan skema pasca-kuantum ML-DSA-65 (FIPS 204) alih-alih skema klasik yang keamanannya dapat menurun selama masa hidup permanen qub.
9.2 Registri Algoritma
sig_alg |
Skema | Ukuran Kunci | Ukuran Tanda Tangan |
|---|---|---|---|
0x00 |
Tanpa tanda tangan (tidak ditandatangani) | — | — |
0x01 |
ML-DSA-65 (FIPS 204) | 1.952 byte | 3.309 byte |
Pembaca HARUS menolak nilai sig_alg yang tidak dikenal.
9.3 Konstruksi Preimage yang Ditandatangani
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
signature = Sign(author_secret_key, sig_input)
Pemisah domain: "QUB_AUTHOR_SIG_V1" adalah 17 byte ASCII: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Tanpa padding.
Byte penutup: byte preimage ke-91 HARUS bernilai 0x00. Implementasi referensi memunculkan ini sebagai konstanta ORG_ID_PRESENT_INDIVIDUAL = 0x00 di crates/qub-core/src/signing.rs; pembaca yang merekonstruksi sig_input untuk verifikasi HARUS mengeluarkan byte yang sama.
Cakupan tanda tangan — apa yang dicakup dan tidak dicakup. sig_input melakukan commit pada empat field envelope: version, qub_id, body_hash, unlock_at (ditambah pemisah domain tetap dan byte org_id_present). Tiga dari empat itu adalah invarian struktural: qub_id itu sendiri diturunkan dari version, content_type, created_at, unlock_at, outcome_at, drand_round, dan body_hash melalui preimage §4.1, sehingga setiap perubahan pada field tersebut menghasilkan qub_id yang berbeda dan membatalkan tanda tangan secara transitif. Permukaan yang diautentikasi secara langsung karenanya adalah:
| Field | Diautentikasi oleh tanda tangan | Cara |
|---|---|---|
version |
✓ | Input langsung ke sig_input |
qub_id |
✓ | Input langsung |
body_hash |
✓ | Input langsung |
unlock_at |
✓ | Input langsung |
content_type |
✓ | Secara transitif, lewat preimage qub_id |
created_at |
✓ | Secara transitif, lewat preimage qub_id |
outcome_at |
✓ | Secara transitif, lewat preimage qub_id |
drand_round |
✓ | Secara transitif, lewat preimage qub_id (V1.2) |
body |
✓ | Secara transitif, lewat body_hash = SHA3-256(body) |
author_pubkey |
— (implisit) | Kunci yang memverifikasi tanda tangan adalah penulis, secara definisi |
sender_label |
✗ | Teks tampilan saja; dapat berubah tanpa merusak tanda tangan |
reply_to |
✗ | Pointer threading; dapat berubah tanpa merusak tanda tangan |
cosigner_pubkey / cosigner_signature |
— | Ditandatangani secara independen atas sig_input yang sama (lihat §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | Field SealedQub luar, tidak di dalam envelope — dicakup oleh invarian struktural mereka sendiri (konsistensi putaran / chain) tetapi tidak oleh tanda tangan penulis. (drand_round kini terikat secara transitif lewat preimage qub_id — lihat di atas.) |
Implikasi keamanan dari field yang tidak diautentikasi.
- Pihak dengan akses tulis ke byte yang disimpan dapat menukar
sender_label("Alice" → "Mallory") tanpa membatalkan tanda tangan penulis.author_pubkeydi dalam envelope tetap menjadi jangkar identitas yang sebenarnya — pembaca HARUS menurunkan identitas tampilan dariauthor_pubkey(melalui lapisan atestasi §9.5) daripada mempercayaisender_label. - Field
reply_tojuga dapat disunting setelah penandatanganan. Karenaqub_idadalah content-addressed, penyerang tidak dapat menunjukreply_toke target yang tidak ada, tetapi mereka dapat secara diam-diam menjadikan balasan sebagai anak dari qub lain yang sudah ada.
Implementasi yang menampilkan sender_label atau reply_to kepada pengguna akhir HARUS memunculkan identitas yang diautentikasi (sidik jari pubkey, atestasi) sebagai sinyal identitas utama, bukan label.
9.4 Prosedur Verifikasi
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 (same formula as §9.3).
6. Verify(author_pubkey, sig_input, author_signature).
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."
Verifikasi tanda tangan adalah operasi yang paling mahal (terutama ML-DSA-65). Sebaiknya dilakukan setelah semua pemeriksaan yang lebih murah (hash, qub_id, unlock_at) berhasil.
9.5 Atestasi Identitas
Atestasi identitas — pemetaan author_pubkey ke klaim identitas yang dapat dikenali manusia seperti handle qub, alamat email, handle sosial, atau kredensial passkey — adalah peningkatan progresif sisi-pembaca dan tidak diperlukan untuk verifikasi tanda tangan. Pembaca yang menyelesaikan atestasi ke identitas tampilan HARUS menerapkan prioritas:
handle > email > social > fingerprint
Fallback sidik jari adalah hex huruf kecil dari SHA3-256(author_pubkey); ini selalu tersedia untuk qub bertanda tangan apa pun. Pembaca BOLEH menyingkatnya untuk tampilan — pembaca referensi merender qub: diikuti oleh empat byte pertama dan terakhir (qub:<8 hex>…<8 hex>).
Verifier yang mengikuti standar dapat menyelesaikan setiap pemeriksaan di §9.4 tanpa menghubungi API qub, tanpa jaringan apa pun di luar penyimpanan permanen dan drand, dan tanpa pencarian sisi-server apa pun. Resolusi atestasi adalah langkah best-effort terpisah yang dilakukan hanya setelah verifikasi tanda tangan berhasil.
9.6 Dampak Ukuran
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| Tanda tangan | 64 byte | 3.309 byte |
| Kunci publik | 32 byte | 1.952 byte |
| Total per qub | 96 byte | 5.261 byte |
| Delta biaya penyimpanan (sekitar $5/MB) | ~$0,0005 | ~$0,026 |
Untuk qub teks 500–2.000 byte, ML-DSA-65 kira-kira melipatgandakan tiga kali lipat ukuran yang disimpan. Biaya absolutnya dapat diabaikan.
9.7 Verifikasi Cosigner (Kesepakatan Bilateral Pakta)
Untuk kesepakatan bilateral (content_type = 0x03), lapisan tanda tangan kedua membuktikan bahwa kedua pihak menyetujui ketentuan yang sama.
Field envelope:
cosigner_pubkey: Kunci publik ML-DSA-65 dari penandatangan-bersama (Pihak B).cosigner_signature: Tanda tangan atassig_inputyang sama dengan penulis (§9.3).
Kedua field HARUS hadir bersama atau keduanya tidak ada. Jika tepat satu yang hadir, pembaca HARUS melaporkan error integritas.
Prosedur verifikasi:
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.
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."
Properti:
- Cosigner menandatangani
sig_inputyang identik dengan penulis — kedua pihak melakukan commit padaqub_id,body_hash, danunlock_atyang sama. - Derivasi
qub_id(§4.1) TIDAK mencakup field cosigner. Menambahkan cosigner ke envelope yang sudah ada tidak mengubahqub_id. - Sebuah pakta dapat ditandatangani-penulis saja (komitmen sepihak), cosigner saja (tidak biasa), atau keduanya (bukti bilateral penuh).
Gerbang pengikatan email (operasional). Ketika pakta yang di-staging membawa kontak email Pihak B (§6.1), layanan upload qub HARUS menolak permintaan tanda-tangan-bersama kecuali ada penanda verifikasi-email berusia pendek yang cocok dengan id staging dan hash email yang dinormalkan dari kontak tersebut. Penanda ditulis oleh /api/v1/auth/verify ketika token magic-link membawa staging_id dan alamat yang diverifikasi cocok dengan SHA-256(normalise_email(party_b.contact)) — di mana normalise_email(addr) mempertahankan kapitalisasi bagian-lokal dan hanya mengubah bagian domain menjadi huruf kecil (sesuai RFC 5321 §2.3.11), dan SHA-256 di sini adalah hash NIST FIPS 180-4 (berbeda dari SHA3-256 yang digunakan dalam derivasi §4) — dan kedaluwarsa 900 detik (15 menit) setelah diterbitkan. Ini adalah gerbang anti-peniruan operasional, BUKAN bagian dari bukti qub on-chain — verifier pihak ketiga yang memutar ulang §11 hanya memerlukan penyimpanan permanen dan drand, tanpa pencarian sisi-server apa pun. Penanda hanya ada di sisi server dan tidak pernah menjadi bagian dari body yang ditandatangani.
Dampak ukuran (penulis ML-DSA-65 + cosigner):
| Komponen | Ukuran |
|---|---|
| Tanda tangan penulis | 3.309 byte |
| Kunci publik penulis | 1.952 byte |
| Tanda tangan cosigner | 3.309 byte |
| Kunci publik cosigner | 1.952 byte |
| Total overhead kripto | 10.522 byte |
| Delta biaya penyimpanan | ~$0,05 |
10. Rendering Markdown dan Sanitasi
Bagian ini sangat kritis untuk keamanan. Pembaca merender qub teks (content_type = 0x01) menggunakan subset Markdown yang dibatasi.
10.1 Elemen yang Diizinkan
- Heading:
#hingga####(tidak ada#####atau######) - Penegasan: tebal (
**), miring (*), coretan (~~) - Daftar: terurut (
1.) dan tak terurut (-,*) - Blockquote (
>) - Kode: span inline (```) dan blok berpagar (`````)
- Garis horisontal (
---) - Pemutus baris (dua spasi penutup atau baris kosong)
- Paragraf
10.2 Elemen yang Dilarang
| Elemen | Penanganan |
|---|---|
HTML mentah (<div>, <script>, dll.) |
Dihapus sepenuhnya. Tidak ada HTML yang lolos. |
Gambar () |
Dihapus. Sintaks gambar dihapus dari output. |
Tautan ([text](url)) |
URL dirender sebagai teks biasa yang terlihat. Tidak otomatis ditautkan. Tidak dapat diklik tanpa tindakan pengguna eksplisit. |
| Skema URL berbahaya | javascript:, data:, vbscript:, file: — dihapus. |
| Iframe, embed, object | Dihapus. |
| Entitas HTML | Didekode ke karakter tampilan hanya jika aman. |
10.3 Implementasi
Implementasi HARUS menggunakan parser allowlist ketat, bukan blocklist. Pendekatan yang direkomendasikan:
- Parsing Markdown menggunakan
pulldown-cmark(atau setara). - Telusuri AST dan buang setiap node yang tidak ada dalam allowlist (§10.1).
- Untuk node tautan: keluarkan URL sebagai teks yang terlihat, bukan sebagai elemen
<a>yang dapat diklik. - Konversikan AST yang difilter ke representasi perantara bertipe (mis., enum
MarkdownNodedengan hanya varian yang aman). HTML mentah secara struktural tidak dapat direpresentasikan dalam IR ini. - Render dari IR bertipe ke lapisan tampilan target (mis., komponen tampilan reaktif, node DOM). Tanpa konkatenasi string HTML atau
innerHTMLdi titik mana pun.
Pendekatan blocklist rapuh karena ekstensi Markdown baru atau keanehan parser dapat memperkenalkan elemen yang tidak terfilter. Pendekatan AST-bertipe membuat XSS secara struktural tidak mungkin — tidak ada varian yang dapat membawa HTML sembarangan.
10.4 Batas Ukuran dan Struktur
- Kedalaman heading maksimum yang dirender:
####(H4).#####dan lebih dalam dirender sebagai teks tebal. - Tidak ada batas jumlah paragraf (batas ukuran body di §6 adalah batasannya).
- Blok kode berpagar: tanpa penyorotan sintaks di MVP. Dirender sebagai teks preformatted monospace.
11. Verifikasi Pihak Ketiga
Pihak ketiga mana pun dapat memverifikasi qub publik tanpa kerja sama qub. Prosedur verifikasi:
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.
Apa yang dibuktikan oleh verifikasi:
| Bukti | Apa yang ditegakkannya |
|---|---|
| Komitmen | Ciphertext sudah ada pada stempel waktu blok penyimpanan. |
| Integritas | Body plaintext cocok dengan hash yang di-commit dan belum diubah. |
| Waktu | Konten tidak dapat dibaca hingga putaran drand, yang sesuai dengan waktu unlock yang dipilih (tunduk pada asumsi keamanan tlock dan drand). |
Apa yang TIDAK dibuktikan oleh verifikasi:
| Bukan-bukti | Mengapa |
|---|---|
| Kepenulisan | sender_label bersifat dekoratif. Tanpa sig_alg ≥ 0x01, siapa pun bisa saja telah menyegel konten ini. |
| Maksud | qub membuktikan konten dan waktu, bukan apa yang dimaksudkan pembuat secara subjektif. |
| Waktu pra-peristiwa | Inklusi blok penyimpanan dapat tertinggal beberapa menit dari upload sebenarnya. Stempel waktu komitmen adalah waktu blok, bukan saat pengguna menekan "seal". |
12. Versi
12.1 Versi Protokol
Field version (u8) di kedua SealedQub dan QubEnvelope mengidentifikasi versi major protokol.
- Pembaca HARUS menolak versi major yang tidak dikenal dengan error yang jelas.
- Versi major yang dikenal BOLEH mentoleransi field opsional yang tidak dikenal jika aturan kompatibilitas-maju mengizinkan (field opsional yang tidak ada dalam urutan key kanonis diabaikan).
- Content type (
content_type) dan skema tanda tangan (sig_alg) ditetapkan oleh versi: nilai baru hanya dapat diperkenalkan bersama dengan versi protokol baru atau pembaruan registri eksplisit.
12.2 Riwayat Versi
| Versi | Nilai | Deskripsi |
|---|---|---|
| v1 | 0x01 |
qub teks publik (content_type 0x01), kesepakatan bilateral pakta (0x03, skema structured/v1, penulis ML-DSA-65 + cosigner), tlock, SHA3-256 |
12.3 Kompatibilitas Maju
Pembaca v1 yang menemui QubEnvelope dengan key map CBOR opsional yang tidak dikenal (key yang tidak ada dalam urutan kanonis §3.2) SEBAIKNYA mengabaikan key tersebut dan melanjutkan verifikasi menggunakan field yang dikenal. Ini memungkinkan tambahan minor di masa depan (mis., metadata baru) tanpa memerlukan peningkatan versi major.
Pembaca v1 yang menemui sig_alg = 0x01 (ML-DSA-65) tetapi tidak memiliki dukungan verifikasi ML-DSA-65 SEBAIKNYA menampilkan konten qub dengan pemberitahuan "tanda tangan ada tetapi tidak dapat diverifikasi", bukan menolak qub sepenuhnya. Implementasi referensi saat ini menolak setiap nilai sig_alg selain 0x00 dan 0x01 karena registri v1 tidak berisi algoritma valid lainnya — penolakan ketat dan kegagalan-lunak secara observasional identik hingga algoritma ketiga didaftarkan. Perilaku kegagalan-lunak di atas menjadi load-bearing setelah §9.2 menerima entri baru, dan pembaca referensi akan diperbarui untuk gagal-lunak pada titik itu.
12.4 Versi Pembungkus Luar
OuterWrapper yang dideskripsikan di §13 membawa byte version miliknya sendiri, independen dari SealedQub.version dan QubEnvelope.version. Kedua ruang versi berkembang secara terpisah: pengganti simetris pasca-kuantum yang aman di masa depan menaikkan byte pembungkus tanpa menyentuh versi protokol dalam, dan tambahan lapisan-protokol di masa depan (mis., field envelope baru) menaikkan versi dalam tanpa menyentuh byte pembungkus.
OUTER_WRAPPER_VERSION_* |
Nilai | Algoritma | Status |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM dengan nonce 12 byte, tag autentikasi 16 byte, AAD terikat ke qub_id |
default v1 |
| — | 0x02–0xFF |
Dicadangkan | Masa depan |
Pembaca HARUS menolak versi pembungkus yang tidak dikenal dengan error yang jelas. Protokol secara sengaja menjaga ruang versi pembungkus tetap sempit hingga pendorong migrasi konkret muncul (mis., panduan NIST yang menyukai AEAD berbeda); slot 0x02 akan dialokasikan dalam revisi yang sama yang memperkenalkan algoritma tersebut.
13. Pembungkus Enkripsi Luar
13.1 Dasar Pemikiran
Lapisan protokol (QubEnvelope → tlock → SealedQub) membuat qub tersegel terkunci waktu: body tidak dapat dibaca hingga unlock_at dan tanda tangan putaran drand telah diterbitkan. Namun setelah unlock, tanda tangan putaran bersifat publik dan bentuk CBOR kanonis dari SealedQub dapat dikenali, sehingga pemanen yang mengindeks transaksi penyimpanan permanen dapat mendekripsi massal seluruh korpus qub.
Pembungkus enkripsi luar menutup saluran tersebut dengan menyisipkan lapisan AEAD simetris tambahan antara SealedQubCbor kanonis dan byte yang ditulis ke penyimpanan permanen. Kunci 256-bit K hidup hanya di fragment URL dari tautan pengiriman dan pada perangkat pengguna; browser tidak mentransmisikan fragment URL ke server, sehingga qub.social, setiap gateway penyimpanan, dan setiap CDN di depan keduanya secara observasional buta terhadap K. Setiap qub di penyimpanan permanen karena itu adalah ciphertext opak yang plaintext-nya tidak dapat dipulihkan tanpa URL yang dipilih pembuat untuk dibagikan.
Efek bersih:
- Kekebalan enumerasi secara default. Byte yang dibungkus di penyimpanan permanen tidak dapat dibedakan-byte dari ciphertext sembarangan. Strategi pemanen "GraphQL-query untuk upload berbentuk qub, dekripsi massal dengan tanda tangan drand publik" tidak berakhir dengan plaintext.
- Postur privasi crypto-shredding. qub.social secara harfiah tidak dapat mendekripsi korpusnya sendiri. Subpoena mencapai ciphertext, bukan plaintext.
- Tangga kerahasiaan dua-tier. Default = akses dikontrol-tautan (bagian ini). qub privat yang dienkripsi-penerima (fitur Fase 2 yang dicadangkan, belum dispesifikasi) menumpuk di atas sebagai tier kedua.
13.2 Pelapisan
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)
Penyegelan dan unlock pada lapisan protokol (§7, §8) tidak berubah di bawah batas pembungkus; pembungkus melekat pada situs panggilan seal() dan lepas pada situs panggilan unlock().
13.3 Struktur Data 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
}
Invarian field.
versionHARUS sama dengan0x01untuk byte pembungkus v1.0.qub_idHARUS sama dengan fieldqub_iddari SealedQub yang dipulihkan setelah pembungkus dibuka. Langkah pembukaan tidak menegakkan ini secara langsung (pengikatan AEAD AAD membuat manipulasi tingkat byte tidak mungkin), tetapi lapisan unlock memeriksa relasi secara transitif: jika pembuat membungkusSealedQubCboryangqub_iddalamnya tidak cocok denganqub_idpembungkus, §8 langkah 11 gagal.nonceHARUS 96 bit (12 byte), dihasilkan baru oleh CSPRNG untuk setiap operasi pembungkusan. Menggunakan ulang nonce di bawah kunci yang sama mengizinkan serangan penggunaan-ulang-nonce AEAD yang memulihkan plaintext; produsen HARUS memperlakukan pasangan (key,nonce) sebagai sekali-pakai.ciphertextadalah output AES-256-GCM: byte ciphertext yang dikonkatenasi dengan tag autentikasi 16 byte.ciphertext.len() == SealedQubCbor.len() + 16tepat.
Pengkodean CBOR. CBOR kanonis per §3, dengan aturan urutan-key yang sama (diurutkan menurut panjang byte terenkode menaik, kemudian secara leksikografis). Empat key tersebut adalah:
| Key | Byte terenkode | Urutan |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
Byte pertama dari OuterWrapper CBOR karenanya adalah header map panjang-pasti untuk map 4-entri (0xA4).
13.4 Pengikatan AAD ke qub_id
Pembungkus mengikat qub_id sebagai additional authenticated data AEAD. Ini adalah pertahanan struktural yang menjadi tumpuan terhadap tiga kelas serangan:
| Serangan | Pertahanan |
|---|---|
Memindahkan ciphertext ke bawah field qub_id yang berbeda di pembungkus |
AAD tidak cocok → autentikasi AEAD gagal |
| Mencampur fragment URL dari qub A dengan byte penyimpanan permanen dari qub B | AAD tidak cocok → autentikasi AEAD gagal |
Memanipulasi field qub_id pembungkus setelah upload |
AAD tidak cocok → autentikasi AEAD gagal |
Membawa qub_id dalam plaintext pembungkus tidak secara berarti melemahkan kekebalan enumerasi — qub_id sendiri adalah hash SHA3-256 dari preimage §4.1 tanpa preimage yang dapat dipulihkan dari digest, dan seorang enumerator yang sudah memanen byte pembungkus tidak belajar apa pun dari qub_id yang terlihat yang tidak bisa mereka simpulkan dari keberadaan upload itu sendiri.
13.5 Algoritma Wrap dan Unwrap
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
Peluruhan mode-kegagalan. K salah, nonce salah, AAD tidak cocok, dan ciphertext yang dimanipulasi semuanya menghasilkan error DECRYPT_FAILED yang sama. Ini adalah properti AEAD yang disengaja: membedakan mode kegagalan akan menciptakan saluran samping yang dapat diuji penyerang jarak jauh dengan mengirim pembungkus yang cacat dan mengukur waktu respons. Implementasi referensi HARUS menyusutkan semua kegagalan AEAD ke satu bentuk error.
13.6 Material Kunci dan Distribusi
Kunci pembungkus K adalah nilai acak seragam 256-bit yang dihasilkan per-qub oleh CSPRNG. Implementasi referensi mengambilnya dari:
- Pembuat WASM:
getrandom(WebCrypto di bawah backendwasm_js). - Rute seal sisi-server Worker:
crypto.getRandomValues.
Distribusi: K HARUS dikodekan sebagai base64 URL-safe (RFC 4648 §5, tanpa padding) dan ditambahkan ke tautan pengiriman sebagai komponen fragment:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragment tidak pernah ditransmisikan ke server mana pun oleh browser yang mengikuti standar. Saluran pemulihan (indeks riwayat sisi-server, kirim-otomatis email opt-in) yang menyimpan tautan pengiriman lengkap — termasuk fragment — di luar perangkat pengguna adalah trade-off eksplisit terhadap postur crypto-shredding default dan HARUS digerbangi pada persetujuan pengguna eksplisit.
Kehilangan fragment. Jika pengguna kehilangan fragment URL dan tidak memiliki saluran pemulihan, qub tidak dapat dibaca. Ini adalah trade-off yang menjadi tumpuan desain dan HARUS diungkapkan kepada pengguna pada saat penyegelan. MVP memperkuat pengungkapan saat-penyegelan dengan teks "simpan URL ini" yang eksplisit dan saluran pemulihan email-terverifikasi untuk pengguna yang opt-in.
13.7 Di Luar Cakupan untuk Bagian Ini
- Penandatanganan kepenulisan (§9) tidak berubah: tanda tangan dihitung di dalam
QubEnvelopedalam dan dipulihkan setelah unwrap → tlock decrypt → CBOR parse. - qub privat yang dienkripsi-penerima (fitur Fase 2 yang dicadangkan, belum dispesifikasi) tersusun di atas pembungkus ini sebagai tier kerahasiaan kedua; kedua tier dapat aktif secara bersamaan.
- Pakta (§6, content_type
0x03) dibungkus persis seperti qub teks; pembungkus buta-byte terhadap content type dalam.
13.8 qub publik (penghilangan pembungkus)
Pembungkus luar bersifat opsional pada lapisan pengiriman. Pembuat dapat menyegel qub sebagai publik, dalam hal ini SealedQubCbor kanonis ditulis ke penyimpanan permanen secara langsung, tanpa lapisan OuterWrapper dan tanpa kunci K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
qub publik bersifat terkunci-waktu tetapi tidak digerbangi-tautan: ia tetap tidak dapat dibaca hingga putaran drand-nya diterbitkan (lapisan tlock tidak berubah), tetapi setelah unlock siapa pun yang memiliki arweave_tx_id dapat mendekripsinya — tidak diperlukan fragment URL, karena tidak ada K. Ini adalah trade-off yang disengaja untuk permukaan yang harus digerakkan server: email pemberitahuan-pengungkapan, sematan pihak ketiga, dan SEO pasca-pengungkapan yang lebih kaya semuanya memerlukan tautan yang berfungsi tanpa rahasia yang tidak pernah dipegang server (§13.6).
Konsekuensi yang HARUS diperhitungkan produsen:
- Tidak ada kekebalan enumerasi. qub publik melepaskan properti kekebalan-enumerasi §13.1 secara konstruksi. Layanan upload referensi menempelkan tag penyimpanan-permanen
Visibility: publicpadanya (dan hanya padanya) sehingga keduanya sengaja dapat ditemukan; qub privat tidak membawa tag semacam itu dan mempertahankan ketidak-dapat-dibedakan-byte-nya. - Judul plaintext terpapar pada saat penyegelan. Field
title§3.2 adalah plaintext di dalamSealedQubCbor. Di bawah pembungkus, ia disembunyikan hingga pembaca menyuplaiK; tanpa pembungkus, ia dapat dibaca siapa saja di penyimpanan permanen sejak momen upload, sebelum unlock. Aplikasi pembuat yang patuh HARUS mengungkapkan ini pada saat penyegelan. - Deteksi bersifat struktural. Pembaca/sematan yang patuh membedakan kedua bentuk melalui parse: byte yang ter-parse sebagai
OuterWrappermenempuh jalur unwrap-dengan-K; byte yang ter-parse sebagaiSealedQubCbortelanjang diterima secara langsung. Tidak diperlukan flag pada wire, danqub_idtidak mengikat visibilitas — konten yang sama identik-byte pada lapisanSealedQubbaik disegel publik maupun privat.
Privat (terbungkus) tetap menjadi default; publik adalah pilihan pembuat per-qub yang eksplisit.
14. Vektor Uji
14.1 Derivasi 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
Implementasi HARUS menghasilkan nilai body_hash dan qub_id yang identik untuk input ini. Vektor uji ini SEBAIKNYA menjadi unit test pertama yang ditulis. Nilai kanonis di atas dihitung oleh implementasi referensi dan HARUS cocok bit demi bit. Tata letak preimage historis (pra-peluncuran — tidak ada qub langsung yang bergantung padanya): qub_id V1.0 92-byte adalah 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; qub_id V1.1 100-byte (setelah melipat outcome_at_or_zero) adalah b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 melipat drand_round ke dalamnya dan menaikkan pemisah domain menjadi QUB_ID_V2.
14.2 Pemetaan Unlock-Round
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 Round-Trip CBOR Kanonis
Implementasi HARUS memverifikasi bahwa serialize(parse(serialize(qub))) == serialize(qub) untuk semua input yang valid. Ini adalah property test, bukan vektor tunggal.
14.4 CBOR PactTerms (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)
Byte CBOR kanonis dan body_hash SHA3-256 dihitung oleh implementasi referensi. Implementasi HARUS menghasilkan CBOR yang identik byte demi byte untuk input ini.
Implementasi juga HARUS memverifikasi bahwa serialize(parse(serialize(pact))) == serialize(pact) untuk semua input PactTerms yang valid (property test).
14.5 Vektor Lintas-Bahasa Pembungkus Luar
Pembungkus luar (§13) memiliki fixture kanonis terpisah di crates/qub-core/tests/vectors/wrapper_v1.json. Setiap kasus menetapkan tuple (key, nonce, qub_id, sealed_cbor) sebagai input hex opak dan menegaskan output expected_wrapper_hex tertentu. Kedua implementasi referensi mengonsumsi file JSON yang sama:
- 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).
Fixture saat ini mematok tiga kasus:
| Kasus | Cakupan |
|---|---|
basic-text-public |
Bentuk SealedQub realistis terkecil; tanpa field opsional. Menetapkan bentuk pembungkus kanonis untuk qub tipikal v1.0. |
with-recipient-pubkey |
SealedQub dengan recipient_pubkey diatur (jalur Fase 2). Set key CBOR dalam berbeda, qub_id berbeda. |
longer-body |
Body ~4 KiB — melatih prefiks panjang CBOR multi-byte di dalam envelope dalam dan ciphertext luar. |
Implementasi HARUS menghasilkan expected_wrapper_hex yang identik byte demi byte untuk input yang direkam. Regenerasi fixture memerlukan QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors dan dicadangkan untuk perubahan format yang disengaja.
15. Tata Kelola Profil Kripto (Masa Depan)
Bagian ini bersifat informatif untuk v1 dan menjadi normatif pertama kali algoritma kedua masuk ke salah satu primitif kriptografis qub.
15.1 Postur Saat Ini
Protokol v1 mengikat tepat satu algoritma per primitif:
- Tanda tangan: ML-DSA-65 (
sig_alg = 0x01; kunci publik 1952 byte, tanda tangan 3309 byte) dan tanpa tanda tangan (sig_alg = 0x00). Registri §9.2 tidak mendefinisikan nilai lain; verifier v1 HARUS menolak setiapsig_algdi luar{0x00, 0x01}. Entri Ed25519 di masa depan diantisipasi (§15.3) tetapi tidak dialokasikan di v1. - Timelock: Hanya drand quicknet — hash chain, kunci publik, waktu genesis, dan periode adalah parameter jaringan tetap yang dibawa oleh referensi
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) danconfig/drand-endpoints.json. - Pembungkus luar: Hanya AES-256-GCM v1 (§13).
Verifier saat ini meng-hardcode panjang kunci dan tanda tangan per primitif. Tidak ada permukaan agility yang diekspos oleh format wire.
15.2 Bentuk yang Diinginkan
Ketika algoritma kedua memasuki protokol, verifier akan dikonfigurasi untuk CryptoProfile bernama (mis., ExqubV1) yang mendaftar set persis nilai yang diizinkan per primitif — sig_algs, chain drand, versi pembungkus, content type. Profil ditetapkan pada waktu verifikasi, tidak pernah dinegosiasikan secara in-band. Setiap nilai di luar profil aktif ditolak.
Ini menjamin bahwa menambahkan ML-DSA-87 atau mengaktifkan Ed25519 tidak dapat secara retroaktif melemahkan konfigurasi verifier yang ada: verifier v1 tetap menjadi verifier v1 bahkan setelah profil v2 diterbitkan.
15.3 Kondisi Pemicu
Tingkatkan §15 ke status normatif ketika salah satu dari berikut diusulkan:
- Byte
sig_algkedua (aktivasi Ed25519, ML-DSA-87, atau entri baru di registri §9). - Chain drand kedua dalam penggunaan produksi.
- Versi pembungkus-luar kedua.
Sampai saat itu §15 adalah placeholder yang menetapkan bentuk migrasi sehingga PR di masa depan mendarat pada target yang diketahui daripada melitigasi ulang permukaan negosiasi dari awal.