Spesifikasi Protokol qub

qub ialah protokol untuk komitmen temporal kriptografi: satu sistem untuk memeterai kata-kata kepada tarikh akan datang dan membuktikan, apabila tarikh itu tiba, apa sebenarnya yang telah dikatakan dan bila.

Tiga primitif menjadikannya berfungsi. drand ialah suar kerawakan ternyahpusat — tarikh pendedahan boleh dikuatkuasakan oleh fizik, bukan oleh niat baik mana-mana pihak. Storan awam kekal ialah simpanan awam tahan-usik — tiada pihak boleh menyunting atau memadam qub setelah ia dimeterai. ML-DSA-65 ialah tandatangan digital pasca-kuantum — setiap qub diikat kepada satu pasangan kunci yang rahsianya tidak pernah meninggalkan peranti pengarang.

Bersama-sama primitif ini menghasilkan satu kenyataan yang terkunci waktu, kalis usik, dan boleh dikaitkan — satu resit yang nilainya bertambah selari dengan kemampuan dunia untuk memalsukan masa lalu.

Sebahagian dokumen yang tinggal ialah spesifikasi normatif yang diperlukan untuk implementasi yang saling kendali.


Spesifikasi Protokol qub

Medan Nilai
Versi 1.0 (versi protokol 0x01, versi pembungkus luar 0x01)
Tarikh 2026-05-01
Status Draf
Disemak sehingga 2026-05-01

Dokumen ini ialah spesifikasi protokol normatif untuk sistem komitmen berwaktu qub. Ia mentakrifkan struktur data, peraturan pensirian, formula derivasi, dan prosedur pengesahan yang diperlukan untuk implementasi yang saling kendali.

Skop: lapisan protokol secara sengaja bersifat neutral-bahasa — badan qub ialah bait teks biasa / markdown / perjanjian yang legap, dan pemaparan mengikut lokal ialah tanggungjawab pembaca (aplikasi web qub.social, iframe <qub-embed>, klien MCP, dsb.).


1. Notasi dan Konvensi

Notasi Maksud
u8, u64, i64 Integer tanpa tanda/bertanda dengan lebar bit yang ditetapkan
[u8; N] Tatasusunan bait panjang tetap sebanyak N bait
Vec<u8> Tatasusunan bait panjang berubah
Option<T> Nilai jenis T, atau tiada
String Rentetan teks UTF-8, dinormalkan NFC
`
SHA3-256(x) Cincang NIST SHA3-256 bagi rentetan bait x (FIPS 202)
ceil(x) Fungsi siling: integer terkecil ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Bait paling signifikan didahulukan

Semua integer dalam pembinaan praimej dikodkan sebagai tatasusunan bait lebar tetap big-endian (i64 → 8 bait, u8 → 1 bait) melainkan dinyatakan sebaliknya.

Semua cap masa ialah saat Unix dalam UTC.


2. Struktur Data

2.1 ComposeQub (Keadaan Dalam-Memori Pencipta)

Tidak disirikan kepada CBOR. Tidak ditulis ke storan kekal. Tempatan pada aplikasi pencipta.

ComposeQub {
    draft_id:       [u8; 16],        // Random, generated locally
    created_at:     i64,             // Unix seconds UTC
    unlock_at:      Option<i64>,     // Unix seconds UTC; None while composing
    visibility:     u8,              // 0x01 = public (only value in MVP)
    content_type:   u8,              // 0x01 = text (only value in MVP)
    plaintext:      Vec<u8>,         // UTF-8 qub body
    sender_label:   Option<String>,  // Decorative display name; not authenticated
    status:         DraftStatus,     // Composing | Sealed | Uploaded | Failed
}

2.2 QubEnvelope (Muatan Ternyahsulit)

Disirikan menggunakan CBOR berkanun (§3). Disulitkan di dalam SealedQub. Inilah struktur yang membuktikan integriti kandungan selepas penyahsulitan.

QubEnvelope {
    version:             u8,              // Protocol major version (0x01 for v1)
    qub_id:              [u8; 32],        // Derived (see §4.1)
    content_type:        u8,              // Content type registry (see §6)
    created_at:          i64,             // Unix seconds UTC
    unlock_at:           i64,             // Unix seconds UTC
    outcome_at:          Option<i64>,     // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
    sender_label:        Option<String>,  // Decorative; not authenticated in MVP
    reply_to:            Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
    body:                Vec<u8>,         // Content payload (UTF-8 for text, CBOR for pact)
    body_hash:           [u8; 32],        // SHA3-256(body) (see §4.2)
    sig_alg:             u8,              // Signature algorithm (see §9.2)
    author_signature:    Option<Vec<u8>>, // Set when sig_alg != 0x00
    author_pubkey:       Option<Vec<u8>>, // Set when sig_alg != 0x00
    cosigner_pubkey:     Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
    cosigner_signature:  Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}

Asas (qub teks tanpa tandatangan): version = 0x01, content_type = 0x01, sig_alg = 0x00, semua medan Option tiada.

Konfigurasi v1 yang lain: content_type = 0x03 (badan perjanjian, 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-sama untuk perjanjian yang ditandatangani bersama (lihat §9.7); reply_to ditetapkan kepada qub_id qub induk untuk qub rantai balas (lihat §9.3 untuk implikasi skop tandatangan).

2.3 SealedQub (Format Wayar Berkanun)

Disirikan menggunakan CBOR berkanun (§3). Ditulis ke storan kekal. Inilah artifak atas-rantai.

SealedQub {
    version:           u8,              // Protocol major version (0x01 for v1)
    qub_id:            [u8; 32],        // Same as QubEnvelope.qub_id
    visibility:        u8,              // 0x01 = public; v1 viewers reject other values
    unlock_at:         i64,             // Unix seconds UTC
    outcome_at:        Option<i64>,     // V1.1 — surfaced on the verdict-watch CTA
                                        //   before reveal; mirrors QubEnvelope.outcome_at;
                                        //   bound to qub_id via the §4.1 preimage.
    drand_chain_id:    String,          // drand chain hash (hex string)
    drand_round:       u64,             // Target drand round number
    tlock_ciphertext:  Vec<u8>,         // tlock-encrypted QubEnvelope CBOR bytes
    recipient_pubkey:  Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
                                        //   but not interpreted by the v1 reference viewer
    title:             Option<String>,  // Plaintext title surfaced on the viewer
                                        //   countdown before reveal. Bound to qub_id
                                        //   via title_hash (§4.1). 1..=100 NFC code
                                        //   points, no control characters.
}

2.4 RevealedQub (Keadaan Aplikasi Pembaca)

Tidak disirikan kepada CBOR. Tempatan pada aplikasi pembaca. Dibina selepas penyahsulitan dan pengesahan berjaya.

RevealedQub {
    qub_id:              [u8; 32],
    arweave_tx_id:       String,
    visibility:          u8,
    content_type:        u8,
    created_at:          i64,
    unlock_at:           i64,
    outcome_at:          Option<i64>,       // V1.1 — dibawa ke hadapan daripada QubEnvelope.outcome_at / SealedQub.outcome_at; memacu blok pemerhatian-keputusan halaman pendedahan (verdict-uplift-plan §5.1)
    drand_chain_id:      String,
    drand_round:         u64,
    sender_label:        Option<String>,
    title:               Option<String>,    // Carried forward from SealedQub.title
    reply_to:            Option<[u8; 32]>,
    body:                Vec<u8>,
    body_hash:           [u8; 32],
    body_hash_verified:  bool,
    author_signature:    Option<Vec<u8>>,
    author_pubkey:       Option<Vec<u8>>,
    signature_verified:  Option<bool>,
    cosigner_pubkey:     Option<Vec<u8>>,
    cosigner_signature:  Option<Vec<u8>>,
    cosigner_verified:   Option<bool>,
}

3. Profil CBOR Berkanun

Semua pensirian SealedQub dan QubEnvelope MUST mematuhi profil ini. Dua implementasi yang diberikan struktur logik yang sama MUST menghasilkan bait yang serupa.

3.1 Peraturan Pengekodan

Peraturan Spesifikasi
Piawai RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Susunan kunci peta Diisih mengikut panjang bait terkod dahulu (yang lebih pendek didahulukan), kemudian secara leksikografi (bait demi bait untuk pengekodan panjang yang sama)
Pengekodan integer Bentuk terpendek: 0–23 dalam bait awal; 24–255 dalam 2 bait; 256–65535 dalam 3 bait; dsb.
Pengekodan panjang Panjang tentu sahaja. Tiada tatasusunan, peta, rentetan bait, atau rentetan teks panjang tak tentu (maklumat tambahan = 31 adalah dilarang).
Tag Tiada tag CBOR (jenis utama 6 adalah dilarang).
Titik apungan Tiada apungan (nilai jenis utama 7 0xF9–0xFB adalah dilarang).
Rentetan teks Dikod UTF-8, dinormalkan NFC (Unicode Normalization Form C).
Rentetan bait Bait mentah. Tiada pengekodan base64 pada lapisan CBOR.
Kunci pendua Tolak dengan ralat. Penghurai MUST NOT menerima kunci peta pendua secara senyap.
Nilai mudah Hanya true (0xF5), false (0xF4), dan null (0xF6) dibenarkan.
Medan pilihan Medan pilihan yang tiada diabaikan daripada peta CBOR sepenuhnya (tidak dikodkan sebagai null). Medan pilihan yang hadir dimasukkan mengikut susunan kunci yang diisih.

3.2 Susunan Kunci Berkanun yang Disahkan

Susunan kunci ini bersifat normatif. Implementasi MUST mengeluarkan kunci dalam susunan yang tepat ini. Penegasan nyahpepijat SHOULD mengesahkan susunan dalam binaan bukan-keluaran.

QubEnvelope (versi 0x01, tanpa tandatangan, semua medan pilihan tiada):

"body"                (5 encoded bytes)
"qub_id"              (7 encoded bytes)
"sig_alg"             (8 encoded bytes)
"version"             (8 encoded bytes)
"reply_to"            (9 encoded bytes)   ← only if present (reply chains)
"body_hash"           (10 encoded bytes)
"unlock_at"           (10 encoded bytes)
"created_at"          (11 encoded bytes)
"outcome_at"          (11 encoded bytes)  ← only if present (V1.1 verdict mechanic)
"content_type"        (13 encoded bytes)
"sender_label"        (13 encoded bytes)  ← only if present
"author_pubkey"       (14 encoded bytes)  ← only if present
"cosigner_pubkey"     (16 encoded bytes)  ← only if present (pact cosign)
"author_signature"    (17 encoded bytes)  ← only if present
"cosigner_signature"  (19 encoded bytes)  ← only if present (pact cosign)

Derivasi susunan kunci QubEnvelope: setiap kunci ialah rentetan teks CBOR. Panjang terkod = 1 bait pengepala + panjang rentetan (untuk rentetan di bawah 24 bait). Diisih mengikut jumlah panjang terkod dahulu, kemudian secara leksikografi bagi kunci panjang yang sama.

SealedQub (versi 0x01, awam, tiada penerima):

"title"             (6 encoded bytes)   ← only if present
"qub_id"            (7 encoded bytes)
"version"           (8 encoded bytes)
"unlock_at"         (10 encoded bytes)
"outcome_at"        (11 encoded bytes)  ← only if present (V1.1 verdict mechanic)
"visibility"        (11 encoded bytes)
"drand_round"       (12 encoded bytes)
"drand_chain_id"    (15 encoded bytes)
"recipient_pubkey"  (17 encoded bytes)  ← only if present
"tlock_ciphertext"  (17 encoded bytes)

PactTerms (badan perjanjian, 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 tatasusunan terms):

"key"    (4 encoded bytes)
"value"  (6 encoded bytes)

PartyIdentifier (peta party_a / party_b):

"label"    (6 encoded bytes)
"contact"  (8 encoded bytes)  ← only if present

3.3 Rujukan Pengekodan Bait

Jenis Pengekodan CBOR Contoh
Cincang SHA3-256 (32 bait) 0x58 0x20 + 32 bait body_hash, qub_id
Cap masa (i64) Jenis utama 0 (positif) atau 1 (negatif), pengekodan terpendek saat Unix
Versi (u8, nilai 1) 0x01 (bait tunggal)
Jenis kandungan (u8, nilai 1) 0x01 (bait tunggal)
sig_alg (u8, nilai 0) 0x00 (bait tunggal)
Tandatangan ML-DSA-65 (3,309 bait) 0x59 0x0C 0xED + 3,309 bait author_signature, cosigner_signature
Kunci awam ML-DSA-65 (1,952 bait) 0x59 0x07 0xA0 + 1,952 bait author_pubkey, cosigner_pubkey

4. Derivasi Normatif

4.1 qub_id

qub_id mengenal pasti qub secara unik dan mengikat QubEnvelope kepada SealedQub. Ia diterbitkan secara deterministik daripada kandungan 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

Pengekodan pemisah domain: Rentetan "QUB_ID_V2" ialah 9 bait ASCII. Satu bait pelapik 0x00 ditambah untuk mencapai 10 bait bagi penjajaran. Implementasi MUST menggunakan tepat 10 bait ini: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Pengekodan outcome_at: V1.1 melanjutkan praimej daripada 92 kepada 100 bait untuk melipat medan outcome_at pilihan ke dalam ikatan. outcome_at yang tiada dikod sebagai 8 bait sifar; pengesah protokol menolak outcome_at <= 0 di mana-mana sahaja, jadi sentinel ini tidak boleh berlanggar dengan nilai yang sah. Lihat §3.2 (format wayar) dan tasks/verdict-uplift-plan.md dalam pokok untuk mekanik verdik yang mendorong medan ini.

Pengekodan drand_round: V1.2 melanjutkan praimej daripada 100 kepada 108 bait untuk melipat drand_round (pusingan drand sasaran, §4.3) ke dalam ikatan, dan menaikkan pemisah domain kepada QUB_ID_V2. Ini mengikat pusingan timelock ke dalam identiti qub: pintu masuk tidak boleh mengikat semula siferteks kepada pusingan yang berbeza (cth., yang sudah berlalu) daripada apa yang disiratkan oleh unlock_at yang dipaparkan. Prosedur buka kunci (§8) selanjutnya mengesahkan bahawa pusingan yang dibakar ke dalam stanza siferteks tlock sepadan dengan unlock_round(unlock_at), jadi masa buka kunci yang dipaparkan terbukti ialah pusingan yang mengawal penyahsulitan.

Sifat:

4.2 body_hash

body_hash = SHA3-256(body)

Di mana body ialah muatan kandungan Vec<u8> mentah. Untuk qub teks, ini ialah badan qub yang dikod 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 ialah tajuk teks biasa pilihan yang dipaparkan pada hitung mundur pembaca sebelum pendedahan (lihat §3.2). Penormalan NFC dijalankan pada masa pencincangan supaya cernaan stabil merentas urutan titik kod yang setara secara visual. Sentinel sifar-semua dikhaskan untuk kes tiada; rentetan kosong ditolak pada sempadan CBOR berkanun sebagai pengekodan bukan-berkanun bagi "tiada" (pengekodan berkanun mengabaikan medan sepenuhnya).

4.3 Pemetaan Pusingan-Buka-Kunci

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Parameter Sumber Contoh
unlock_at Saat Unix UTC yang dipilih pengguna 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time maklumat rantai drand (genesis_time) 1595431050
chain_period_seconds maklumat rantai drand (period) 30

Operasi ceil() memilih pusingan drand pertama yang masa pendedahannya ≥ unlock_at. Ini memastikan qub tidak menjadi boleh dinyahsulit sebelum masa buka kunci yang dipilih.

Kes pinggir: jika (unlock_at - chain_genesis_time) boleh dibahagikan tepat dengan chain_period_seconds, hasilnya ialah pusingan tepat itu — qub buka kunci tepat pada masa pendedahan pusingan itu.

Pengesahan: unlock_at MUST berada pada masa hadapan pada masa pemeteraian. unlock_at MUST NOT lebih daripada 10 tahun daripada created_at (untuk mengehadkan risiko kebergantungan drand jangka panjang; UI SHOULD memberi amaran untuk tarikh buka kunci melebihi 2 tahun).


5. Jenis Baharu Format Wayar

Jenis baharu format wayar menyediakan keselamatan masa-kompilasi terhadap mengelirukan bait CBOR dengan JSON, teks biasa mentah, atau pengekodan bait lain.

Jenis Mengandungi Dihasilkan Oleh Digunakan Oleh
SealedQubCbor CBOR berkanun bagi SealedQub serialize_sealed_qub() Muat naik storan kekal, ambilan pembaca
QubEnvelopeCbor CBOR berkanun bagi QubEnvelope serialize_qub_envelope() Input penyulitan tlock, output penyahsulitan tlock

5.1 Peraturan Pembinaan

// 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 Pengesahan pada Pembinaan

from_encoded() SHOULD mengesahkan bahawa input bermula dengan pengepala peta CBOR yang sah. Pengesahan struktur penuh berlaku pada masa hurai, bukan masa pembinaan, untuk mengelakkan hurai-dua-kali.


6. Pendaftaran Jenis Kandungan

Nilai Jenis Saiz Badan Maks Nota
0x00 Dikhaskan (tidak sah) MUST NOT digunakan
0x01 Teks biasa (UTF-8, Markdown terhad) 50 KB berbayar / 10 KB percuma Lihat §10 untuk peraturan pemaparan. Pemisahan percuma / berbayar dikuatkuasakan oleh perkhidmatan muat naik; siling keras lapisan protokol ialah 50 KB.
0x02 Dikhaskan (masa depan) Diperuntukkan untuk jenis kandungan masa depan; tidak sah dalam v1. Pembaca MUST menolak mengikut peraturan di bawah.
0x03 Perjanjian (perjanjian dwihala, badan CBOR) 100 KB Badan ialah CBOR berkanun PactTerms (§6.1). Tandatangan bersama mengikut §9.7.
0x04 Keputusan (penilaian-diri pencipta, badan CBOR) 8 KB Badan ialah CBOR berkanun VerdictBody (§6.2). Dikeluarkan hanya oleh hasrat sisi-sistem verdict. Hubungan induk berada pada tag Arweave Parent-Tx-Id, bukan pada badan. Lihat verdict-uplift-plan §3.4.

Pembaca MUST menolak jenis kandungan yang tidak dikenali dengan ralat yang jelas kelihatan kepada pengguna. Pembaca MUST NOT cuba memaparkan jenis yang tidak dikenali sebagai teks.

6.1 Badan Perjanjian (content_type = 0x03)

Badan perjanjian ialah pengekodan CBOR berkanun bagi nilai PactTerms:

PactTerms {
    pact_version:  u8,                    // 0x01 for structured/v1
    title:         String,                // ≤ 200 bytes, NFC
    terms:         Vec<PactTerm>,         // ≤ 20 rows
    party_a:       PartyIdentifier,       // initiator
    party_b:       PartyIdentifier,       // counter-signer
    notes:         Option<String>,        // ≤ 5,000 bytes, NFC; absent key if none
}

PactTerm       { key: String (≤ 100), value: String (≤ 2,000) }   // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }

Susunan kunci CBOR berkanun untuk ketiga-tiga peta diberikan dalam §3.2. Jumlah CBOR perjanjian yang disirikan MUST NOT melebihi 100 KB (sepadan dengan §6).

Pembeza skema. Baris pertama dalam terms untuk perjanjian structured/v1 MUST ialah { key: "pact_schema", value: "structured/v1" }. Baris tanpa penanda ini ialah perjanjian "tersuai" dan tidak menerima pengesahan berstruktur atau pemaparan sedar-skema.

Slot pengakuan yang dibekukan. Perjanjian structured/v1 membawa tepat empat baris pengakuan di bawah kunci berikut:

"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"

Nilai value bagi setiap satu ialah salah satu daripada lapan rentetan Inggeris dibekukan yang dipilih oleh pasangan (role, kind), di mana role ∈ { seller, buyer, provider, client } dan kind ∈ { standard, capacity }. Rentetan itu sendiri ialah data protokol normatif — tandatangan ML-DSA-65 kedua-dua pihak komited kepada bait yang tepat melalui body_hash. Ia TIDAK dilokalkan; badan yang ditandatangani bersifat neutral-bahasa. Apa-apa perubahan kata-kata memerlukan versi skema baharu (structured/v2).

Lapan rentetan, carian (acknowledgement_for(role, kind)), dan rasional bagi setiap satu disematkan oleh implementasi rujukan. Implementasi yang mematuhi MUST mengeluarkan nilai pengakuan yang serupa bait-demi-bait; ujian body-cincangan SHA3-256 lekapan-emas yang meliputi keempat-empat kombinasi peranan menangkap apa-apa hanyutan.

Susunan paparan pembaca. Rentetan pengakuan mengandungi frasa seperti "described above", yang menganggap baris penerangan / skop dipaparkan sebelum pengakuan. Pembaca MUST memaparkan tatasusunan terms dalam susunan CBOR; menyusun semula merosakkan semantik prosa.

Hubungan Pihak Lawan. Apabila contact Pihak B ialah alamat e-mel yang sah, perkhidmatan muat naik qub menghantar e-mel jemputan semakan / tandatangan bersama secara automatik pada masa pementasan dan mengikat tandatangan bersama akhirnya kepada pengesahan alamat yang sama itu (§9.7). Perjanjian yang contact Pihak B-nya tiada masih boleh ditandatangani bersama, tetapi hanya melalui saluran luar-jalur — perkhidmatan menolak permintaan tandatangan bersama yang tidak boleh menghasilkan penanda pengesahan-e-mel 15 minit yang sepadan.

6.2 Badan Keputusan (content_type = 0x04)

Badan keputusan ialah pengekodan CBOR berkanun bagi 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
}

Susunan kunci CBOR berkanun:

"outcome"          (8 encoded bytes)
"reflection"       (11 encoded bytes)  ← only if present
"evidence_url"     (13 encoded bytes)  ← only if present
"verdict_version"  (16 encoded bytes)

Jumlah CBOR keputusan yang disirikan MUST NOT melebihi 8 KB (sepadan dengan baris pendaftaran di atas).

Enum keputusan. Bait wayar bersifat hasrat-neutral; keempat-empat kategori Right / Partial / Wrong / Unfalsifiable meliputi ruang keputusan setiap hasrat yang membawa keputusan. Label setiap-hasrat ("Tepat sasaran" / "Saya menunaikannya" / "Dikeluarkan" / "Disahkan" untuk Right, dsb.) ialah kebimbangan pemaparan sisi-pembaca yang diselesaikan terhadap hasrat qub induk — wayar kekal neutral-bahasa dan neutral-hasrat. Nilai di luar 1..=4 MUST ditolak pada penyahkodan.

Pautan induk. Sebuah qub keputusan TIDAK membawa rujukan induk dalam badannya. Id transaksi Arweave qub induk dikeluarkan sebagai tag storan Parent-Tx-Id pada masa muat naik (§7 lapisan tag storan). Ini mengekalkan badan sebagai pernyataan penilaian-diri yang ditandatangani dan serba lengkap; rantai audit ("betul tentang apa?") ditubuhkan melalui carian tag Arweave.

Keselamatan URL bukti (normatif). Apabila evidence_url hadir, pengesah (sisi-karang, sisi-wayar, edge Worker) MUST menguatkuasakan:

  1. HTTPS sahaja. Rentetan MUST bermula dengan urutan bait https://. Apa-apa skema lain — http, ftp, javascript, data, file, dsb. — ditolak.
  2. Had panjang. ≤ 2,048 bait (had praktikal URL pelayar).
  3. Pemeriksaan NFC + kod-titik bermusuhan. Peraturan yang sama seperti title dan reflection — kod-titik bidi-override / lebar-sifar / blok-tag / BOM / C0 / C1 ditolak. Definisi sepadan dengan Rust crate::handle::contains_hostile_text_codepoint dan TS workers/api/src/utils/unicode.ts::isHostileCodepoint (kekalkan selari).
  4. Tiada ruang putih, tiada kawalan ASCII. Ruang putih / DEL / bait di bawah 0x20 di mana-mana dalam URL ditolak — menutup vektor suntikan \n/\t yang tidak diliputi peraturan bidi.
  5. Segmen hos tidak kosong. Semua yang berada di antara https:// dan /, ?, atau # yang pertama MUST tidak kosong.

Tiada pengambilan sisi-pelayan. Worker MUST NOT memproksi, mengambil, atau pratonton URL. Protokol menyimpan rentetan; pemaparan berlaku sisi-pembaca dengan rel="nofollow noopener noreferrer" target="_blank" dan hos yang kelihatan dipaparkan bersebelahan teks pautan.

Refleksi. Teks refleksi tulisan-pencipta pilihan ("apa yang berubah, apa yang anda pelajari"). Pengesahan NFC + kod-titik bermusuhan yang sama seperti title. Input kosong / hanya ruang putih menguncup kepada tiada pada masa pembinaan.

Versi skema. v1 menyokong verdict_version = 0x01 sahaja. Semakan skema masa depan menaikkan bait ini dan tiba bersama versi protokol baharu mengikut §12.


7. Protokol Meterai

Urutan pemeteraian lengkap. Setiap langkah bersifat normatif.

 1. User composes plaintext and metadata in ComposeQub.
 2. Validate:
    a. body is non-empty.
    b. body size ≤ max for content_type and user tier (see §6).
    c. unlock_at is in the future.
    d. unlock_at ≤ created_at + 10 years.
    e. content_type is a known, supported value.
 3. Compute body_hash = SHA3-256(body).
 4. Set created_at = current Unix seconds UTC.
 5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
    compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
    (Computed here, before qub_id, because drand_round is bound into the qub_id
    preimage — §4.1, V1.2.)
 6. Compute qub_id (see §4.1), folding in drand_round from step 5.
 7. Construct QubEnvelope with all fields.
 8. Serialise QubEnvelope using canonical CBOR → bytes B.
    Assert: serialised output matches canonical profile (§3).
 9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C, and matching qub_id, version,
    unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
     Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
     per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
     never the bare SealedQubCbor. K leaves the device only as the URL
     fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
    signs and uploads to permanent storage. The service is byte-blind to the inner
    SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
    `<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
    when a short code is allocated). Browsers do not transmit URL fragments
    to servers, so K is never observed by qub.social or any storage gateway.

Lapisan tag storan (luar-jalur). Perkhidmatan muat naik qub melampirkan satu set tag transaksi storan yang sengaja kecil di sebelah muatan terbalut. Content-Type=application/octet-stream diperlukan secara normatif. Perkhidmatan rujukan juga melampirkan tiga tag pilihan apabila pencipta memilih untuk memaparkannya: Intent (niat penggubahan disahkan-senarai dibenarkan — cth., quote, reply, commitment), Author (cap jari kunci awam §9.3 pencipta sebagai heks huruf kecil 64-aksara), dan Parent-Tx-Id (ID transaksi storan qub induk untuk rantai balas, base64url 43-aksara).

Tag Author adalah pilih-masuk setiap qub: aplikasi pencipta rujukan melampirkannya hanya apabila pengguna mendayakan atribusi awam secara eksplisit pada masa pemeteraian. Apabila togol mati — lalai — tiada tag Author ditulis dan qub tidak dikaitkan pada rantai: tiada apa-apa dalam storan kekal yang menghubungkan muat naik kepada pemegang, e-mel, atau qub lain pencipta. Apabila togol hidup, cap jari Author menyelesaikan kepada @handle yang dipilih pencipta melalui rantai pengesahan §9.5. Hubungan rantai-balas dan Intent tidak mengenal pasti. Pembungkus luar (§13) melindungi badan dalaman daripada korelasi siferteks — menghalang penuai daripada mengenali dan menyahsulit secara pukal muat naik berbentuk qub selepas pusingan drand mereka diterbitkan.

Perkhidmatan rujukan sengaja TIDAK melampirkan tag App-Name, App-Version, atau Type: apa-apa penapis bernilai-tunggal sebegini akan mengembalikan keseluruhan korpus qub kepada pertanyaan GraphQL, yang tidak konsisten dengan skop kerahsiaan badan-sahaja pembungkus.

Pengesah yang mematuhi MUST NOT bergantung pada apa-apa tag storan untuk pengesahan pihak-ketiga §11; cincang badan / qub_id / tandatangan komited hanya kepada CBOR dalaman, tidak pernah kepada set tag.


8. Protokol Buka Kunci

Urutan buka kunci lengkap. Setiap langkah bersifat normatif.

 1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
    K = base64url_decode(fragment) from the URL fragment. If the fragment
    is absent or malformed → display "this URL is missing its decryption
    key" and stop; the viewer MUST NOT contact the storage gateway
    without K, since fetching wrapped bytes the viewer cannot decrypt
    serves no purpose and only leaks the access attempt.
 2. Check denylist. If tx_id is denylisted → display block message. Stop.
 3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
 3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
    `version` byte is `0x01`, and compute SealedQubCbor =
    unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
    failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
    swapped nonce) → display "this URL's decryption key does not match
    the stored qub" and stop. Authentication failures are
    indistinguishable to the viewer per §13.5.
 4. Parse SealedQubCbor → SealedQub.
 5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
 6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
 6a. Round-binding check (V1.2). Recompute expected_round =
    ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
    Reject unless SealedQub.drand_round == expected_round AND the round baked
    into the tlock ciphertext stanza (read via the age/tlock header, no signature
    required) == expected_round. The stanza round is the one that actually gates
    decryption; without this check a malicious creator could bind the ciphertext
    to an already-past round while displaying a future countdown, so anyone
    reading the stored bytes could decrypt before unlock_at. Implementations with
    no chain identity (test mocks) skip this check.
 7. Once current time ≥ SealedQub.unlock_at:
    a. Fetch drand round signature for SealedQub.drand_round from drand network.
    b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
 8. Parse B → QubEnvelope.
 9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
    Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
    Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
    Fail → integrity error.
13. Verify: QubEnvelope.content_type is known and renderable.
    Known values: 0x01 (text), 0x03 (pact). Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.

9. Tandatangan Pengarang

9.1 Rasional

qub disimpan dalam storan kekal. Tandatangan pengarang mesti kekal tidak boleh dipalsukan selama-lamanya, sebab itulah v1.0 menggunakan skema ML-DSA-65 pasca-kuantum (FIPS 204) berbanding skema klasik yang keselamatannya boleh merosot dalam hayat kekal qub.

9.2 Pendaftaran Algoritma

sig_alg Skema Saiz Kunci Saiz Tandatangan
0x00 Tiada tandatangan (tanpa tandatangan)
0x01 ML-DSA-65 (FIPS 204) 1,952 bait 3,309 bait

Pembaca MUST menolak nilai sig_alg yang tidak dikenali.

9.3 Pembinaan Praimej Bertandatangan

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

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

signature = Sign(author_secret_key, sig_input)

Pemisah domain: "QUB_AUTHOR_SIG_V1" ialah 17 bait ASCII: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Tiada pelapik.

Bait belakang: bait praimej ke-91 MUST ialah 0x00. Implementasi rujukan mendedahkan ini sebagai pemalar ORG_ID_PRESENT_INDIVIDUAL = 0x00 dalam crates/qub-core/src/signing.rs; pembaca yang membina semula sig_input untuk pengesahan MUST mengeluarkan bait yang sama.

Skop tandatangan — apa yang diliputi dan apa yang tidak. sig_input komited kepada empat medan envelope: version, qub_id, body_hash, unlock_at (tambah pemisah domain tetap dan bait org_id_present). Tiga daripada empat itu ialah invarian struktur: qub_id itu sendiri diterbitkan daripada version, content_type, created_at, unlock_at, outcome_at, drand_round, dan body_hash melalui praimej §4.1, jadi apa-apa perubahan kepada medan-medan itu menghasilkan qub_id yang berbeza dan membatalkan tandatangan secara transitif. Permukaan yang disahkan secara langsung dengan itu ialah:

Medan Disahkan oleh tandatangan Bagaimana
version Input langsung kepada sig_input
qub_id Input langsung
body_hash Input langsung
unlock_at Input langsung
content_type Secara transitif, melalui praimej qub_id
created_at Secara transitif, melalui praimej qub_id
outcome_at Secara transitif, melalui praimej qub_id
drand_round Secara transitif, melalui praimej qub_id (V1.2)
body Secara transitif, melalui body_hash = SHA3-256(body)
author_pubkey — (tersirat) Kunci yang mengesahkan tandatangan ialah pengarang, secara takrif
sender_label Teks paparan-sahaja; boleh diubah tanpa kerosakan tandatangan
reply_to Penunjuk benang; boleh diubah tanpa kerosakan tandatangan
cosigner_pubkey / cosigner_signature Ditandatangani secara bebas pada sig_input yang sama (lihat §9.7)
drand_chain_id, tlock_ciphertext, visibility Medan SealedQub luaran, bukan di dalam envelope — diliputi oleh invarian struktur mereka sendiri (konsistensi pusingan / rantai) tetapi bukan oleh tandatangan pengarang. (drand_round kini diikat secara transitif melalui praimej qub_id — lihat di atas.)

Implikasi keselamatan medan tidak-disahkan.

Implementasi yang memaparkan sender_label atau reply_to kepada pengguna akhir MUST mempamerkan identiti yang disahkan (cap jari kunci awam, pengesahan) sebagai isyarat identiti utama, bukan label.

9.4 Prosedur Pengesahan

1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (same formula as §9.3).
6. Verify(author_pubkey, sig_input, author_signature).
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."

Pengesahan tandatangan ialah operasi yang paling mahal (terutamanya ML-DSA-65). Ia SHOULD dilakukan selepas semua pemeriksaan yang lebih murah (cincang, qub_id, unlock_at) telah lulus.

9.5 Pengesahan Identiti

Pengesahan identiti — pemetaan author_pubkey kepada tuntutan identiti yang boleh dikenali manusia seperti pemegang qub, alamat e-mel, pemegang sosial, atau kelayakan passkey — ialah peningkatan progresif sisi-pembaca dan tidak diperlukan untuk pengesahan tandatangan. Pembaca yang menyelesaikan pengesahan kepada identiti paparan MUST menggunakan kekananan:

handle > email > social > fingerprint

Sandaran cap jari ialah heks huruf kecil bagi SHA3-256(author_pubkey); ia sentiasa tersedia untuk mana-mana qub bertandatangan. Pembaca MAY meringkaskannya untuk paparan — pembaca rujukan memaparkan qub: diikuti oleh empat bait pertama dan terakhir (qub:<8 hex>…<8 hex>).

Pengesah yang mematuhi boleh melengkapkan setiap pemeriksaan dalam §9.4 tanpa menghubungi API qub, tanpa apa-apa rangkaian melebihi storan kekal dan drand, dan tanpa apa-apa carian sisi-pelayan. Penyelesaian pengesahan ialah langkah terbaik-cuba yang berasingan yang dilakukan hanya selepas pengesahan tandatangan berjaya.

9.6 Kesan Saiz

Ed25519 ML-DSA-65
Tandatangan 64 bait 3,309 bait
Kunci awam 32 bait 1,952 bait
Jumlah setiap qub 96 bait 5,261 bait
Delta kos storan (pada ~$5/MB) ~$0.0005 ~$0.026

Untuk qub teks 500–2,000 bait, ML-DSA-65 kira-kira menggandakan tiga kali ganda saiz yang disimpan. Kos mutlak adalah boleh diabaikan.

9.7 Pengesahan Penandatangan Bersama (Perjanjian Dwihala Pakta)

Untuk perjanjian dwihala (content_type = 0x03), lapisan tandatangan kedua membuktikan kedua-dua pihak bersetuju dengan terma yang sama.

Medan envelope:

Kedua-dua medan MUST hadir bersama-sama atau kedua-duanya tiada. Jika tepat satu hadir, pembaca MUST melaporkan ralat integriti.

Prosedur pengesahan:

1. If cosigner_pubkey absent and cosigner_signature absent → no cosigner. Done.
2. If exactly one is present → integrity error.
3. Verify cosigner_pubkey != author_pubkey (prevent self-cosigning).
   Fail → display "cosigner pubkey must differ from author."
4. Reconstruct sig_input using the same formula as §9.3.
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."

Sifat:

Pintu pengikatan-e-mel (operasi). Apabila perjanjian yang dipentaskan membawa hubungan e-mel Pihak B (§6.1), perkhidmatan muat naik qub MUST menolak permintaan tandatangan bersama melainkan penanda pengesahan-e-mel jangka pendek wujud yang sepadan dengan kedua-dua id pementasan dan cincang e-mel ternormal hubungan tersebut. Penanda itu ditulis oleh /api/v1/auth/verify apabila token pautan-ajaib membawa staging_id dan alamat yang disahkan sepadan dengan SHA-256(normalise_email(party_b.contact)) — di mana normalise_email(addr) mengekalkan kes bahagian-tempatan dan menurunkan huruf kecil hanya bahagian domain (mengikut RFC 5321 §2.3.11), dan SHA-256 di sini ialah cincang NIST FIPS 180-4 (berbeza daripada SHA3-256 yang digunakan dalam derivasi §4) — dan tamat tempoh 900 saat (15 minit) selepas dikeluarkan. Ini ialah pintu anti-penyamaran operasi, BUKAN sebahagian daripada bukti qub atas-rantai — pengesah pihak-ketiga yang memainkan semula §11 hanya memerlukan storan kekal dan drand, tanpa apa-apa carian sisi-pelayan. Penanda wujud sisi-pelayan sahaja dan tidak pernah menjadi sebahagian daripada badan yang ditandatangani.

Kesan saiz (pengarang ML-DSA-65 + penandatangan bersama):

Komponen Saiz
Tandatangan pengarang 3,309 bait
Kunci awam pengarang 1,952 bait
Tandatangan penandatangan bersama 3,309 bait
Kunci awam penandatangan bersama 1,952 bait
Jumlah overhed kripto 10,522 bait
Delta kos storan ~$0.05

10. Pemaparan dan Sanitasi Markdown

Bahagian ini kritikal keselamatan. Pembaca memaparkan qub teks (content_type = 0x01) menggunakan subset Markdown yang terhad.

10.1 Elemen Dibenarkan

10.2 Elemen Dilarang

Elemen Pengendalian
HTML mentah (<div>, <script>, dsb.) Dibuang sepenuhnya. Tiada HTML melepasi.
Imej (![alt](url)) Dibuang. Sintaks imej dialih keluar daripada output.
Pautan ([text](url)) URL dipaparkan sebagai teks biasa yang kelihatan. Tidak auto-pautkan. Tidak boleh diklik tanpa tindakan pengguna yang eksplisit.
Skema URL berbahaya javascript:, data:, vbscript:, file: — dibuang.
Iframe, embed, objek Dibuang.
Entiti HTML Dinyahkod kepada aksara paparan hanya jika selamat.

10.3 Implementasi

Implementasi MUST menggunakan penghurai senarai dibenarkan ketat, bukan senarai disekat. Pendekatan yang disyorkan:

  1. Hurai Markdown menggunakan pulldown-cmark (atau setara).
  2. Susuri AST dan buang mana-mana nod yang tidak dalam senarai dibenarkan (§10.1).
  3. Untuk nod pautan: keluarkan URL sebagai teks yang kelihatan, bukan sebagai elemen <a> yang boleh diklik.
  4. Tukarkan AST yang ditapis kepada perwakilan perantaraan bertaip (cth., enum MarkdownNode dengan hanya varian selamat). HTML mentah tidak boleh diwakili secara struktur dalam IR ini.
  5. Paparkan daripada IR bertaip kepada lapisan pandangan sasaran (cth., komponen pandangan reaktif, nod DOM). Tiada penyambungan rentetan HTML atau innerHTML pada bila-bila masa.

Pendekatan senarai disekat adalah rapuh kerana sambungan Markdown baharu atau keanehan penghurai boleh memperkenalkan elemen yang tidak ditapis. Pendekatan AST bertaip menjadikan XSS mustahil secara struktur — tiada varian yang boleh membawa HTML sewenang-wenangnya.

10.4 Had Saiz dan Struktur


11. Pengesahan Pihak Ketiga

Mana-mana pihak ketiga boleh mengesahkan qub awam tanpa kerjasama qub. Prosedur pengesahan:

1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.

Apa yang dibuktikan oleh pengesahan:

Bukti Apa yang ia tetapkan
Komitmen Siferteks wujud menjelang cap masa blok storan.
Integriti Badan teks biasa sepadan dengan cincang yang dikomited dan tidak diubah.
Pemasaan Kandungan tidak boleh dibaca sehingga pusingan drand, yang sepadan dengan masa buka kunci yang dipilih (tertakluk kepada andaian keselamatan tlock dan drand).

Apa yang TIDAK dibuktikan oleh pengesahan:

Bukan-bukti Mengapa
Pengarangan sender_label bersifat hiasan. Tanpa sig_alg0x01, sesiapa sahaja boleh meterai kandungan ini.
Niat qub membuktikan kandungan dan pemasaan, bukan apa yang dimaksudkan pencipta secara subjektif.
Pemasaan pra-peristiwa Kemasukan blok storan mungkin lewat muat naik sebenar dengan beberapa minit. Cap masa komitmen ialah masa blok, bukan saat pengguna menekan "meterai."

12. Versi

12.1 Versi Protokol

Medan version (u8) dalam kedua-dua SealedQub dan QubEnvelope mengenal pasti versi protokol utama.

12.2 Sejarah Versi

Versi Nilai Penerangan
v1 0x01 qub teks awam (content_type 0x01), badan perjanjian dwihala (0x03, skema structured/v1, pengarang + penandatangan bersama ML-DSA-65), tlock, SHA3-256

12.3 Keserasian Ke Hadapan

Pembaca v1 yang menjumpai QubEnvelope dengan kunci peta CBOR pilihan yang tidak dikenali (kunci yang tiada dalam susunan berkanun §3.2) SHOULD mengabaikan kunci tersebut dan teruskan dengan pengesahan menggunakan medan yang dikenali. Ini membolehkan penambahan kecil di masa hadapan (cth., metadata baharu) tanpa memerlukan tolakan versi utama.

Pembaca v1 yang menjumpai sig_alg = 0x01 (ML-DSA-65) tetapi kekurangan sokongan pengesahan ML-DSA-65 SHOULD memaparkan kandungan qub dengan notis "tandatangan hadir tetapi tidak boleh disahkan", bukannya menolak qub sepenuhnya. Implementasi rujukan hari ini menolak setiap nilai sig_alg selain daripada 0x00 dan 0x01 kerana pendaftaran v1 tidak mengandungi algoritma sah lain — penolakan ketat dan gagal-lembut adalah serupa dari segi pemerhatian sehingga algoritma ketiga didaftarkan. Tingkah laku gagal-lembut di atas menjadi memikul-beban sebaik sahaja §9.2 menerima entri baharu, dan pembaca rujukan akan dikemas kini kepada gagal-lembut pada ketika itu.

12.4 Versi Pembungkus Luar

OuterWrapper yang diterangkan dalam §13 membawa bait version-nya sendiri, bebas daripada SealedQub.version dan QubEnvelope.version. Kedua-dua ruang versi berkembang secara berasingan: penggantian simetrik selamat-pasca-kuantum pada masa hadapan menolak bait pembungkus tanpa menyentuh versi protokol dalaman, dan penambahan lapisan protokol pada masa hadapan (cth., medan envelope baharu) menolak versi dalaman tanpa menyentuh bait pembungkus.

OUTER_WRAPPER_VERSION_* Nilai Algoritma Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM dengan nonce 12-bait, tag pengesahan 16-bait, AAD terikat kepada qub_id lalai v1
0x020xFF Dikhaskan Masa Hadapan

Pembaca MUST menolak versi pembungkus yang tidak dikenali dengan ralat yang jelas. Protokol sengaja mengekalkan ruang versi pembungkus yang sempit sehingga pemacu migrasi konkrit muncul (cth., panduan NIST yang mengutamakan AEAD yang berbeza); slot 0x02 akan diperuntukkan dalam semakan yang sama yang memperkenalkan algoritma.


13. Pembungkus Penyulitan Luar

13.1 Rasional

Lapisan protokol (QubEnvelope → tlock → SealedQub) menjadikan qub yang dimeterai terkunci-waktu: badan tidak boleh dibaca sehingga unlock_at dan tandatangan pusingan drand telah diterbitkan. Walau bagaimanapun, selepas buka kunci, tandatangan pusingan adalah awam dan bentuk CBOR berkanun SealedQub boleh dikenali, jadi penuai yang mengindeks transaksi storan kekal boleh menyahsulit korpus qub keseluruhan secara pukal.

Pembungkus penyulitan luar menutup saluran itu dengan menyelitkan lapisan AEAD simetrik tambahan antara SealedQubCbor berkanun dan bait yang ditulis ke storan kekal. Kunci 256-bit K wujud hanya dalam fragmen URL pautan penghantaran dan pada peranti pengguna; pelayar tidak menghantar fragmen URL kepada pelayan, jadi qub.social, setiap pintu masuk storan, dan setiap CDN di hadapan kedua-duanya adalah buta dari segi pemerhatian terhadap K. Oleh itu, setiap qub dalam storan kekal ialah siferteks legap yang teks biasanya tidak boleh dipulihkan tanpa URL yang dipilih pencipta untuk dikongsi.

Kesan bersih:

13.2 Berlapis

plaintext body                       ← QubEnvelope.body (§2.2)
  ↓ canonical CBOR (§3)
envelope CBOR
  ↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
  ↓ canonical CBOR (§3)
SealedQubCbor bytes                  ← inner wire artifact
  ↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes              ← uploaded to permanent storage (§7 step 15)

Meterai dan buka kunci pada lapisan protokol (§7, §8) tidak berubah di bawah sempadan pembungkus; pembungkus dilekatkan pada laman panggilan seal() dan ditanggalkan pada laman panggilan unlock().

13.3 Struktur Data OuterWrapper

struct OuterWrapper {
    version:    u8,           // 0x01, see §12.4
    qub_id:     [u8; 32],     // copied from inner SealedQub; AEAD AAD
    nonce:      [u8; 12],     // 96-bit AEAD nonce
    ciphertext: Vec<u8>,      // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}

Invarian medan.

Pengekodan CBOR. CBOR berkanun mengikut §3, dengan peraturan susunan-kunci yang sama (diisih mengikut panjang bait terkod menaik, kemudian secara leksikografi). Empat kunci tersebut ialah:

Kunci Bait terkod Susunan
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Bait pertama OuterWrapper CBOR oleh itu ialah pengepala peta panjang-tentu untuk peta 4-entri (0xA4).

13.4 Pengikatan AAD kepada qub_id

Pembungkus mengikat qub_id sebagai data pengesahan tambahan AEAD. Ini ialah pertahanan struktur yang memikul beban terhadap tiga kelas serangan:

Serangan Pertahanan
Alih siferteks di bawah medan qub_id yang berbeza dalam pembungkus Ketidakpadanan AAD → pengesahan AEAD gagal
Campurkan fragmen URL qub A dengan bait storan kekal qub B Ketidakpadanan AAD → pengesahan AEAD gagal
Usik medan qub_id pembungkus selepas muat naik Ketidakpadanan AAD → pengesahan AEAD gagal

Membawa qub_id dalam teks biasa pembungkus tidak melemahkan imuniti penghitungan dengan ketara — qub_id itu sendiri ialah cincang SHA3-256 bagi praimej §4.1 tanpa praimej boleh-pulih daripada cernaan, dan penghitung yang telah menuai bait pembungkus tidak belajar apa-apa daripada qub_id yang kelihatan yang tidak boleh disimpulkan daripada kewujudan muat naik itu sendiri.

13.5 Algoritma Bungkus dan Nyahbungkus

wrap_sealed_qub(SealedQubCbor S, qub_id Q, key K, nonce N):
    require K.len() == 32 and N.len() == 12 and Q.len() == 32
    C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
    // C includes the 16-byte authentication tag at the end
    return canonical_cbor_encode(OuterWrapper{
        version:    0x01,
        qub_id:     Q,
        nonce:      N,
        ciphertext: C,
    })

unwrap_sealed_qub(OuterWrapper bytes W, key K):
    require K.len() == 32
    O := canonical_cbor_decode(W) as OuterWrapper
    require O.version == 0x01           // §12.4
    P := AES_256_GCM_decrypt(
            key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
         )
    // any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
    return P                            // P is the inner SealedQubCbor

Keruntuhan mod-kegagalan. K yang salah, nonce yang salah, ketidakpadanan AAD, dan siferteks yang diusik semuanya menghasilkan ralat DECRYPT_FAILED yang sama. Ini ialah sifat AEAD yang disengajakan: membezakan mod kegagalan akan mencipta saluran sampingan yang penyerang jauh boleh siasat dengan menghantar pembungkus cacat dan mengetik tindak balas. Implementasi rujukan MUST meruntuhkan semua kegagalan AEAD kepada satu bentuk ralat.

13.6 Bahan Kunci dan Pengedaran

Kunci pembungkusan K ialah nilai rawak seragam 256-bit yang dihasilkan setiap-qub oleh CSPRNG. Implementasi rujukan mendapatkannya daripada:

Pengedaran: K MUST dikodkan sebagai base64 URL-selamat (RFC 4648 §5, tanpa pelapik) dan ditambah kepada pautan penghantaran sebagai komponen fragmen:

delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>

Fragmen tidak pernah dihantar kepada mana-mana pelayan oleh pelayar yang mematuhi. Saluran pemulihan (indeks sejarah sisi-pelayan, hantar-auto e-mel pilih-masuk) yang mengekalkan pautan penghantaran penuh — termasuk fragmen — di luar peranti pengguna ialah pertukaran eksplisit terhadap postur kripto-pemusnahan lalai dan MUST dipintu pada persetujuan eksplisit pengguna.

Kehilangan fragmen. Jika pengguna kehilangan fragmen URL dan tiada saluran pemulihan, qub tidak boleh dibaca. Inilah pertukaran memikul-beban reka bentuk dan MUST didedahkan kepada pengguna pada masa pemeteraian. MVP mengukuhkan pendedahan masa-pemeteraian dengan salinan eksplisit "simpan URL ini" dan saluran pemulihan e-mel disahkan untuk pengguna yang pilih-masuk.

13.7 Di Luar Skop bagi Bahagian Ini

13.8 qub awam (peninggalan pembungkus)

Pembungkus luar bersifat pilihan pada lapisan penghantaran. Seorang pencipta boleh memeterai qub sebagai awam, dan dalam kes itu SealedQubCbor berkanun ditulis ke storan kekal secara langsung, tanpa lapisan OuterWrapper dan tanpa kunci K:

SealedQubCbor bytes  ──(public)──▶  uploaded to permanent storage as-is
SealedQubCbor bytes  ──(private)─▶  AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded

Sebuah qub awam terkunci-waktu tetapi tidak berpintu-pautan: ia kekal tidak boleh dibaca sehingga pusingan drandnya diterbitkan (lapisan tlock tidak berubah), tetapi selepas buka kunci sesiapa sahaja yang mempunyai arweave_tx_id boleh menyahsulitnya — tiada fragmen URL diperlukan, kerana tiada K. Inilah pertukaran yang disengajakan untuk permukaan yang pelayan mesti pacu: e-mel pemberitahuan-pendedahan, sematan pihak-ketiga, dan SEO pasca-pendedahan yang lebih kaya semuanya memerlukan pautan yang berfungsi tanpa rahsia yang pelayan tidak pernah pegang (§13.6).

Akibat yang pengeluar MUST ambil kira:

Peribadi (terbungkus) kekal sebagai lalai; awam ialah pilihan pencipta setiap-qub yang eksplisit.


14. Vektor Ujian

14.1 Derivasi qub_id

Input:
  version      = 0x01
  content_type = 0x01
  created_at   = 1735689600 (2025-01-01 00:00:00 UTC)
  unlock_at    = 1736294400 (2025-01-08 00:00:00 UTC)
  outcome_at   = absent
  drand_round  = 4695445  (= (1736294400 - 1595431050) / 30, drand mainnet params §14.2)
  body         = "Hello, future."  (UTF-8, 14 bytes)
  title        = absent

Intermediate:
  body_hash  = SHA3-256("Hello, future.")
             = 76ab8b3f843c6ed4f2d0fd75b9f457b4
               ad49dd4450f9c22723ae430e3af3211d
  title_hash = [0u8; 32]   (title absent — §4.2.1 sentinel)

Domain separator (10 bytes):
  [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]

Preimage (108 bytes — V1.2):
  domain_separator    ||  // 10 bytes
  0x01                ||  // version
  0x01                ||  // content_type
  0x0000000067748580  ||  // created_at as i64 big-endian (1735689600)
  0x00000000677DC000  ||  // unlock_at as i64 big-endian (1736294400)
  0x0000000000000000  ||  // outcome_at_or_zero (outcome_at absent)
  0x000000000047A595  ||  // drand_round as u64 big-endian (4695445)
  body_hash           ||  // 32 bytes
  title_hash              // 32 bytes (all-zeros sentinel; title absent)

Expected output:
  qub_id = SHA3-256(preimage)
         = 3a9fcb31b750d985c262fada6d4f777f
           d6a28be831d941d85c131f5a4bbaf8a4

Implementasi MUST menghasilkan nilai body_hash dan qub_id yang serupa untuk input ini. Vektor ujian ini SHOULD menjadi ujian unit pertama yang ditulis. Nilai berkanun di atas dikira oleh implementasi rujukan dan MUST sepadan bit-demi-bit. Susun atur praimej sejarah (pra-pelancaran — tiada qub langsung bergantung pada nilai ini): qub_id V1.0 92-bait ialah 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; qub_id V1.1 100-bait (selepas melipat outcome_at_or_zero) ialah b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 melipat drand_round ke dalam dan menaikkan pemisah domain kepada QUB_ID_V2.

14.2 Pemetaan Pusingan-Buka-Kunci

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

Calculation:
  (1735689600 - 1595431050) / 30 = 4675285.0
  ceil(4675285.0) = 4675285

drand_round = 4675285

14.3 Pergi-Balik CBOR Berkanun

Implementasi MUST mengesahkan bahawa serialize(parse(serialize(qub))) == serialize(qub) untuk semua input yang sah. Ini ialah ujian sifat, 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)

Bait CBOR berkanun dan body_hash SHA3-256 dikira oleh implementasi rujukan. Implementasi MUST menghasilkan CBOR yang serupa bait-demi-bait untuk input ini.

Implementasi juga MUST mengesahkan bahawa serialize(parse(serialize(pact))) == serialize(pact) untuk semua input PactTerms yang sah (ujian sifat).

14.5 Vektor Silang-Bahasa Pembungkus Luar

Pembungkus luar (§13) mempunyai lekapan berkanun yang berasingan di crates/qub-core/tests/vectors/wrapper_v1.json. Setiap kes menetapkan tupel (key, nonce, qub_id, sealed_cbor) sebagai input heks legap dan menegaskan output expected_wrapper_hex tertentu. Kedua-dua implementasi rujukan menggunakan fail JSON yang sama:

Lekapan tersebut kini menyemat tiga kes:

Kes Liputan
basic-text-public Bentuk SealedQub realistik terkecil; tiada medan pilihan. Menetapkan bentuk pembungkus berkanun untuk qub tipikal-v1.0.
with-recipient-pubkey SealedQub dengan recipient_pubkey ditetapkan (laluan Fasa 2). Set kunci CBOR dalaman yang berbeza, qub_id yang berbeza.
longer-body Badan ~4 KiB — melatih awalan panjang CBOR pelbagai bait dalam kedua-dua envelope dalaman dan siferteks luar.

Implementasi MUST menghasilkan expected_wrapper_hex yang serupa bait-demi-bait untuk input yang direkodkan. Penjanaan semula lekapan memerlukan QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors dan dikhaskan untuk perubahan format yang disengajakan.


15. Tadbir Urus Profil Kripto (Masa Hadapan)

Bahagian ini bersifat informatif untuk v1 dan menjadi normatif kali pertama algoritma kedua memasuki mana-mana primitif kriptografi qub.

15.1 Postur Semasa

Protokol v1 mengikat tepat satu algoritma setiap primitif:

Pengesah kini mengekodkan-keras panjang kunci dan tandatangan setiap primitif. Tiada permukaan kelincahan didedahkan oleh format wayar.

15.2 Bentuk yang Dimaksudkan

Apabila algoritma kedua memasuki protokol, pengesah akan dikonfigurasi untuk CryptoProfile bernama (cth., ExqubV1) yang menyenaraikan set tepat nilai dibenarkan setiap primitif — sig_alg, rantai drand, versi pembungkus, jenis kandungan. Profil ditetapkan pada masa pengesahan, tidak pernah dirunding dalam-jalur. Apa-apa nilai di luar profil aktif ditolak.

Ini menjamin bahawa menambah ML-DSA-87 atau mengaktifkan Ed25519 tidak boleh secara retroaktif melemahkan konfigurasi pengesah sedia ada: pengesah v1 kekal sebagai pengesah v1 walaupun selepas profil v2 diterbitkan.

15.3 Syarat Pencetus

Naikkan §15 kepada status normatif apabila mana-mana yang berikut dicadangkan:

Sehingga itu §15 ialah pemegang tempat yang menetapkan bentuk migrasi supaya PR masa hadapan mendarat terhadap sasaran yang diketahui dan bukannya bertikai semula permukaan rundingan dari awal.