Spesifikasi Protokol qub

qub adalah protokol untuk komitmen temporal kriptografis: sebuah sistem untuk menyegel kata-kata ke tanggal masa depan dan kemudian memverifikasi secara persis apa yang disegel, putaran drand mana yang menggerbangi rilisnya, dan—ketika transaksi penyimpanan atau bukti log transparansi tersedia—batas atas bercap waktu independen tentang kapan ciphertext dikomitmenkan.

Tiga primitif membuatnya bekerja. drand adalah beacon keacakan terdesentralisasi—tanggal pengungkapan ditegakkan secara kriptografis, bukan oleh niat baik qub. Penyimpanan tahan-lama ditambah log transparansi hanya-tambah mempertahankan bita tersegel dan menambatkan komitmen terbatch ke penyimpanan publik permanen; jalur T3 berbayar juga menulis transaksi penyimpanan permanen individual. ML-DSA-65 adalah tanda tangan digital pasca-kuantum—ketika kepenulisan diaktifkan, qub terikat pada pasangan kunci yang rahasianya tidak pernah meninggalkan perangkat penulis.

Bersama-sama primitif ini menghasilkan pernyataan yang terkunci waktu dan tahan-rusak, dapat diatribusikan secara opsional, dan dapat diberi cap waktu secara independen—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
Rilis dokumen 1.0.0 (protocol-v1.0.0)
Protokol wire 0x01
Pembungkus luar 0x01
Tanggal efektif 2026-09-23
Status Saat ini
Ditinjau hingga 2026-09-23

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,              // 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 (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>,     // 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
}

Baseline (qub teks tak bertanda tangan): version = 0x01, content_type = 0x01, sig_alg = 0x00; field tanda tangan dan penandatangan bersama tidak ada. Field metadata opsional lainnya boleh 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). Ini adalah artefak wire dalam: pengiriman publik menyimpan bita ini tanpa pembungkus, sedangkan pengiriman privat membungkusnya dalam OuterWrapper sebelum penyimpanan (§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 (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>,       // Carried from both wire layers; drives the verdict-watch block
    drand_chain_id:      String,
    drand_round:         u64,
    sender_label:        Option<String>,
    title:               Option<String>,    // Carried forward from SealedQub.title
    reply_to:            Option<[u8; 32]>,
    body:                Vec<u8>,
    body_hash:           [u8; 32],
    body_hash_verified:  bool,
    author_signature:    Option<Vec<u8>>,
    author_pubkey:       Option<Vec<u8>>,
    signature_verified:  Option<bool>,
    cosigner_pubkey:     Option<Vec<u8>>,
    cosigner_signature:  Option<Vec<u8>>,
    cosigner_verified:   Option<bool>,
}

3. Profil 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.
Key tidak dikenal Tolak dengan error. Parser TIDAK BOLEH menoleransi key map di luar himpunan key kanonis milik tipe tersebut — dua byte string kanonis yang berbeda tidak boleh didekodekan ke nilai yang sama (encode(decode(x)) == x), dan pada payload bertanda tangan, sebuah key ekstra akan menjadi konten tersembunyi yang ikut di-commit oleh kedua tanda tangan. Evolusi skema berjalan melalui version, tidak pernah melalui key ekstra.
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 (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 (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 (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: Revisi implementasi pra-rilis 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 putusan yang memotivasi field ini.

Pengkodean drand_round: Revisi implementasi pra-rilis yang lebih baru 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:

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 = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
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

Ini adalah pemetaan tlock referensi (CurrentRound milik drand). drand menerbitkan putaran N pada chain_genesis_time + (N - 1) * chain_period_seconds, sehingga rumus memilih putaran yang berjalan pada unlock_at—putaran yang tanda tangannya pertama kali dapat digunakan oleh pembaca yang tiba pada unlock_at.

Properti penyelarasan (kasus yang penting dalam praktik): ketika (unlock_at - chain_genesis_time) habis dibagi tepat oleh chain_period_seconds, tanda tangan putaran yang dipilih diterbitkan tepat pada unlock_at, tidak pernah sebelumnya. Ini selalu berlaku untuk deployment referensi: waktu genesis quicknet (1692803367) habis dibagi periodenya yang 3 detik, dan aplikasi referensi mematok waktu buka kunci ke menit penuh. Untuk unlock_at yang tidak selaras, tanda tangan putaran yang dipilih diterbitkan kurang dari satu periode sebelum unlock_at—presisi temporal komitmennya adalah satu periode beacon.

Pemetaan pra-rilis lama dan toleransi sisi-unlock: pemetaan awal adalah ceil((unlock_at - chain_genesis_time) / chain_period_seconds), yang—untuk kasus selaras-periode di atas—memilih putaran yang diterbitkan satu periode penuh sebelum unlock_at, sehingga ciphertext dapat didekripsi tepat satu periode lebih awal. Kedua pemetaan berbeda tepat +1 ketika delta habis dibagi periode, dan sama dalam kasus lain. Karena drand_round dilipat ke dalam preimage qub_id yang tidak dapat diubah (§4.1), artefak yang disegel dengan pemetaan lama tidak dapat diturunkan ulang; verifier yang melakukan pemeriksaan silang putaran pada §8 langkah 6a karena itu HARUS menerima drand_round tersimpan yang sama dengan putaran turunan atau putaran turunan dikurangi satu (dan HARUS mewajibkan putaran stanza tlock sama persis dengan putaran tersimpan). Toleransi tersebut memperlebar tanda tangan penggerbang paling awal paling banyak satu periode. Layanan penyiapan pakta menerapkan toleransi yang sama saat menurunkan ulang qub_id pakta yang disiapkan (saat penyiapan dan penandatanganan bersama): jika putaran pemetaan saat ini tidak mereproduksi qub_id yang dikomitmenkan dan delta habis dibagi periode, layanan mencoba ulang dengan putaran dikurangi satu, lalu menyegel pakta final ke putaran yang benar-benar diikat oleh qub_id—tidak pernah secara buta ke putaran hasil hitung ulang, yang akan membuat artefak selamanya tidak dapat diturunkan.

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() Artefak wire dalam; disimpan tanpa pembungkus untuk pengiriman publik atau dibungkus untuk pengiriman privat, lalu dipulihkan oleh 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 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }

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:

  1. HTTPS saja. String HARUS dimulai dengan urutan byte https://. Skema lain — http, ftp, javascript, data, file, dsb. — ditolak.
  2. Batas panjang. ≤ 2.048 byte (batas praktis URL peramban).
  3. Pemeriksaan NFC + codepoint-bermusuhan. Aturan yang sama dengan title dan reflection — codepoint bidi-override / zero-width / tag-block / BOM / C0 / C1 ditolak. Definisi sesuai dengan Rust crate::handle::contains_hostile_text_codepoint dan TS workers/api/src/utils/unicode.ts::isHostileCodepoint (jaga agar selaras).
  4. Tidak ada whitespace, tidak ada kontrol ASCII. Whitespace / DEL / byte sub-0x20 di mana pun dalam URL ditolak — menutup vektor injeksi \n/\t yang tidak tercakup aturan bidi.
  5. 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.
    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.

Lapisan tag penyimpanan (di luar jalur). Layanan upload qub melampirkan sehimpunan kecil tag transaksi penyimpanan secara sengaja di samping payload unggah yang dipilih. 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 terhadap daftar izin—announcement, thesis, prediction, letter, secret, commitment, proof, atau verdict yang dikeluarkan sistem), 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. Untuk pengiriman privat, pembungkus luar (§13) mengenkripsi artefak SealedQub dalam yang dapat dikenali, sehingga memanen pembungkus tersimpan dan memperoleh tanda tangan drand publik tetap tidak cukup untuk memulihkan body tanpa K; tag penyimpanan tetap merupakan metadata publik secara sengaja.

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 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. 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 Status
0x00 Tanpa tanda tangan (tidak ditandatangani) — — Aktif
0x01 ML-DSA-65 (FIPS 204) 1.952 byte 3.309 byte Aktif
0x02 Ed25519 32 byte 64 byte Konstanta dicadangkan; tidak didukung dalam protokol v1

Pembaca protokol-v1 HARUS menolak setiap nilai di luar {0x00, 0x01}, termasuk nilai cadangan 0x02. Reservasi mencegah penggunaan ulang yang tidak disengaja; ini bukan aktivasi. Aktivasi memerlukan perubahan bertata kelola yang dijelaskan di §15.

9.3 Konstruksi Preimage yang Ditandatangani

Dua versi preimage pernah ada. Semua tanda tangan HARUS menggunakan V2, dan verifier HARUS menerima V2 saja. Preimage V1 lama (didokumentasikan di bawah untuk referensi historis) diterima sebagai fallback verifikasi-saja selama migrasi V2; fallback tersebut telah dihentikan dan tanda tangan V1-saja kini ditolak.

V2 (saat ini — dihasilkan oleh semua penandatanganan penulis baru, dan oleh kedua tanda tangan dari alur staging / tanda-tangan-bersama pakta):

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 mengikuti konvensi sentinel-absen yang sama dengan title_hash (§4.2.1): 32 byte nol bukan output SHA3-256 yang valid, sehingga "absen" tidak pernah bisa berbenturan dengan label yang hadir. Semua field berlebar-tetap, sehingga preimage tidak ambigu tanpa prefiks panjang.

V1 (lama — DIHENTIKAN; tidak lagi dihasilkan dan tidak lagi diterima pada verifikasi):

sig_input = SHA3-256(
    "QUB_AUTHOR_SIG_V1"  ||    // domain separator (17 bytes)
    version              ||    // u8 (1 byte)
    qub_id               ||    // [u8; 32] (32 bytes)
    body_hash            ||    // [u8; 32] (32 bytes)
    unlock_at            ||    // i64 big-endian (8 bytes)
    0x00                       // u8 (1 byte): MUST be 0x00 in v1.0
)

// Total preimage: 91 bytes → 32-byte hash

Preimage V1 menghilangkan sender_label dan reply_to. Ia diterima sebagai fallback verifikasi-saja selama migrasi ke V2; fallback tersebut sejak itu telah dihentikan — verifier HARUS menerima preimage V2 saja. Definisinya dipertahankan di sini untuk referensi historis dan untuk menjelaskan pemisah domain di bawah. Tanda tangan yang hanya terverifikasi terhadap V1 HARUS diperlakukan sebagai kegagalan verifikasi.

Pemisah domain: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" masing-masing adalah 17 byte ASCII ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Tanpa padding. Pemisah yang berbeda memisahkan-domain kedua konstruksi tersebut, sehingga tanda tangan atas satu preimage tidak pernah bisa terverifikasi sebagai yang lain.

Byte org_id_present: byte yang mengikuti unlock_at 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 V2 melakukan commit secara langsung pada version, qub_id, body_hash, unlock_at, sender_label, dan reply_to (ditambah pemisah domain tetap dan byte org_id_present). 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 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
sender_label ✓ Input langsung via sender_label_hash (preimage V2 — satu-satunya bentuk yang diterima)
reply_to ✓ Input langsung via reply_to_or_zero (preimage V2 — satu-satunya bentuk yang diterima)
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
body ✓ Secara transitif, lewat body_hash = SHA3-256(body)
author_pubkey — (implisit) Kunci yang memverifikasi tanda tangan adalah penulis, secara definisi
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.)

Mengapa V2 adalah satu-satunya preimage yang diterima.

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 (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."

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:

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 (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."

Properti:

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

10.2 Elemen yang Dilarang

Elemen Penanganan
HTML mentah (<div>, <script>, dll.) Dihapus sepenuhnya. Tidak ada HTML yang lolos.
Gambar (![alt](url)) 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:

  1. Parsing Markdown menggunakan pulldown-cmark (atau setara).
  2. Telusuri AST dan buang setiap node yang tidak ada dalam allowlist (§10.1).
  3. Untuk node tautan: keluarkan URL sebagai teks yang terlihat, bukan sebagai elemen <a> yang dapat diklik.
  4. Konversikan AST yang difilter ke representasi perantara bertipe (mis., enum MarkdownNode dengan hanya varian yang aman). HTML mentah secara struktural tidak dapat direpresentasikan dalam IR ini.
  5. Render dari IR bertipe ke lapisan tampilan target (mis., komponen tampilan reaktif, node DOM). Tanpa konkatenasi string HTML atau innerHTML di 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


11. Verifikasi Pihak Ketiga

Pihak ketiga mana pun yang memegang bita tersimpan (dan K untuk qub privat/terbungkus) dapat memverifikasi artefak kriptografis tanpa kerja sama qub. Klaim keberadaan yang diberi cap waktu secara independen juga memerlukan inklusi penyimpanan permanen per-qub yang terverifikasi atau bukti log transparansi §16 yang terverifikasi.

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.

Apa yang dibuktikan oleh verifikasi:

Input bukti Apa yang ditetapkannya
Bundel / artefak tersegel yang valid + tanda tangan drand Badan yang dipulihkan cocok dengan body_hash; metadata yang terikat ke qub_id utuh; ciphertext terikat ke putaran drand yang dinyatakan; dan putaran tersebut telah berlalu. Ini tidak menetapkan kapan ciphertext dibuat.
Tanda tangan penulis/penandatangan bersama V2 yang valid Pemegang kunci rahasia yang bersangkutan mengautentikasi permukaan bertanda tangan pada §9.3.
Transaksi penyimpanan per-qub yang diverifikasi secara independen Ciphertext tersimpan yang persis sudah ada paling lambat pada cap waktu bloknya.
Bukti log transparansi tertambat yang valid Klaim khusus-jenis-daun pada §16.11, termasuk waktu komitmen batas atas dari blok tambatan.

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 Artefak membuktikan bita dan hubungan kriptografis, bukan apa yang dimaksudkan pembuat secara subjektif.
Komitmen yang sudah ada sebelumnya hanya dari .qub Pembuat dapat menyusun bundel valid setelah putaran terikat berlalu. Tanda tangan drand yang disematkan membuktikan bahwa putaran telah berlalu, bukan bahwa ciphertext sudah ada sebelumnya.
Waktu tombol-segel yang persis Cap waktu blok penyimpanan atau tambatan adalah batas atas yang dapat diverifikasi secara independen dan dapat tertinggal dari tindakan lokal pengguna. Asersi sealed_at / received_at tidak bersifat evidensial.

Log transparansi yang diimplementasikan (§16) memperluas verifikasi lintas qub dengan pengurutan tahan-rusak dan waktu komitmen batas atas nirpercaya (waktu blok tambatan), sesuai cakupan jenis daun (§16.11). Log tersebut tidak menambahkan kepenulisan atau maksud; untuk jalur unggah buta-bita default, log itu sendiri tidak membuktikan body_hash atau drand_round, yang tetap berasal dari pemeriksaan artefak.


12. Versi dan kontrol rilis

Rilis dokumen, protokol wire dalam, dan pembungkus luar merupakan ruang versi yang terpisah. Karena itu, klarifikasi khusus dokumen tidak secara diam-diam mengubah bita, dan migrasi wire masa depan tidak dapat menyamar sebagai revisi editorial.

12.1 Versi rilis dokumen

Spesifikasi ini menggunakan rilis dokumen semantik (MAJOR.MINOR.PATCH) dan tag Git yang tidak dapat diubah bernama protocol-v<release>.

Status rilis adalah salah satu dari Draf (belum normatif), Saat ini (satu-satunya target implementasi yang direkomendasikan), atau Digantikan (dipertahankan untuk verifikasi historis). Rute /protokol tanpa versi menampilkan rilis Saat ini; tag rilis mempertahankan sumber persisnya dan setiap lokal yang diterbitkan bersamanya. Perubahan status atau nomor rilis mewajibkan pembaruan tabel ini dan riwayat rilis dalam perubahan tertinjau yang sama.

Rilis dokumen Tanggal efektif Status Protokol wire Pembungkus Sumber
1.0.0 2026-09-23 Saat ini 0x01 0x01 protocol-v1.0.0

12.2 Versi protokol

Field version (u8) di kedua SealedQub dan QubEnvelope mengidentifikasi versi major protokol.

12.3 Riwayat versi protokol

Versi Nilai Deskripsi
v1 0x01 Pengiriman privat/terbungkus dan publik/tanpa pembungkus; badan teks (0x01), pakta (0x03), dan putusan (0x04); penandatanganan penulis/penandatangan bersama ML-DSA-65 V2; tlock quicknet drand; SHA3-256.

12.4 Kompatibilitas maju

Pembaca v1 yang menemui QubEnvelope dengan key map CBOR yang tidak dikenal (key yang tidak ada dalam urutan kanonis §3.2) HARUS menolaknya dengan error dekode (§3.1). Kompatibilitas maju bertumpu pada field version, bukan pada toleransi key: tambahan di masa depan — bahkan metadata minor — dirilis dengan nilai version yang baru, yang ditolak pembaca v1 dengan error "protokol lebih baru" yang jelas alih-alih secara diam-diam membuang konten yang ikut di-commit oleh tanda tangan.

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.5 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 Aktif untuk pengiriman privat
— 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.

Untuk pengiriman privat, pembungkus enkripsi luar menutup saluran tersebut dengan menyisipkan lapisan AEAD simetris tambahan antara SealedQubCbor kanonis dan bita tersimpan. Pada jalur segel-peramban, kunci 256-bit K hidup hanya di fragmen URL dari tautan pengiriman dan pada perangkat pengguna; peramban tidak mentransmisikan fragmen URL ke server, sehingga qub.social, setiap gateway penyimpanan, dan setiap CDN di depan keduanya secara observasional buta terhadap K. Representasi tersimpan qub privat karena itu adalah ciphertext buram yang teks polosnya tidak dapat dipulihkan tanpa URL yang dipilih pembuat untuk dibagikan. Pengiriman publik sengaja menghilangkan lapisan ini (§13.8).

Efek bersih:

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
  ├─ 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

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.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
}

Invarian field.

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 qub A dengan bita tersimpan qub B Kunci salah (dan AAD yang terikat secara independen) → 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
    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

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:

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)>

Fragmen tidak pernah ditransmisikan ke server mana pun oleh peramban yang mengikuti standar. Saluran pemulihan (indeks riwayat sisi-server, kirim-otomatis email opt-in) yang menyimpan tautan pengiriman lengkap—termasuk fragmen—di luar perangkat pengguna adalah pertukaran 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

13.8 qub publik (penghilangan pembungkus)

Pembungkus luar bersifat opsional pada lapisan pengiriman. Pembuat dapat menyegel qub sebagai publik, dalam hal ini SealedQubCbor kanonis masuk ke pipeline penyimpanan secara langsung, tanpa lapisan OuterWrapper dan tanpa kunci K:

SealedQubCbor bytes  ──(public)──▶  stored as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored

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 id transaksi penyimpanan 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, tautan oEmbed/sematan otomatis tanpa fragment, dan SEO pascapengungkapan yang lebih kaya semuanya memerlukan tautan yang berfungsi tanpa rahasia yang tidak pernah dipegang server (§13.6). qub privat tetap dapat menggunakan bentuk eksplisit <qub-embed src="full_delivery_url"> ketika penerbit memasok kapabilitas lengkap yang membawa fragment.

Konsekuensi yang HARUS diperhitungkan produsen:

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  = 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

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 prototipe pra-peluncuran historis (tidak ada qub langsung yang bergantung pada dua tata letak pertama) menggunakan 92 byte sebelum outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) dan 100 byte setelah menambahkan outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). Tata letak 108-byte saat ini kemudian menambahkan drand_round dan pemisah domain QUB_ID_V2. Vektor 108-byte awal menggunakan pemetaan putaran ceil lama (drand_round = 4695445) dan menghasilkan 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—tetap merupakan qub_id yang valid untuk input putaran tersebut, sedangkan contoh di atas mengikuti pemetaan putaran-saat-ini §4.3.

14.2 Pemetaan Unlock-Round

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

Putaran 4675286 diterbitkan pada 1595431050 + (4675286 - 1) * 30 = 1735689600—tepat pada unlock_at, tidak pernah sebelumnya. (Pemetaan ceil pra-rilis lama menghasilkan 4675285, yang diterbitkan pada 1735689570—30 detik lebih awal; verifier menerima putaran lama tersebut sesuai §4.3.)

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:

Fixture saat ini mematok tiga kasus pembungkus tingkat rendah. Kasus-kasus tersebut menguji pengodean OuterWrapper deterministik dan interoperabilitas AEAD secara terpisah dari invarian bentuk pengiriman §13.8; khususnya, nama historis basic-text-public dan visibility = 0x01 di bagian dalamnya tidak menjadikan bita terbungkus yang dihasilkan sebagai pengiriman publik yang sesuai. Produsen tetap HARUS menyimpan bita dalam publik tanpa pembungkus dan hanya membungkus bita dalam privat (0x00).

Kasus Cakupan
basic-text-public Nama historis fixture tingkat rendah. Bentuk SealedQub realistis terkecil tanpa field opsional; hanya menguji bita pembungkus dan bukan pengiriman tersimpan yang sesuai dengan §13.8.
with-recipient-pubkey SealedQub dengan recipient_pubkey diatur (jalur masa depan yang dicadangkan). Menguji set key CBOR dalam yang berbeda; konten fixture yang berbeda secara independen menghasilkan qub_id yang berbeda (recipient_pubkey itu sendiri tidak berada dalam preimage §4.1).
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:

Verifier saat ini meng-hardcode panjang kunci dan tanda tangan per primitif aktif. Byte sig_alg dan versi pembungkus merupakan pemilih eksplisit, tetapi v1 tidak melakukan negosiasi in-band dan hanya menerima nilai aktif di atas.

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:

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.


16. Log Transparansi dan Tingkatan Ketahanan (Diterapkan — review selesai)

Status. Bagian ini telah diterapkan (W5/UP-B1, Tahap 1–8), dengan jangkauan produsen dan trust-root yang dinyatakan di sini. Format wire, hashing, dan jalur verifikator aktif: tipe Merkle inti + canonical-CBOR (qub-core), cermin TypeScript + bundler ANS-104 (workers/api/src/crypto/), LogDO pencatat tunggal + store node R2 berbasis koordinat, upaya log-append /upload, cron anchor harian + bundler-drain, GET /api/v1/qub/:tx_id/proof (inklusif) dan GET /api/v1/log/consistency (RFC 9162) endpoints bukti, bukti inklusi bertipe yang dibawa dalam bundle .qub (§17.5), verifier anchor ANS-104 asli (tools/qub-verify), dan hook dual self-published-heads (§16.6). /upload yang berhasil selalu tahan R2 namun hanya tercakup log saat LOG_DO dikonfigurasi dan append inline berhasil; hanya saat itu responsnya membawa log_seq, receipt, dan anchor_status. Jika RECEIPT_SK tidak ada atau tidak valid, sig_b64url untuk tanda terima tersebut kosong dan tidak memberikan non-repudiasi. Jalur /seal dan publikasi pact saat ini menjadwalkan transaksi Arweave individual tetapi tidak menambahkan leaf log. Saat ini tidak ada kode yang melakukan rekonsiliasi yang diusulkan di komentar /upload setelah kegagalan append. Review eksternal W5 selesai: §16.15 mencatat keputusan desain dan batasan peluncuran, tetapi batasan tersebut tidak memperluas cakupan produsen yang baru dijelaskan. Tiga item trust/deployment masih dibatasi: (a) wallet anchor khusus (ANCHOR_JWK; LogProfile.anchor_owner masih placeholder [0xAB; 32]); (b) kunci tanda terima dan pin public-key yang sesuai (RECEIPT_SK opsional dan LogProfile.receipt_pubkey saat ini kosong); dan (c) repositori GitHub + token self-published-heads (§16.6). Hingga anchor/profile pins disediakan, verifier mandiri melaporkan status bukti secara jujur alih-alih mengklaim verifikasi yang sepenuhnya tertambat dan dipin. Desain bersifat aditif dan tidak ada perubahan pada format wire SealedQub / QubEnvelope.

16.1 Alasan dan Tingkatan Ketahanan

Jalur publikasi saat ini memisahkan pengakuan dari konfirmasi Arweave: mereka menurunkan dan menandatangani transaksi individual, menyimpan artefak dan status pengiriman tepat di R2, lalu memposting secara asinkron. Log transparansi menambahkan lapisan pengurutan yang tertambat secara independen untuk subset permintaan umum /upload yang append LogDO-nya berhasil:

Tingkat Nama Jaminan Kapan
T1 R2-ack sinkron pertama Lantai ketahanan — byte yang disegel dan status publikasi yang tepat ditulis ke penyimpanan tahan lama sebelum sukses dikembalikan. Diimplementasikan di jalur publikasi saat ini.
T2 Penyertaan log-transparansi dalam batch Komitmen hanya-tambah, yang terbukti-tamper + pengurutan total begitu dimasukkan dan ditambatkan. Produser saat ini: tambahan LogDO yang berhasil dari /upload; respons membawa tupel tanda terima. Tidak universal.
T3 Permanensi Arweave per-qub Transaksi Arweave individual untuk qub. Saat ini dipersiapkan untuk setiap publikasi yang diterima dan diposting secara asinkron; transaksi bertanda tangan yang tepat tetap berada di kotak keluar yang dapat dikuras sampai dikirim.

Tingkat-tingkat ini menjelaskan sifat bukti dan ketahanan yang berbeda, bukan rencana komersial saat ini. Kode saat ini masih menjadwalkan transaksi Arweave individual untuk setiap publikasi yang diterima; ini tidak mengekspos T3 hanya sebagai upsell berbayar. Batas kuota API-key/akun tetap merupakan pengendalian aplikasi yang terpisah.

Kejujuran ketahanan. Penulisan T1 bersifat sinkron, sehingga respons yang sukses menetapkan ketahanan tingkat aplikasi tanpa menunggu gateway Arweave. Ini sendiri tidak menetapkan cap waktu independen. Transaksi individual yang dikonfirmasi memberikan batas atas waktu bloknya. Untuk respons yang membawa tupel tanda terima T2 lengkap, tawan yang dikonfirmasi berikutnya dapat menyediakan bukti log yang dijelaskan di bawah. Jika tupel tidak ada, tidak ada permukaan yang dapat menyiratkan bahwa qub ini sudah ada di log transparansi. Tambat dan latensi publikasi tidak memiliki SLA numerik tingkat protokol.

16.2 Struktur LogLeaf (dua bentuk yang dikomit)

Entri log adalah LogLeaf, dikodekan sebagai CBOR kanonik tulisan tangan berdasarkan profil §3.1 (panjang pasti, tanpa tag, tanpa angka desimal, bilangan bulat bentuk terpendek, teks NFC, bidang opsional dihilangkan ketika tidak ada, kunci diurutkan berdasarkan panjang byte terkode yang menaik lalu per byte). Penjaga kanonik §3.1 parse → re-encode → compare diterapkan pada jalur encode sebelum hashing (bukan hanya saat decode), sehingga dua implementasi tidak bisa berbeda pada byte daun melalui perbedaan lebar integer atau urutan kunci. Semua bilangan bulat adalah u8 / u64 / i64; semua digest adalah string byte 32-byte (bstr[32]). ID transaksi Arweave yang disimpan adalah digest SHA-256 mentah 32-byte yang dibawa sebagai bstr[32], tidak pernah string teks base64url (sesuai §3.3).

Daun memiliki dua bentuk yang dipilih oleh sebuah byte kind, karena jalur unggah umum tidak memperhatikan byte: POST /api/v1/upload sengaja memperlakukan kedua bentuk payload yang diterima sebagai opak dan hanya menerima qub_id dan unlock_at sebagai pernyataan klien yang tidak terpercaya. Pada jalur privat default, body_hash, drand_round, created_at, dan drand_chain_version juga disembunyikan di dalam pembungkus luar §13, yang kuncinya tidak pernah dimiliki oleh Worker. Sistem tipe juga mendefinisikan bentuk yang diakui untuk produsen yang menurunkan body_hash / drand_round sendiri. Jalur /seal saat ini memiliki nilai-nilai tersebut tetapi tidak memanggil LogDO, sehingga produksi saat ini hanya menghasilkan daun yang ditegaskan (0x02) dari append unggahan umum yang berhasil. Pemisahan ini menjaga setiap nilai yang dikomit tetap jujur tanpa berpura-pura produsen yang diakui terhubung:

Kunci Enc. len Tipe Kehadiran Arti
seq 4 u64 diperlukan indeks daun Global berbasis 0; posisi yang dikomitmenkan bukti inklusi.
kind 5 u8 diperlukan 0x01 yang mampu disahkan (didefinisikan, belum dikirimkan saat ini) atau 0x02 diklaim (client-seal / unggahan byte-blind).
ref 4 bstr[32] diperlukan ID referensi Daun. Disahkan → qub_id mentah. Diklaim → id blinded SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1).
chash 6 bstr[32] SHA3-256(stored_bytes) alamat Content yang diperlukan — satu-satunya ikatan konten yang selalu dapat dihitung dengan jujur oleh Pekerja, di kedua jalur.
unlock_at 10 i64 diperlukan Disalin (disahkan) atau ditegaskan (diafirmasikan); divalidasi > 0 sebelum masuk ke daun.
received_at 12 i64 diperlukan jam dinding pekerja di R2-ack. Non-bukti (diklaim operator; §16.6). Hadir untuk deskripsi diri, tidak pernah menjadi bukti. Divalidasi > 0.
body_hash 10 bstr[32] kind=0x01 hanya Dihilangkan pada 0x02 — Pekerja tidak memilikinya berdasarkan §13.
drand_round 12 u64 kind=0x01 hanya dihilangkan pada 0x02.

Sebuah daun kind=0x02 sengaja tidak melakukan commit body_hash maupun drand_round: ia mencatatkan komitmen dan urutan ciphertext yang tidak transparan pada chash alamat-konten, mengklaim qub_id dan unlock_at — bukan plaintext atau round-nya. Plaintext/round leg untuk qub yang diajukan berasal dari verifikasi .qub-bundle §11 yang sudah ada, bukan dari log (§16.11). drand_chain_version tidak ada di leaf (berada di dalam pembungkus pada jalur default); granularitas rantai hidup pada anchor (§16.7). Disiplin encoder: tolak ref atau chash nol semua, dan tolak unlock_at / received_at non-positif, mencerminkan penjaga sentinel outcome_at > 0 di cbor.rs.

16.2.1 Private-qub blinding

Log tidak boleh menjadi oracle enumerasi yang ada untuk mencegah wrapper luar §13 (§13.1). Untuk qub privat (dibungkus), asserted leaf mengikat blinded identifier SHA3-256(qub_id ‖ log_blind_secret), di mana log_blind_secret adalah rahasia yang dipegang server, dan menghilangkan body_hash. Pihak ketiga tidak dapat mengaitkan leaf tersebut dengan qub_id tertentu; pemegang qub, yang memiliki URL pengiriman dan karenanya qub_id, dapat menghitung ulang blind untuk mengonfirmasi inklusi mereka sendiri. Sebuah qub publik (sudah enumerable, sudah membawa tag Visibility: public Arweave sesuai §13.8) mengikat qub_id secara mentah. Ini adalah satu-satunya tempat verifikasi mandiri sengaja memberi jalan pada invariant privasi yang memikul beban; kaitan mandiri untuk qub privat adalah chash (§16.9).

log_blind_secret custody (resolved — §16.15 Q4). Blind melindungi unlinkability leaf, bukan kerahasiaan plaintext (wrapper §13 menanganinya secara independen). Pada kompromi log_blind_secret, untuk setiap qub_id yang sudah dipegang atau dapat direkonstruksi oleh lawan (setiap qub yang bundle/URL-nya dimiliki, ditambah qub_id entropy rendah atau publik), adversary menghitung ulang leaf ref dalam satu hash dan mengaitkannya — ini adalah kaitan langsung dari populasi yang diketahui, bukan brute-force atas ruang yang tidak diketahui. Klasifikasikan log_blind_secret sebagai rahasia korelasi/Sybil-grade dalam tingkat custody yang sama dengan rahasia server lainnya, dan hanya lakukan rotasi ke depan (rotasi akan re-blind leaf di masa mendatang; tidak dapat membatalkan linkage leaf yang sudah terjangkar).

16.3 Leaf dan Node Hashing

RFC 6962 §2.1 domain-separated hashing dengan SHA-256 diganti oleh 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

Byte prefix domain 0x02 (entry chain, §16.4) dan 0x03 (STH hash, §16.6) dicadangkan dan berbeda dari ini. Mereka adalah single byte sehingga tidak dapat bertabrakan dengan pemisah domain ASCII 10-byte yang ada (QUB_ID_V2, dll.). Pohon adalah RFC 6962 left-full unbalanced tree (setiap split interior pada pangkat dua terbesar yang secara ketat kurang dari jumlah leaf subtree), yang memungkinkan bukti inklusi dan konsistensi berbagi satu algoritma audit-path. Spesifikasi referensi memuat pseudocode derivasi kiri/kanan yang eksplisit dan menetapkan non-power-of-two (5-leaf) test vector sehingga kasus promosi tepi-kanan — yang disembunyikan oleh vector 4-leaf — diuji.

16.4 Hash Chaining (internal)

LogDO menjaga entry chain internal hanya untuk crash-consistency. Ini tidak pernah diterbitkan dan tidak pernah menghadap verifier:

entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1]  = SHA3-256("QUB_TLOG_GENESIS_V1")

Otoritas yang dipublikasikan hanya lampiran adalah akar Merkle kumulatif + jangkarnya (§16.5–16.6), tidak pernah urutan mentah di mana operator kebetulan melayani berleda: rantai menghitung ulang untuk setiap urutan yang dilayani, sehingga hanya pin akar yang berjangkar yang berada di posisi kanonik.

16.5 Pohon Merkle Kumulatif dan Pengolahan

Ada satu pohon RFC 6962 yang terus berkembang di semua daun dalam urutan seq — bukan pohon terisolasi per batch. (Konstruksi rantai daun bawa per batch ditolak: ini bukan relasi awalan yang benar, sehingga "bukti konsistensi"nya tidak masuk akal.) Pohon kumulatif memberikan bukti konsistensi RFC 9162 yang asli dan memungkinkan satu anchor terbaru untuk inklusi bukti untuk qub lama mana pun.

Objek Durable LogDO adalah single writer (blockConcurrencyWhile, mirroring QuotaDO / EntitlementDO) — menambahkan ke log bersama adalah read-modify-write pada shared state sehingga HARUS melalui DO, bukan KV. Ia menyimpan cache di batas tepi kanan pohon (hash O(log n)) sehingga menutup batch adalah O(batch). batch adalah set daun yang diikat bersama; trigger yang diimplementasikan adalah kemajuan tree_size minimal LOG_BATCH_MAX_LEAVES (default 4096), usia mencapai kadensa anchor, atau penutupan paksa administratif/cron yang eksplisit. root_i adalah Merkle Tree Hash kumulatif di atas daun 0 .. tree_size_i.

16.6 Kepala Pohon Bertanda Tangan melalui Arweave Anchor

Transaksi anchor Arweave adalah Signed Tree Head dan menggantikan tanda tangan operator untuk tree head itu sendiri: anchor harian tidak membutuhkan kunci qub karena owner Tx Arweave adalah tanda tangan. Tesis parit berlaku — substrat yang tidak dapat diubah, bukan rahasia qub-hold, adalah penyangga beban untuk akar yang diikat.

Desain log memerlukan satu kunci tambahan hangat yang berhasil tombol penerimaan (§16.10), yang dipin dalam LogProfile dan ditandatangani silang oleh anchor_owner. Implementasi saat ini belum menyelesaikan penyediaan akar kepercayaan tersebut: RECEIPT_SK opsional, kunci yang tidak ada/tidak valid menghasilkan sig_b64url: "", dan LogProfile.receipt_pubkey yang dikompilasi kosong. Tanda terima seperti itu dapat menggambarkan daun yang dilampirkan tetapi bukan tanda terima yang tidak dapat ditolak. Klaim desain yang lebih kuat hanya berlaku setelah verifikator melepaskan pin kunci publik yang sesuai dan pemilik anchor menandatanganinya silang. Respons publikasi tanpa tuple penerimaan lengkap tidak membuat klaim penerimaan log; yang memiliki tanda tangan kosong membuat klaim posisi lampiran tetapi tidak klaim verifikasi tanda tangan.

SignedTreeHead tersebut adalah CBOR kanonik (kunci berdasarkan panjang yang dikodekan): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (sth_hash sebelumnya; genesis = 32 byte nol), log_id:bstr[32], first_seq:u64, anchored_at:i64. Hash-nya adalah sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).

root kepercayaan yang disematkan. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Verifier yang sesuai HARUS memerlukan anchor_tx.owner == LogProfile.anchor_owner, di mana anchor_owner (dan kunci publik kunci penerimaan) dimasukkan ke dalam qub_core sebagai LogProfile — bersama dengan konstanta quicknet yang sudah ada di DrandTimelockProvider::quicknet() — dan didistribusikan bersama biner verifier. Verifier juga HARUS memverifikasi pengikat→ tx_id an data data Arweave secara lokal daripada mempercayai respons gateway /raw/. Ini menutup lubang ambiguitas dompet jahat: "anchored on Arweave" tidak berarti sampai verifier menyematkan dompet mana.

Rotasi adalah ekstensi tata kelola §15, bukan penggunaan ulang (diselesaikan — §16.15 Q3). Permukaan profil §15.2 saat ini hanya mencantumkan rantai sig_algs / rantai DRAND / versi pembungkus / tipe konten, dan pemicu §15.3 tidak mencantumkan satupun dari ini — LogProfile / anchor_owner belum dalam permukaan §15. Tata kelola rotasi oleh karena itu harus dibangun: §15.3 diperluas (di bawah) untuk menambahkan pemicu LogProfile, dan rotasi adalah LogProfile bump yang ditandatangani yang dikirim dalam pembaruan verifier. Rotasi yang direncanakan melakukan tanda silang keluar → masuk; Rotasi yang didorong kompromi tidak dapat (kunci keluar tidak dapat dipercaya/tidak tersedia tepat saat itu) dan kembali ke bump yang diatur §15, dengan pemeriksaan fork prev-anchor (di bawah) membatasi kerusakan sementara waktu.

Jendela ambiguation (parameter kepercayaan kelas satu). Sebuah daun hanya tahan keraguan setelah jangkar penutupnya dikonfirmasi oleh Arweave. Jendela tersebut received_at → anchor confirmation (kadensa + finalitas Arweave, tanpa jaminan latensi protokol). Sebelum penyediaan akar kepercayaan, implementasi saat ini menyediakan integritas operasional qub plus metadata tambahan yang belum ditandatangani yang ada; tidak menyediakan jaminan non-penolakan yang direncanakan. Tiga artefak akuntabilitas mendefinisikan desain yang selesai (model saksi adalah resolusi §16.15 Q2):

  1. Tanda terima segel (tergantung pada penyediaan) — analog SCT dikembalikan ketika penambahan log unggahan berhasil (§16.10). Ini menjadi tidak dapat disangkal hanya ketika sig_b64url tidak kosong dan hubungan kunci publik/pemilik jangkar yang sesuai dipasang di verifier. Pin profil produksi yang saat ini kosong tidak dapat mendukung putusan itu. Kontrol ini tidak berlaku untuk tuple tanda terima yang dihilangkan atau tanda terima yang tidak ditandatangani.
  2. Metodologi pemantauan yang dipublikasikan + berjalan pada rantai sebelumnya — rantai jangkar prev dilalui dari kepala→genesis; sebuah percabangan (dua jangkar pada satu size dengan root berbeda, atau prev yang rusak) dapat menjadi bukti publik dari perilaku buruk. Deteksi perselisihan adalah komitmen operasional yang dinyatakan, bukan asumsi diam.
  3. Kepala yang diterbitkan sendiri ganda — setiap kepala baru {sth_hash, tree_size} diposting ke repository GitHub publik, hanya-tambahkan yang dimiliki qub (bagian publikasi sendiri yang tahan-rusak dan penopang beban), dengan posting sosial hanya sebagai upaya terbaik untuk menguatkan. Posting yang gagal HARUS mengirim pemberitahuan (tidak gagal diam-diam). Diimplementasikan (Tahap 8) sebagai hook publishHead pada cron jangkar (workers/api/src/utils/heads-publish.ts): PUT ke API konten tanpa sha hanya dapat menambahkan (422 berarti kepala sudah diterbitkan, tidak pernah menimpa); opt-in / dipersonalisasi pada PUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO} dan tidak aktif sampai repository disediakan. Kegagalan GitHub yang keras memberi halaman melalui saluran health_alert dan menaikkan metrik gagal yang tahan lama (m:tlog_publish_head_fail); jangkar Arweave itu sendiri tidak pernah mundur pada kegagalan publikasi. "Tidak gagal diam-diam" dijamin oleh metrik tahan lama itu — yang HARUS diperhatikan dalam dashboard oleh ops — bahkan jika pemberitahuan email upaya terbaik tidak dapat dikirim. Dua keterbatasan jujur muncul dari "jangkar-dimana-advance" (cron hanya mempublikasikan ketika ukuran maju): kegagalan GitHub sementara meninggalkan celah dalam urutan kepala yang diterbitkan untuk ukuran itu — terbatas, tidak diam-diam (mengirim halaman), dan karena setiap kepala berkomitmen pada pohon himpunan yang lebih besar, bukti konsistensi §16.9 menjembatani celah; yang penting, bukti konsistensi itu dihitung dari pohon yang dipermantau Arweave yang otoritatif, bukan dari permukaan GitHub, sehingga celah GitHub tidak pernah melemahkan keterverifikasian. Backfill yang mengejar dan mengisi celah kepala yang diterbitkan adalah peningkatan yang ditunda.

Kejujuran terikat (kendala yang mengikat). Karena qub mengontrol kedua permukaan posting yang direncanakan, ini adalah diterbitkan sendiri, bukan disaksikan secara independen. Tidak ada produk, pemasaran, atau permukaan legal yang boleh mengklaim bahwa log tersebut "disaksikan secara independen". Setelah gerbang penerimaan/profil/head dipersiapkan, klaim yang diperbolehkan adalah bahwa ekuivokasi dapat terdeteksi dan penambahan yang ditandatangani dengan sukses menghasilkan tanda terima yang tidak dapat disangkal. Sebelum itu, klaim tersebut tidak tersedia. Saksi pihak ketiga independen yang sebenarnya ditunda untuk pembaruan tata kelola §15 di masa depan.

received_at diklaim oleh operator dan tidak ada klaim yang boleh bergantung padanya — ini tidak pernah ditampilkan sebagai bukti atau sebagai pendukung sengketa pada permukaan produk/legal/API/rendering bukti mana pun. Waktu blok jangkar Arweave T adalah satu-satunya cap waktu tanpa kepercayaan (batas atas pada "dicatat oleh"). Setiap pemeriksaan kesehatan monitor pada received_at HARUS dibandingkan dengan T, bukan dengan bidang STH anchored_at yang dikontrol operator; pemeriksaan semacam itu hanya sebagai pengaman terhadap kesalahan jam operator yang jujur, bukan kontrol akuntabilitas terhadap operator yang berniat jahat (§16.15 Q5).

16.7 Format dan Kadensi Transaksi Jangkar

AnchorBundle adalah badan transaksi Arweave canonical-CBOR, ditulis melalui bundler §16.8: ver:u8, sth:bstr (byte SignedTreeHead canonical), prev_anchor:bstr (byte mentah id tx jangkar sebelumnya; diabaikan saat genesis), chain_hash:tstr (rantai drand yang berlaku — quicknet), dan stream CBOR leaf batch dalam urutan seq sehingga jangkar bersifat mandiri: monitor menurunkan kembali root dari badan dengan nol ketergantungan terhadap qub. (Jika stream leaf menjadi besar pada volume tinggi, revisi di masa depan mungkin hanya akan mengunci rentang leaf dengan referensi; dicatat, tidak diterapkan di v1.)

Tag Arweave sengaja dapat dihitung — log dimaksudkan untuk ditemukan, berbeda dengan qubs pribadi: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Tag adalah petunjuk yang tidak terpercaya; badan CBOR adalah otoritas tunggal.

Cadence: harian secara default, diulang dengan volume (pemicu ukuran otomatis memperpendek irama efektif saat beban). Produsen saat ini tidak menerapkan hook force-anchor dengan segel berbayar. Dompet anchor bersifat khusus dan berkecepatan rendah, terpisah dari dompet unggah — HARUS menjadi JWK miliknya sendiri (kunci yang berbeda, bukan peran logis pada dompet unggah) sehingga kompromi dompet unggah tidak dapat memalsukan anchor — dengan anggaran transaksi anchor per hari yang ketat. Postur kustodinya dinyatakan dengan jelas: hot key dengan scope sempit dengan pemutus sirkuit ketat dan saldo rendah, bukan "dingin" — dompet yang menandatangani otomatis setiap hari tidak bisa dingin, dan spesifikasi tidak berpura-pura sebaliknya.

16.8 ANS-104 Bundler

Encoder dan penandatangan deep-hash ANS-104 ANS-104 internal, sekitar 300 baris, Hanya Web Crypto, ketergantungan nol npm (kedua Turbo SDK gagal pada gerbang rantai pasok npm ci --ignore-scripts). Tata letak byte 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

Penandatanganan adalah Arweave deepHash — digest SHA-384 rekursif (persyaratan wire Arweave, crypto.subtle.digest("SHA-384")) melalui ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — lalu RSA-PSS melalui deep hash dengan dompet JWK melalui crypto.subtle; id = base64url(SHA-256(signature)). SHA-384 di sini dikarantina sebagai primitif hanya kawat Arweave, tidak pernah primitif qub trust (§15 mencatat pagar; hashing qub trust adalah SHA3-256 sepanjang waktu).

Jalur kode ANS-104 melayani mesin cadangan/drain yang ditunda dan menulis AnchorBundle DataItems. Jalur publikasi biasa pertama-tama menciptakan transaksi Arweave yang tepat dan menyimpan JSON-nya dalam kotak keluar yang tahan lama; posting langsung adalah optimasi latensi, dan jalur drain mencoba ulang transaksi yang sama sebelum menerapkan fallback bundler-nya. Skema tanda tangan (terselesaikan — §16.15 Q8): v1 menandai dengan RSA-PSS (tipe tanda tangan 1) menggunakan kembali mekanisme JWK dompet Arweave yang ada (nol penyimpanan kunci baru yang bertahan lama, melayani tesis "satu rahasia lebih sedikit"); Ed25519 ditunda ke jalur migrasi PQ §15.

Hash dalam yang digulir secara manual adalah kode dengan risiko tertinggi dan cakupan alami terendah di W5, sehingga pagting-nya tidak dapat dinegosiasikan (§16.15 Q8):

  1. Fixture lintas-bahasa tlog_v1.json (Rust + TS, pola §14.5 wrapper_v1.json) mencakup deep-hash, byte + id DataItem, hash daun, akar 5-daun + jalur audit, hash STH, bukti inklusi, dan bukti konsistensi — dalam arah tanda dan verifikasi (arah verifikasi penting karena pemeriksaan tx lokal → tx_id di §16.6 menarik deep hash ke setiap verifikator mandiri, bukan hanya penulis).
  2. Satu kali perjalanan pulang-pergi interop melalui bundler referensi ANS-104, digunakan sebagai data uji statis saja — tidak pernah sebagai dependensi runtime npm (postur hanya Web-Crypto / tanpa skrip instalasi tetap berlaku).
  3. Jalur deep-hash + RSA-PSS harus melalui perjalanan pulang-pergi dengan primitif crypto.subtle yang sama yang digunakan produksi, sehingga encoder internal kompatibel byte.
  4. Monitor penerimaan pasca-bundle yang berkelanjutan memastikan setiap DataItem anchor / fallback benar-benar diterima Arweave, dengan alarm + pemutus sirkuit — karena deep hash juga melayani antrean fallback Arweave yang tidak tersedia, sehingga regresi diam-diam akan mengisi antrean tersebut dengan item yang ditolak jaringan selama pemadaman yang dimaksud untuk ditangani.

16.9 Bukti Inklusi dan Konsistensi

Keduanya adalah RFC 9162, SHA3-256, disajikan sebagai CBOR kanonik.

InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (CBOR daun tepat — verifikator menghitung ulang leaf_hash sendiri dan tidak pernah mempercayai hash yang diberikan), 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. Daftar kunci tunggal yang tidak ambigu, diikat oleh vektor uji.

Verifikasi mandiri (tanpa server qub, memperluas §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).

Penyimpanan yang mendukung bukti HARUS dikunci-koordinat (terpecahkan — §16.15 Q7, prasyarat yang menghalangi). Pembuatan bukti daun dingin adalah netral terhadap kebenaran hanya jika bahan audit R2 adalah penyimpanan node Merkle persisten yang dikunci oleh koordinat pohon absolut (level, index) — bukan delta node per-batch. Dengan penyimpanan yang dikunci-koordinat, setiap jalur audit (leaf i, size N) adalah sekumpulan O(log N) R2 langsung GET dengan tidak ada perhitungan ulang di seluruh batas batch; dengan penyimpanan yang dikunci-batch tidak demikian, yang merupakan kesenjangan tata letak penyimpanan yang ditutup oleh resolusi ini. Badan daun juga dapat diakses berdasarkan konten oleh seq. Vektor uji W5 HARUS membuktikan daun dingin era-genesis terhadap root yang jauh kemudian menggunakan hanya R2 + Arweave dengan penyimpanan LogDO dihapus, sehingga klaim keamanan reclaim pada §16.13 didukung dan bukan sekadar diklaim. R2 O(log N) berurutan GET berlaku hanya pada titik akhir bukti asinkron — tidak pernah pada jalur panas seal (§16.10) atau cron per-tick.

16.10 Urutan Ack R2-Pertama

Urutan POST /api/v1/upload yang diimplementasikan adalah:

  1. Gerbang setengah depan (otentikasi, validasi, kunci shard idempotensi) — tidak berubah.
  2. Buat, tag, dan tanda tangani transaksi Arweave individual yang tepat. Ini menghasilkan tx_id secara lokal, meskipun pembuatan transaksi dapat mengambil metadata reward/anchor dari gateway. Kegagalan persiapan tetap gagal sebelum pengakuan.
  3. Secara sinkron tulis artefak yang dipilih di qub-cache/<tx_id> dan pertahankan catatan operasi/persetujuan pembuatan yang stabil. Ini adalah dasar daya tahan dan percobaan ulang; kegagalan sebelum penyelesaian mengembalikan 503.
  4. Ketika LOG_DO dikonfigurasi, secara sinkron coba LogDO.append(leaf). Penulis tunggal menetapkan seq, memperpanjang rantai entri, dan memperbarui frontier. RPC append hanya melakukan itu; penutupan batch berjalan di jalur terpisah pada alarm. Kegagalan transport/aplikasi append saat ini fail-soft: respons masih dapat berhasil tanpa log_seq, receipt, atau anchor_status. Meskipun ada komentar implementasi, tidak ada rekonsiliasi log otomatis selanjutnya yang terpasang saat ini.
  5. Kembalikan pengakuan. Sertakan { log_seq, anchor_status: "pending", receipt } hanya ketika append mengembalikan tuple sukses lengkap. receipt.sig_b64url kosong ketika penanda terima tidak tersedia; klien HARUS TIDAK memanggil nilai itu yang ditandatangani atau tidak dapat disangkal. Ketidakhadiran tuple berarti publikasi yang tahan lama saja, bukan penerimaan log-transparansi.
  6. Gunakan satu tugas tertunda untuk mengirimkan transaksi yang ditandatangani secara tepat. Keberhasilan menghapus outbox; kegagalan meninggalkannya untuk cron drain terbatas dan tidak boleh mengubah tx_id yang sudah diakui. Metadata sementara dan sidecar upaya-terbaik lainnya juga ditunda.

Batas latensi. Jalur permintaan mencakup otoritas/kuota bagian depan, persiapan/penandatanganan transaksi, penulisan R2 yang tahan lama, dan (jika dikonfigurasi) upaya LogDO. < 300 ms muncul dalam tinjauan desain sebagai target operasional, bukan jaminan protokol; langkah persiapan transaksi saat ini mungkin melakukan permintaan metadata gateway. Alarm latensi dan gerbang peluncuran adalah kontrol operasional, bukan bukti yang tersedia untuk verifier.

16.11 Model Kepercayaan — klaim tepat, dibatasi berdasarkan jenis daun

Untuk kind=0x01 (tersertifikasi): "Konten ini — tubuh yang sesuai dengan body_hash, diidentifikasi oleh qub_id — telah dikomit ke log append-only qub pada posisi seq dan ada paling lambat pada waktu blok Arweave T; ini tidak dapat dibaca secara kriptografi hingga putaran drand R = unlock_round(unlock_at)." Ini adalah triple {tlock round binding + Merkle inclusion + anchored root} lengkap.

Untuk kind=0x02 (ditegaskan, default): "Sebuah ciphertext opak dengan alamat-konten chash, mengklaim qub_id dan unlock_at, dikomit ke log append-only pada posisi seq dan ada paling lambat pada waktu blok Arweave T." Putaran dan bagian tubuh disediakan oleh verifikasi bundel §11 .qub yang ada (qub_core::unlock), bukan oleh log; apa yang ditambahkan log dibandingkan transaksi per-qub kosong adalah pengurutan yang dapat terlihat jika dirusak, waktu komitmen batas atas tanpa kepercayaan, dan ketahanan terhadap permusuhan.

Kedua klaim mengecualikan, sesuai §11: kepengarangan tanpa sig_alg ≥ 0x01, maksud, dan waktu granularitas sub-anchor. Keduanya tidak memungkinkan klaim apapun mengandalkan received_at.

Batas klaim (kendala peluncuran yang mengikat — diselesaikan §16.15 Q1). Untuk daun yang ditegaskan (kind=0x02), klaim yang dibatasi di atas adalah batas atas dari apa pun yang dapat diklaim oleh produk, pemasaran, syarat, atau permukaan yang menampilkan bukti. Tidak ada permukaan yang boleh menyatakan atau menyiratkan bahwa log membuktikan konten atau putaran unlock dari unggahan byte-blind — log membuktikan pengurutan + waktu komitmen batas atas tanpa kepercayaan dari ciphertext yang opak. Bukti konten dan putaran berasal secara eksklusif dari verifikasi bundel §11 .qub yang ada, yang independen terhadap log. Publikasi yang tidak memiliki append/receipt yang berhasil tidak memiliki klaim log sama sekali.

16.12 Versi dan Koordinasi W3

Tidak ada tidak ada SealedQub benjolan kawat dan oleh karena itu tidak ada benjolan versi protokol (§12.2): log adalah sidecar yang berkomitmen ke bidang dan byte yang sudah ada, sehingga tidak masuk ke riwayat versi protokol §12.3. drand_chain_version opsional W3 tidak tersentuh dan tetap menjadi satu-satunya bidang opsional SealedQub. Log ini memperkenalkan ruang versi independen sendiri — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — mencerminkan independensi versi pembungkus §12.5 (pembungkus membawa byte versi independen dari versi protokol, dan versi log mengikuti pemisahan yang sama).

Pengiriman bukti diambil secara default, dengan ride-along opsional. Proof tidak dapat ada saat segel (anchor belum ditulis), sehingga bundle .qub seal-time tetap bebas bukti. Verifier W7 mengambil GET …/proof sekali, atau dalam mode sepenuhnya offline merekonstruksi bukti dari AnchorBundle publik melalui query Arweave pada Log-Id. Bundle .qub (W7) menyimpan anggota inclusion_proof opsional — tidak ada saat seal, diisi oleh ekspor ulang pasca-anchor untuk arsip dingin — mengikuti pola "opsional, dihilangkan secara default, aditif" seperti drand_chain_version W3.

16.13 Retensi

Jendela retensi untuk ekor terbuka LogDO, substrat pembuktian R2, penghitung pemutus sirkuit anchor, dan antrian fallback bundler ditentukan dalam docs/DATA-RETENTION.md. Prinsip: penyimpanan hot per-entry log (LogDO) dapat direklamasi setelah jangkar; materi auditnya — penyimpanan node Merkle (level, index) berkoordinasi + badan daun beralamat seq (§16.9) + jangkar Arweave — bersifat permanen. Merebut daun dingin dari DO tidak pernah membatalkan bukti yang dikeluarkan, karena bukti menyelesaikan terhadap penyimpanan node R2 permanen dan jangkar Arweave, bukan DO (dan vektor uji wiped-DO §16.9 membuktikannya).

16.14 Vektor Uji

W5 mengirimkan tlog_v1.json fixture lintas bahasa (§16.8) plus vektor yang telah dikerjakan: kind=0x01 dan kind=0x02 leaf → leaf_hash; akar kumulatif 5 daun; satu bukti inklusi; satu bukti konsistensi; satu AnchorBundle; dan satu id DataItem. Ini hidup bersama vektor pembungkus luar §14.5 dan digunakan oleh implementasi Rust (qub-core) dan TypeScript (Worker).

16.15 Keputusan Tinjauan (W5 — diselesaikan)

Tinjauan eksternal W5 (persetujuan desain adversarial + persetujuan pemilik) telah selesai. Setiap keputusan di bawah ini diselesaikan dan tercermin dalam teks §16 di atas; batasan peluncuran yang mengikat dinyatakan kembali di akhir. Implementasi dapat dilanjutkan di bawah ketentuan tersebut.

  1. Kejujuran daun jalur default (kind=0x02) — SELESAI. Kirim pemisahan jenis dua daun sesuai spesifikasi: kind=0x02 tidak mengkomit body_hash maupun drand_round. Tidak ada field *_body_hash pada byte-blind path (ini akan menjadi sinyal "terverifikasi" palsu yang paling mudah dibaca untuk integrator dan merupakan kemudahan yang sudah disediakan §11 dari bundle). Jangan **** memerlukan server-seal untuk qub yang dicatat log (yang akan memaksa plaintext melalui Worker dan menghancurkan parit penghancuran kripto). Setiap korsleting yang menggambarkan dirinya sendiri seharusnya masuk ke .qub bundle / amplop bukti sebagai bidang yang dihitung ulang oleh verifier, bukan field daun. Batas klaim yang dikonfirmasi pemilik: §16.11.

  2. Ambiguasi / kelalaian akuntabilitas — DESAIN SELESAI, PENYEDIAAN TIDAK LENGKAP. Desain mengharuskan kunci tanda terima segel dipin dalam LogProfile dan ditandatangani silang oleh anchor_owner, ditambah metodologi monitor, prev-chain walk, dan dua kepala yang diterbitkan sendiri. Profil yang dikompilasi dan hook penerapan masih merupakan placeholder/opsional seperti yang dijelaskan dalam §16.6, sehingga klaim terdeteksi + diterima yang lebih kuat tidak berlaku sampai gerbang tersebut ditutup. Klaim tersebut tidak boleh dipasarkan sebagai disaksikan secara independen. Saksi pihak ketiga yang sebenarnya ditunda oleh peningkatan tata kelola §15.

  3. Anchor-owner trust root + rotasi yang dipin — TERSELESAIKAN. Adopsi pin LogProfile (§16.6); verifier memeriksa anchor_tx.owner == anchor_owner dan memverifikasi data TX → tx_id binding secara lokal. Tata kelola rotasi adalah §15 perluasan untuk build (§15.3 trigger ditambahkan), bukan penggunaan ulang; rotasi terencana cross-sign, rotasi yang didorong kompromi kembali ke kerusakan batas §15 bump dengan fork check.

  4. Private-qub leaf blinding — TERSELESAIKAN. Teruskan blinding untuk qub privat (ref = SHA3-256(qub_id ‖ log_blind_secret)), qub_id mentah untuk qub publik (sudah §16.2.1), chash sebagai seri mandiri. log_blind_secret adalah korelasi/Sybil-grade secret, hanya diputar maju (§16.2.1).

  5. received_at — DIPUTUSKAN. Simpan di daun, dikomitmenkan tetapi secara eksplisit tidak bersifat bukti; tidak pernah muncul sebagai bukti atau penguatan sengketa di permukaan manapun. Setiap pemeriksaan kewarasan monitor dibandingkan dengan waktu blok Arweave T, bukan dengan anchored_at yang dikendalikan operator (§16.6).

  6. Waktu pembuktian bertingkat — RESOLUSI DESAIN, BUKAN ROUTING SAAT INI. Desain yang ditinjau menetapkan waktu blok jangkar pada tier batched dan bukti jam tepat pada T3 berbayar, tanpa SLA numerik untuk yang pertama. Rute saat ini tidak menetapkan perbedaan komersial tersebut: mereka menjadwalkan transaksi individual untuk setiap publikasi yang diterima, dan cakupan log tetap bersyarat seperti yang dinyatakan dalam §16.1/§16.10. Salinan produk harus menjelaskan implementasi, bukan pembagian tier di masa depan ini.

  7. Pohon kumulatif pada Pekerja — TERSALAHKAN. Pohon RFC 9162 kumulatif tunggal + LogDO penulis tunggal dengan frontier-cache (memiliki ruang nyaman dibandingkan batas ~1k tulis/detik DO; menunda sharding Merkle-of-shard-roots hingga mendekati batas tersebut). Toko node (level, index) R2 yang dikunci dengan koordinat + vektor tes cold-leaf DO yang sudah dihapus telah diimplementasikan (§16.9). < 300 ms tetap menjadi target desain/operasional, bukan janji protokol (§16.10).

  8. Skema tanda tangan ANS-104 + deep-hash — TERSALAHKAN. RSA-PSS (tipe sig 1, menggunakan kembali JWK anchor-wallet khusus); Ed25519 ditunda ke jalur PQ §15. Deep hash SHA-384 buatan tangan dibatasi pada fixture lintas-implementasi kedua arah, pemeriksaan interop bundler referensi statis-only, round-trip crypto.subtle bersama, dan pemantau penerimaan Arweave pasca-bundle (§16.8).

Batasan peluncuran yang mengikat (dibawa ke implementasi + tinjauan produk/hukum):


17. Bundel verifikasi portabel (.qub)

Status. Bagian ini telah diimplementasikan (W7 / UP-C2): qub_core::export menghasilkan dan mengurai bundel, dan tools/qub-verify adalah CLI publik mandiri yang memverifikasi satu bundel secara luring. §11 dan §16.9 sudah mengacu pada "bundel .qub" sebagai unit yang digunakan verifier mandiri; bagian ini menetapkan bita dan panduan verifikasinya. Bagian ini bersifat aditif murni—bundel mengemas input §11 yang ada dan tidak mengubah format wire on-chain apa pun.

17.1 Tujuan

§11 menetapkan bahwa pihak ketiga mana pun dapat memverifikasi artefak kriptografis qub tanpa kerja sama qub. Bundel .qub membuat verifikasi tersebut portabel dan luring: bundel mengemas CBOR tersegel dan tanda tangan putaran drand yang membukanya ke dalam satu artefak mandiri, sehingga penerima dapat memverifikasi integritas konten, pengikatan putaran, dan tanda tangan kepenulisan apa pun dengan tanpa panggilan jaringan sama sekali (tanpa pengambilan penyimpanan, tanpa permintaan drand langsung, tanpa API qub). Bundel saja tidak membuktikan kapan ciphertext dibuat; transaksi penyimpanan yang diverifikasi secara independen atau bukti log tertambat menyediakan klaim waktu-keberadaan terpisah tersebut (§11, §17.5).

17.2 Format bundel

QubBundle adalah CBOR kanonis tulisan-tangan di bawah profil §3.1 (panjang pasti, tanpa tag, tanpa float, bilangan bulat bentuk terpendek, teks NFC, field opsional dihilangkan bila tidak ada, key diurutkan menaik berdasarkan panjang bita terenkode lalu secara bytewise). Tiga key 15-karakter berurutan d < i < s. Berkas .qub mentah berisi tepat bita-bita ini; untuk transportasi URL atau salin-tempel, bita yang sama menggunakan base64url (tanpa padding).

Key Pjg. enc. Tipe Kehadiran Makna
version 8 u8 wajib Versi format bundel (0x01).
sealed_at 10 i64 opsional Waktu segel yang diasersikan pembuat (detik Unix); deskriptif-mandiri, non-evidensial.
drand_round 12 u64 wajib Putaran tempat qub dikunci. Proyeksi dari qub tersegel yang disematkan.
arweave_tx_id 14 tstr wajib Id transaksi tempat bita tersegel disimpan (penunjuk asal-usul).
drand_chain_id 15 tstr wajib Rantai drand (hex). Proyeksi dari qub tersegel yang disematkan.
drand_signature 16 bstr wajib Tanda tangan beacon drand untuk drand_round—nilai yang membuka ciphertext.
inclusion_proof 16 bstr opsional Bukti inklusi Merkle log transparansi §16, setelah log dirilis (§17.5).
sealed_qub_cbor 16 bstr wajib Bita SealedQubCbor dalam (setelah pembukaan bungkus §13), yaitu input verifikasi §11.

drand_round dan drand_chain_id adalah proyeksi praktis dari sealed_qub_cbor, dibawa agar alat dapat membacanya tanpa mengurai CBOR dalam. Nilai ini diturunkan saat konstruksi dan diperiksa ulang saat decode terhadap qub tersegel yang diurai; bundel yang field tingkat-atasnya tidak sesuai dengan muatannya ditolak. Disiplin encoder mencerminkan format wire lainnya: tolak drand_signature atau arweave_tx_id kosong, dan batasi setiap field berpanjang-variabel.

17.3 Apa yang dibuktikan tanda tangan drand yang disematkan

Bundel membawa tanda tangan drand alih-alih mengharuskan verifier mengambilnya. Dekripsi timelock (tlock di atas rantai drand, §8) hanya dapat berhasil dengan tanda tangan beacon asli untuk putaran terikat—nilai yang diterbitkan rantai hanya setelah putaran tersebut berlalu dan yang merupakan tanda tangan BLS valid di bawah kunci publik rantai. Tanda tangan palsu atau salah gagal pada verifikasi BLS atau dekripsi IBE/AEAD. Karena itu, bundel yang dapat didekripsi membuktikan: ciphertext terikat ke putaran R, dan putaran R telah berlalu. Verifier mematok rantai (DrandTimelockProvider::quicknet()) dan menerapkan pemeriksaan pengikatan putaran §11, sehingga bundel tidak dapat mengklaim putaran yang tidak mengikat ciphertext-nya.

Ini adalah bukti kondisi-rilis, bukan cap waktu pembuatan. Setelah putaran R berlalu, siapa pun dapat membuat ciphertext baru untuk R dan mengemas tanda tangannya yang sudah publik. Karena itu, bundel saja TIDAK BOLEH digambarkan sebagai bukti bahwa ciphertext atau konten sudah ada sebelum R, sebelum unlock_at, atau sebelum peristiwa apa pun.

17.4 Panduan verifikasi luring

qub-verify <file.qub> menjalankan prosedur standar §11 sepenuhnya dari bundel, dengan menggerakkan qub_core::unlock::unlock menggunakan DrandTimelockProvider terpaku:

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 keluar dengan 0 (terverifikasi), 1 (verifikasi gagal—masih terkunci, hash badan tidak cocok, pengikatan putaran/rantai rusak, atau tanda tangan gagal diverifikasi), atau 2 (penggunaan / bundel salah bentuk). Laporan --json membawa putusan yang sama untuk otomatisasi. Karena bundel mandiri, crate verifier (qub-core) dan CLI (qub-verify) adalah satu-satunya perangkat lunak yang diperlukan pihak ketiga; keduanya publik dan menggunakan ulang jalur verifikasi protokol yang ada—tanpa kripto khusus.

17.5 Hubungan dengan log transparansi

inclusion_proof adalah slot opsional untuk bukti inklusi Merkle §16. Verifikasi hanya-bundel (§17.4) lengkap untuk integritas, pengikatan putaran / putaran berlalu, dan kepenulisan opsional, tetapi sengaja tidak memiliki klaim keberadaan bercap waktu independen. inclusion_proof yang terisi dan sepenuhnya terverifikasi-tambatan menambahkan komitmen khusus-jenis-daun dan waktu batas atas dari §16.11 tanpa mengubah versi format bundel. Bukti yang tidak ada hanya berarti "tidak ada bukti yang disertakan"—bukan "tidak valid" dan belum tentu "tidak ditambatkan".

Dalam implementasi referensi, slot tersebut kini bertipe: qub_core::export::QubBundle::inclusion_proof_typed() mengembalikan Option<InclusionProof> yang membawa struktur §16.9 lengkap (daun, jalur audit, akar tertambat, dan AnchorRef) melalui field CBOR buram yang sama—tanpa bump versi format bundel. CLI mandiri qub-verify menggunakannya melalui kaki --anchor, dan—hingga dompet tambatan disediakan (§16, Status)—melaporkan bukti yang terisi tetapi pemiliknya placeholder sebagai inklusi saja, bukan sepenuhnya terverifikasi-tambatan.