qub Protocol Specification

Ang qub ay isang protokol para sa mga kriptograpikong pangako na nakatali sa oras: isang sistemang nagsesellyo ng mga salita patungo sa isang hinaharap na petsa at, sa pagdating ng petsang iyon, nagpapatunay nang eksakto kung ano ang sinabi at kailan.

Tatlong primitibo ang nagpapagana nito. drand ay isang desentralisadong randomness beacon — ang petsa ng paghahayag ay ipinapatupad ng pisika, hindi ng kabutihang-loob ng sinumang panig. Ang permanenteng pampublikong storage ay isang tamper-proof na pampublikong imbakan — walang sinumang panig ang maaaring mag-edit o magtanggal ng isang qub kapag naiselyo na ito. ML-DSA-65 ay isang post-quantum na digital na lagda — bawat qub ay nakatali sa isang key pair na ang lihim ay hindi umaalis sa device ng may-akda.

Magkasama, ang mga primitibong ito ay gumagawa ng isang pahayag na naka-time-lock, may-katibayan-ng-pakikialam, at maiuugnay sa may-akda — isang resibo na ang halaga ay lumalago habang umuunlad ang kakayahan ng mundong likhain muli ang nakaraan.

Ang natitirang bahagi ng dokumentong ito ay ang normatibong pagtutukoy na kinakailangan para sa magkakaugnay na implementasyon.


qub Protocol Specification

Field Value
Version 1.0 (protocol version 0x01, outer wrapper version 0x01)
Date 2026-05-01
Status Draft
Reviewed through 2026-05-01

Ang dokumentong ito ay ang normatibong tukoy ng protokol para sa sistema ng nakatakdang-oras na pangako ng qub. Tinutukoy nito ang mga istruktura ng datos, mga panuntunan sa serialisation, mga pormula ng derivation, at mga pamamaraan ng pagberipika na kinakailangan para sa magkakaugnay na implementasyon.

Saklaw: ang protocol layer ay sadyang neutral sa wika — ang katawan ng qub ay opaque plaintext / markdown / pact bytes, at ang locale-aware na pag-render ay responsibilidad ng mambabasa (qub.social web app, <qub-embed> iframe, MCP clients, atbp.).


1. Notasyon at mga Kumbensiyon

Notation Meaning
u8, u64, i64 Unsigned/signed integers of specified bit width
[u8; N] Fixed-length byte array of N bytes
Vec<u8> Variable-length byte array
Option<T> Value of type T, or absent
String UTF-8 text string, NFC normalised
`
SHA3-256(x) NIST SHA3-256 hash of byte string x (FIPS 202)
ceil(x) Ceiling function: smallest integer ≥ x
CBOR Concise Binary Object Representation (RFC 8949)
big-endian Most significant byte first

Lahat ng integer sa preimage constructions ay naka-encode bilang big-endian fixed-width byte arrays (i64 → 8 bytes, u8 → 1 byte) maliban kung iba ang itinakda.

Lahat ng timestamp ay Unix seconds sa UTC.


2. Mga Istruktura ng Datos

2.1 ComposeQub (Estado ng Tagalikha sa Memorya)

Hindi ini-serialize sa CBOR. Hindi isinusulat sa permanenteng storage. Lokal lamang sa app ng tagalikha.

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 (Na-decrypt na Payload)

Naka-serialize gamit ang canonical CBOR (§3). Naka-encrypt sa loob ng SealedQub. Ito ang istrukturang nagpapatunay ng integridad ng nilalaman pagkatapos ng pag-decrypt.

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 — kapag nagbigay ng hatol ang realidad (verdict-uplift-plan §3.1)
    sender_label:        Option<String>,  // Decorative; not authenticated in MVP
    reply_to:            Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
    body:                Vec<u8>,         // Content payload (UTF-8 for text, CBOR for pact)
    body_hash:           [u8; 32],        // SHA3-256(body) (see §4.2)
    sig_alg:             u8,              // Signature algorithm (see §9.2)
    author_signature:    Option<Vec<u8>>, // Set when sig_alg != 0x00
    author_pubkey:       Option<Vec<u8>>, // Set when sig_alg != 0x00
    cosigner_pubkey:     Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
    cosigner_signature:  Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}

Baseline (hindi naka-lagdang text qub): version = 0x01, content_type = 0x01, sig_alg = 0x00, lahat ng Option fields ay wala.

Iba pang v1 configurations: content_type = 0x03 (pact body, tingnan §6.1); sig_alg = 0x01 (ML-DSA-65) na may author_signature at author_pubkey (tingnan §9.3); cosigner_pubkey at cosigner_signature ay magkasama para sa mga kasunduang may kasamang lagda (tingnan §9.7); reply_to itinakda sa qub_id ng magulang na qub para sa mga reply-chain qubs (tingnan §9.3 para sa mga implikasyon sa saklaw ng lagda).

2.3 SealedQub (Canonical Wire Format)

Naka-serialize gamit ang canonical CBOR (§3). Isinusulat sa permanenteng storage. Ito ang on-chain artifact.

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 — inilalantad sa verdict-watch CTA
                                        //   bago ang paghahayag; sumasalamin sa QubEnvelope.outcome_at;
                                        //   nakatali sa qub_id sa pamamagitan ng §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 (Estado ng Aplikasyon ng Mambabasa)

Hindi ini-serialize sa CBOR. Lokal lamang sa app ng mambabasa. Binubuo pagkatapos ng matagumpay na pag-decrypt at pagberipika.

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 — dinadala mula sa QubEnvelope.outcome_at / SealedQub.outcome_at; nagpapatakbo sa verdict-watch block ng reveal page (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. Canonical CBOR Profile

Lahat ng SealedQub at QubEnvelope serialisation ay MUST sumunod sa profile na ito. Dalawang implementasyon na binigyan ng parehong lohikal na istruktura ay MUST gumawa ng magkaparehong bytes.

3.1 Mga Panuntunan sa Pag-encode

Rule Specification
Standard RFC 8949 §4.2.1 (Core Deterministic Encoding Requirements)
Map key ordering Sorted by encoded byte length first (shorter before longer), then lexicographically (byte-by-byte for same-length encodings)
Integer encoding Shortest form: 0–23 in initial byte; 24–255 in 2 bytes; 256–65535 in 3 bytes; etc.
Length encoding Definite lengths only. No indefinite-length arrays, maps, byte strings, or text strings (additional info = 31 is forbidden).
Tags No CBOR tags (major type 6 is forbidden).
Floating-point No floats (major types 7 values 0xF9–0xFB are forbidden).
Text strings UTF-8 encoded, NFC normalised (Unicode Normalization Form C).
Byte strings Raw bytes. No base64 encoding at the CBOR layer.
Duplicate keys Reject with error. Parsers MUST NOT silently accept duplicate map keys.
Simple values Only true (0xF5), false (0xF4), and null (0xF6) are permitted.
Optional fields Absent optional fields are omitted from the CBOR map entirely (not encoded as null). Present optional fields are included in sorted key order.

3.2 Verified Canonical Key Orders

Ang mga key order na ito ay normatibo. Ang mga implementasyon ay MUST mag-emit ng mga key sa eksaktong pagkakasunod na ito. Ang mga debug assertion ay SHOULD magberipika ng pagkakasunod sa mga non-release build.

QubEnvelope (version 0x01, unsigned, all optional fields absent):

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

Derivation ng key order ng QubEnvelope: bawat key ay isang CBOR text string. Encoded length = 1 byte header + string length (para sa mga string na mas mababa sa 24 bytes). Pagsunod-sunurin ayon sa kabuuang encoded length muna, pagkatapos ay lexicographically para sa mga key na may parehong haba.

SealedQub (version 0x01, public, no recipient):

"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 (pact body, 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 (row of the terms array):

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

PartyIdentifier (party_a / party_b map):

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

3.3 Sanggunian ng Byte Encoding

Type CBOR encoding Example
SHA3-256 hash (32 bytes) 0x58 0x20 + 32 bytes body_hash, qub_id
Timestamps (i64) Major type 0 (positive) or 1 (negative), shortest encoding Unix seconds
Version (u8, value 1) 0x01 (single byte)
Content type (u8, value 1) 0x01 (single byte)
sig_alg (u8, value 0) 0x00 (single byte)
ML-DSA-65 signature (3,309 bytes) 0x59 0x0C 0xED + 3,309 bytes author_signature, cosigner_signature
ML-DSA-65 public key (1,952 bytes) 0x59 0x07 0xA0 + 1,952 bytes author_pubkey, cosigner_pubkey

4. Mga Normatibong Derivation

4.1 qub_id

Ang qub_id ay natatanging tumutukoy sa isang qub at nagbubuklod sa QubEnvelope sa SealedQub. Ito ay deterministikong nakuha mula sa nilalaman ng 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 kapag wala ang outcome_at)
    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

Encoding ng domain separator: Ang string na "QUB_ID_V2" ay 9 ASCII bytes. Isang 0x00 padding byte ang idinaragdag upang maabot ang 10 bytes para sa pagkakahanay. Ang mga implementasyon ay MUST gumamit ng eksaktong 10 bytes na ito: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Encoding ng outcome_at: Pinalawak ng V1.1 ang preimage mula 92 patungong 100 bytes upang isama ang opsyonal na outcome_at field sa pagkakatali. Ang walang outcome_at ay naka-encode bilang 8 zero bytes; tinatanggihan ng mga protocol validator ang outcome_at <= 0 saanman, kaya ang sentinel na ito ay hindi maaaring mag-collide sa isang lehitimong halaga. Tingnan ang §3.2 (wire format) at ang in-tree na tasks/verdict-uplift-plan.md para sa verdict mechanic na nag-uudyok sa field na ito.

Encoding ng drand_round: Pinalawak ng V1.2 ang preimage mula 100 patungong 108 bytes upang isama ang drand_round (ang target drand round, §4.3) sa pagkakatali, at itinaas ang domain separator patungong QUB_ID_V2. Ito ang nag-uugnay ng timelock round sa pagkakakilanlan ng qub: hindi maaaring muling itali ng isang gateway ang ciphertext sa ibang round (hal. lumipas na) kaysa sa ipinahihiwatig ng ipinapakitang unlock_at. Bukod pa rito, bineberipika ng unlock procedure (§8) na ang round na nakabaon sa tlock ciphertext stanza ay tumutugma sa unlock_round(unlock_at), kaya ang ipinapakitang unlock time ay napapatunayang ang round na nagbabantay sa pag-decrypt.

Mga katangian:

4.2 body_hash

body_hash = SHA3-256(body)

Kung saan ang body ay ang raw Vec<u8> content payload. Para sa mga text qub, ito ang UTF-8 encoded na katawan ng qub.

4.2.1 title_hash

title_hash = SHA3-256(NFC(title).utf8_bytes)   if title is present
title_hash = [0u8; 32]                         if title is absent

Kung saan ang title ay ang opsyonal na plaintext title na ipinapakita sa countdown ng mambabasa bago ang paghahayag (tingnan §3.2). Ang NFC normalisation ay tumatakbo sa oras ng pag-hash upang ang digest ay stable sa mga visually-equivalent code-point sequence. Ang all-zeros sentinel ay nakalaan para sa kasong wala; ang isang walang lamang string ay tinatanggihan sa canonical CBOR boundary bilang non-canonical encoding ng "wala" (ang canonical encoding ay tinatanggal ang field nang buo).

4.3 Pagmapa ng Unlock-Round

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Parameter Source Example
unlock_at User-chosen Unix seconds UTC 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time drand chain info (genesis_time) 1595431050
chain_period_seconds drand chain info (period) 30

Pinipili ng ceil() na operasyon ang unang drand round na ang reveal time ay ≥ unlock_at. Sinisiguro nito na ang qub ay hindi nagiging decryptable bago ang napiling unlock time.

Edge case: kung ang (unlock_at - chain_genesis_time) ay tumpak na nahahati ng chain_period_seconds, ang resulta ay ang eksaktong round na iyon — ang qub ay nag-aalis ng selyo nang tumpak sa reveal time ng round na iyon.

Pagberipika: ang unlock_at ay MUST sa hinaharap sa oras ng pag-selyo. Ang unlock_at ay MUST NOT na hihigit sa 10 taon mula created_at (upang limitahan ang panganib ng long-horizon drand dependency; ang UI ay SHOULD magbabala para sa mga petsa ng paghahayag na lampas sa 2 taon).


5. Mga Newtype ng Wire Format

Ang mga newtype ng wire format ay nagbibigay ng compile-time na kaligtasan laban sa paglilito ng CBOR bytes sa JSON, raw plaintext, o iba pang byte encodings.

Type Contains Produced By Consumed By
SealedQubCbor Canonical CBOR of SealedQub serialize_sealed_qub() Permanent-storage upload, viewer fetch
QubEnvelopeCbor Canonical CBOR of QubEnvelope serialize_qub_envelope() tlock encrypt input, tlock decrypt output

5.1 Mga Panuntunan sa Construction

// 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 Pagberipika sa Construction

Ang from_encoded() ay SHOULD magberipika na ang input ay nagsisimula sa isang valid na CBOR map header. Ang buong structural validation ay nangyayari sa parse time, hindi construction time, upang maiwasan ang double-parsing.


6. Rehistro ng Content Type

Value Type Max Body Size Notes
0x00 Reserved (invalid) MUST NOT be used
0x01 Plain text (UTF-8, restricted Markdown) 50 KB paid / 10 KB free See §10 for rendering rules. The free / paid split is enforced by the upload service; the protocol-layer hard ceiling is 50 KB.
0x02 Reserved (future) Allocated for a future content type; not valid in v1. Viewers MUST reject per the rule below.
0x03 Pact (bilateral agreement, CBOR body) 100 KB Body is canonical CBOR PactTerms (§6.1). Cosigner signing per §9.7.
0x04 Verdict (self-grading ng tagalikha, CBOR body) 8 KB Ang body ay canonical CBOR VerdictBody (§6.2). Inilalabas lamang ng system-side na verdict intent. Ang relasyong magulang ay nasa Arweave tag na Parent-Tx-Id, hindi sa body. Tingnan ang verdict-uplift-plan §3.4.

Ang mga mambabasa ay MUST tanggihan ang mga hindi kilalang content type na may malinaw na error na nakikita ng gumagamit. Ang mga mambabasa ay MUST NOT subukang i-render ang mga hindi kilalang uri bilang text.

6.1 Katawan ng Kasunduan (content_type = 0x03)

Ang katawan ng kasunduan ay ang canonical CBOR encoding ng isang PactTerms na halaga:

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

Ang mga canonical CBOR key order para sa lahat ng tatlong map ay nasa §3.2. Ang kabuuang naka-serialize na pact CBOR ay MUST NOT lumampas sa 100 KB (tumutugma sa §6).

Tagapag-iba ng schema. Ang unang row sa terms para sa isang structured/v1 na kasunduan ay MUST { key: "pact_schema", value: "structured/v1" }. Ang mga row na walang ganitong marker ay "custom" na kasunduan at hindi tumatanggap ng structured validation o schema-aware rendering.

Frozen acknowledgement slots. Ang structured/v1 na kasunduan ay nagdadala ng eksaktong apat na acknowledgement row sa ilalim ng mga key na ito:

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

Ang value para sa bawat isa ay isa sa walong frozen English string na pinili ng (role, kind) pair, kung saan role ∈ { seller, buyer, provider, client } at kind ∈ { standard, capacity }. Ang mga string mismo ay normatibong datos ng protokol — parehong ML-DSA-65 signatures ng dalawang panig ay nangangako sa eksaktong bytes sa pamamagitan ng body_hash. HINDI sila isinasalin; ang naka-lagdang katawan ay neutral sa wika. Ang anumang pagbabago sa pananalita ay nangangailangan ng bagong bersyon ng schema (structured/v2).

Ang walong string, ang kanilang lookup (acknowledgement_for(role, kind)), at ang katwiran para sa bawat isa ay nakatakda ng reference implementation. Ang mga sumusunod na implementasyon ay MUST mag-emit ng byte-identical na acknowledgement values; ang golden-fixture SHA3-256 body-hash tests na sumasaklaw sa lahat ng apat na kombinasyon ng role ay nakahuli ng anumang pag-anod.

Pagkakasunod-sunod ng pagpapakita sa mambabasa. Ang mga acknowledgement string ay naglalaman ng mga parirala tulad ng "described above", na ipinagpapalagay na ang mga description / scope row ay nag-render nang nauna sa mga acknowledgement. Ang mga mambabasa ay MUST mag-render ng terms array sa pagkakasunod ng CBOR; ang pag-aayos muli ay sumisira sa semantiko ng prosa.

Pakikipag-ugnayan sa kabilang panig. Kapag ang contact ni Party B ay isang valid na email address, ang qub upload service ay awtomatikong nagpapadala ng review / co-sign invite email sa stage time at nagbubuklod sa magiging co-sign sa pagberipika ng parehong address na iyon (§9.7). Ang mga kasunduan na ang Party B contact ay wala ay maaari pa ring magkaroon ng kasamang lagda, ngunit sa pamamagitan lamang ng out-of-band channel — tinatanggihan ng serbisyo ang mga co-sign request na hindi makakagawa ng tumutugmang 15-minutong email-verification marker.

6.2 Katawan ng Hatol (content_type = 0x04)

Ang katawan ng hatol ay ang canonical CBOR encoding ng isang VerdictBody na halaga:

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; "ano ang nagbago, ano ang natutunan mo"
    evidence_url:    Option<String>,      // ≤ 2,048 bytes; HTTPS lamang; walang key kapag tinanggal
}

Canonical CBOR key order:

"outcome"          (8 encoded bytes)
"reflection"       (11 encoded bytes)  ← kung nasa loob lamang
"evidence_url"     (13 encoded bytes)  ← kung nasa loob lamang
"verdict_version"  (16 encoded bytes)

Ang kabuuang naka-serialize na verdict CBOR ay MUST NOT lumampas sa 8 KB (tumutugma sa registry row sa itaas).

Outcome enum. Ang wire byte ay neutral sa intent; ang apat na bucket na Right / Partial / Wrong / Unfalsifiable ay sumasaklaw sa buong outcome space ng bawat verdict-bearing intent. Ang mga per-intent label ("Tama ang hula" / "Tinupad ko" / "Naipadala" / "Pinatunayan" para sa Right, atbp.) ay isang viewer-side rendering concern na sinasagot laban sa intent ng magulang na qub — ang wire ay nananatiling neutral sa wika at intent. Ang mga value na nasa labas ng 1..=4 ay MUST tanggihan sa decode.

Pag-uugnay sa magulang. Ang isang verdict qub ay HINDI nagdadala ng reference sa magulang sa loob ng kanyang body. Ang Arweave transaction id ng magulang na qub ay inilalabas bilang Parent-Tx-Id storage tag sa upload time (§7 storage-tag layer). Pinapanatili nito ang body bilang isang self-contained na nakalagdang pahayag ng self-assessment; ang audit chain ("tama tungkol saan?") ay itinatatag sa pamamagitan ng Arweave-tag lookup.

Kaligtasan ng evidence URL (normatibo). Kapag mayroong evidence_url, ang mga validator (compose-side, wire-side, Worker edge) ay MUST ipatupad ang sumusunod:

  1. HTTPS lamang. Ang string ay MUST magsimula sa byte sequence na https://. Anumang ibang scheme — http, ftp, javascript, data, file, atbp. — ay tinatanggihan.
  2. Limitasyon ng haba. ≤ 2,048 bytes (praktikal na limitasyon ng browser URL).
  3. NFC + hostile-codepoint check. Parehong tuntunin sa title at reflection — ang mga bidi-override / zero-width / tag-block / BOM / C0 / C1 codepoint ay tinatanggihan. Ang depinisyon ay tumutugma sa Rust crate::handle::contains_hostile_text_codepoint at sa TS workers/api/src/utils/unicode.ts::isHostileCodepoint (panatilihing magkasabay).
  4. Walang whitespace, walang ASCII controls. Ang whitespace / DEL / sub-0x20 bytes saanman sa URL ay tinatanggihan — pinipigilan ang \n/\t injection vector na hindi sakop ng tuntunin ng bidi.
  5. Hindi walang-laman ang host segment. Lahat sa pagitan ng https:// at ng unang /, ?, o # ay MUST hindi walang laman.

Walang server-side fetching. Ang Worker ay MUST NOT mag-proxy, mag-fetch, o mag-preview ng URL. Ang protocol ay nag-iimbak ng isang string; ang rendering ay nangyayari viewer-side gamit ang rel="nofollow noopener noreferrer" target="_blank" at isang nakikitang host na ipinapakita kasama ng link text.

Repleksyon. Opsyonal na repleksyon na isinulat ng tagalikha ("ano ang nagbago, ano ang natutunan mo"). Parehong NFC + hostile-codepoint validation tulad ng title. Ang walang laman / whitespace-only input ay isasara sa absent sa construction time.

Bersyon ng schema. Ang v1 ay sumusuporta sa verdict_version = 0x01 lamang. Ang mga panghinaharap na schema revision ay magtataas ng byte na ito at darating kasabay ng bagong protocol version ayon sa §12.


7. Protokol ng Pag-selyo

Ang kumpletong sequence ng pag-selyo. Bawat hakbang ay normatibo.

 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.

Layer ng storage tag (out-of-band). Ang qub upload service ay nag-aattach ng sadyang maliit na set ng storage transaction tags kasama ang wrapped payload. Ang Content-Type=application/octet-stream ay normatibong kinakailangan. Ang reference service ay karagdagang nag-aattach ng tatlong opsyonal na tag kapag pinili ng tagalikha na ilantad ang mga ito: Intent (allowlist-validated compose intent — hal., quote, reply, commitment), Author (pubkey fingerprint ng tagalikha §9.3 bilang 64-char lowercase hex), at Parent-Tx-Id (storage transaction id ng magulang na qub para sa reply chains, 43-char base64url).

Ang Author tag ay opt-in bawat qub: ina-attach lamang ito ng reference creator app kapag tahasang pinagana ng gumagamit ang pampublikong attribution sa oras ng pag-selyo. Kapag naka-off ang toggle — ang default — walang Author tag na isinulat at ang qub ay walang attribution sa chain: walang anumang bagay sa permanenteng storage ang nag-uugnay sa upload sa handle, email, o ibang qub ng tagalikha. Kapag naka-on ang toggle, ang Author fingerprint ay nareresolba sa napiling @handle ng tagalikha sa pamamagitan ng §9.5 attestation chain. Ang mga reply-chain relationship at Intent ay hindi nagpapakilala. Ang outer wrapper (§13) ay nagpoprotekta sa panloob na katawan mula sa ciphertext correlation — pinipigilan ang isang harvester mula sa pagkilala at bulk-decryption ng mga qub-shaped upload pagkatapos i-publish ang kanilang drand round.

Ang reference service ay sinasadyang HINDI nag-aattach ng App-Name, App-Version, o Type tags: ang anumang ganitong single-value filter ay magbabalik ng buong qub corpus sa isang GraphQL query, na hindi tugma sa body-only confidentiality scope ng wrapper.

Ang isang sumusunod na verifier ay MUST NOT umaasa sa anumang storage tag para sa §11 third-party verification; ang body hash / qub_id / signature ay nangangako lamang sa panloob na CBOR, hindi sa tag set.


8. Protokol ng Paghahayag

Ang kumpletong sequence ng paghahayag. Bawat hakbang ay normatibo.

 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. Paglagda ng Pagiging May-akda

9.1 Katwiran

Ang mga qub ay permanenteng naka-imbak sa permanenteng storage. Ang mga lagda ng pagiging may-akda ay dapat manatiling hindi maaaring pekein nang walang hangganan, kaya naman ang v1.0 ay gumagamit ng post-quantum ML-DSA-65 scheme (FIPS 204) sa halip na isang klasikal na scheme na ang seguridad ay maaaring humina sa loob ng permanenteng buhay ng qub.

9.2 Rehistro ng Algorithm

sig_alg Scheme Key Size Signature Size
0x00 No signature (unsigned)
0x01 ML-DSA-65 (FIPS 204) 1,952 bytes 3,309 bytes

Ang mga mambabasa ay MUST tanggihan ang mga hindi kilalang sig_alg values.

9.3 Konstruksiyon ng Naka-lagdang Preimage

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)

Domain separator: "QUB_AUTHOR_SIG_V1" ay 17 ASCII bytes: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Walang padding.

Trailing byte: ang ika-91 preimage byte ay MUST 0x00. Ang reference implementation ay naglalantad nito bilang ang constant na ORG_ID_PRESENT_INDIVIDUAL = 0x00 sa crates/qub-core/src/signing.rs; ang mga mambabasang nagbubuo muli ng sig_input para sa pagberipika ay MUST mag-emit ng parehong byte.

Saklaw ng lagda — kung ano ang sakop at hindi. Ang sig_input ay nangangako sa apat na envelope field: version, qub_id, body_hash, unlock_at (kasama ang nakapirming domain separator at org_id_present byte). Tatlo sa apat na iyon ay structural invariants: ang qub_id mismo ay nakuha mula sa version, content_type, created_at, unlock_at, outcome_at, drand_round, at body_hash sa pamamagitan ng §4.1 preimage, kaya ang anumang pagbabago sa mga field na iyon ay gumagawa ng ibang qub_id at transitively na nagpapawalang-bisa sa lagda. Ang direktang pinapatotohanang ibabaw ay samakatuwid:

Field Authenticated by signature How
version Direct input to sig_input
qub_id Direct input
body_hash Direct input
unlock_at Direct input
content_type Transitively, via qub_id preimage
created_at Transitively, via qub_id preimage
outcome_at Transitively, via qub_id preimage
drand_round Transitively, via qub_id preimage (V1.2)
body Transitively, via body_hash = SHA3-256(body)
author_pubkey — (implicit) Key that verified the signature is the author, by definition
sender_label Display-only text; mutable without signature breakage
reply_to Threading pointer; mutable without signature breakage
cosigner_pubkey / cosigner_signature Independently signed over the same sig_input (see §9.7)
drand_chain_id, tlock_ciphertext, visibility Outer SealedQub fields, not inside the envelope — covered by their own structural invariants (round / chain consistency) but not by the author signature. (drand_round is now bound transitively via the qub_id preimage — see above.)

Mga implikasyon ng seguridad ng mga hindi pinatotohanang field.

Ang mga implementasyon na nagpapakita ng sender_label o reply_to sa mga end user ay MUST ilantad ang authenticated identity (pubkey fingerprint, attestation) bilang pangunahing signal ng pagkakakilanlan, hindi ang label.

9.4 Pamamaraan ng Pagberipika

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

Ang pagberipika ng lagda ay ang pinakamamahal na operasyon (lalo na ang ML-DSA-65). Ito ay SHOULD isagawa pagkatapos pumasa ang lahat ng mas murang pagsusuri (hash, qub_id, unlock_at).

9.5 Mga Attestation ng Pagkakakilanlan

Ang mga attestation ng pagkakakilanlan — ang pagmamapa ng author_pubkey sa mga claim ng pagkakakilanlan na nakikilala ng tao tulad ng qub handle, email address, social handle, o passkey credential — ay isang viewer-side progressive enhancement at hindi kinakailangan para sa pagberipika ng lagda. Ang mga mambabasang nagrereresolba ng attestation sa display identity ay MUST gamitin ang precedence:

handle > email > social > fingerprint

Ang fingerprint fallback ay ang lowercase hex ng SHA3-256(author_pubkey); palagi itong available para sa anumang naka-lagdang qub. Ang mga viewer ay MAY paikliin ito para sa pagpapakita — ang reference viewer ay nagre-render ng qub: na sinusundan ng unang at huling apat na bytes (qub:<8 hex>…<8 hex>).

Ang isang sumusunod na verifier ay maaaring makumpleto ang bawat tseke sa §9.4 nang hindi nakikipag-ugnay sa qub API, nang walang network maliban sa permanenteng storage at drand, at walang anumang server-side lookup. Ang attestation resolution ay isang hiwalay na best-effort step na isinasagawa lamang pagkatapos ng matagumpay na pagberipika ng lagda.

9.6 Epekto sa Laki

Ed25519 ML-DSA-65
Signature 64 bytes 3,309 bytes
Public key 32 bytes 1,952 bytes
Total per qub 96 bytes 5,261 bytes
Storage cost delta (at ~$5/MB) ~$0.0005 ~$0.026

Para sa isang text qub na 500–2,000 bytes, ang ML-DSA-65 ay halos triple ang naka-imbak na laki. Ang absolute cost ay napakaliit.

9.7 Pagberipika ng Kasamang Lagda (mga Bilateral na Kasunduan)

Para sa mga bilateral na kasunduan (content_type = 0x03), isang pangalawang signature layer ang nagpapatunay na pinagsang-ayunan ng dalawang panig ang parehong mga termino.

Mga envelope field:

Parehong field ay MUST naroroon nang sama-sama o parehong wala. Kung eksaktong isa lamang ang naroroon, ang mga mambabasa ay MUST mag-ulat ng integrity error.

Pamamaraan ng pagberipika:

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

Mga katangian:

Email-binding gate (operasyonal). Kapag ang isang staged na kasunduan ay nagdadala ng Party B email contact (§6.1), ang qub upload service ay MUST tanggihan ang co-sign request maliban kung mayroong panandaliang email-verification marker na tumutugma sa staging id at sa normalised-email hash ng contact na iyon. Ang marker ay isinusulat ng /api/v1/auth/verify kapag ang magic-link token ay nagdadala ng staging_id at ang naverify na address ay tumutugma sa SHA-256(normalise_email(party_b.contact)) — kung saan ang normalise_email(addr) ay nagpapanatili ng case ng local-part at nagpapababa lamang ng case ng domain part (ayon sa RFC 5321 §2.3.11), at ang SHA-256 dito ay ang NIST FIPS 180-4 hash (naiibang mula sa SHA3-256 na ginagamit sa §4 derivations) — at nag-eexpire 900 segundo (15 minuto) pagkatapos ng pag-issue. Ito ay operasyonal na anti-impersonation gate, HINDI bahagi ng on-chain qub proof — ang isang third-party verifier na nagre-replay ng §11 ay nangangailangan lamang ng permanenteng storage at drand, walang anumang server-side lookup. Ang marker ay umiiral sa server-side lamang at hindi kailanman bahagi ng naka-lagdang katawan.

Epekto sa laki (ML-DSA-65 may-akda + kasamang lumagda):

Component Size
Author signature 3,309 bytes
Author public key 1,952 bytes
Cosigner signature 3,309 bytes
Cosigner public key 1,952 bytes
Total crypto overhead 10,522 bytes
Storage cost delta ~$0.05

10. Pag-render at Pag-sanitise ng Markdown

Ang seksyong ito ay kritikal sa seguridad. Ang mambabasa ay nagre-render ng mga text qub (content_type = 0x01) gamit ang isang pinaghihigpitang subset ng Markdown.

10.1 Mga Pinapayagang Elemento

10.2 Mga Ipinagbabawal na Elemento

Element Handling
Raw HTML (<div>, <script>, etc.) Stripped entirely. No HTML passes through.
Images (![alt](url)) Stripped. Image syntax is removed from output.
Links ([text](url)) URL rendered as visible plain text. Not auto-linked. Not clickable without explicit user action.
Dangerous URL schemes javascript:, data:, vbscript:, file: — stripped.
Iframes, embeds, objects Stripped.
HTML entities Decoded to display characters only if safe.

10.3 Implementasyon

Ang mga implementasyon ay MUST gumamit ng strict allowlist parser, hindi blocklist. Ang inirerekomendang approach:

  1. I-parse ang Markdown gamit ang pulldown-cmark (o katumbas).
  2. Lakaran ang AST at i-drop ang anumang node na wala sa allowlist (§10.1).
  3. Para sa mga link node: i-emit ang URL bilang nakikitang text, hindi bilang isang clickable na <a> element.
  4. I-convert ang filtered AST sa isang typed intermediate representation (hal., isang MarkdownNode enum na may mga safe variant lamang). Ang raw HTML ay structurally unrepresentable sa IR na ito.
  5. Mag-render mula sa typed IR papunta sa target view layer (hal., reactive view components, DOM nodes). Walang HTML string concatenation o innerHTML sa anumang punto.

Ang mga blocklist approach ay marupok dahil ang mga bagong Markdown extension o parser quirk ay maaaring magpasok ng mga hindi nasala na elemento. Ang typed-AST approach ay ginagawang structurally imposible ang XSS — walang variant na maaaring magdala ng arbitrary HTML.

10.4 Mga Limitasyon sa Laki at Istruktura


11. Pagberipika ng Third-Party

Ang sinumang third party ay maaaring magberipika ng isang pampublikong qub nang walang kooperasyon ng qub. Ang pamamaraan ng pagberipika:

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.

Ano ang pinatutunayan ng pagberipika:

Proof What it establishes
Commitment The ciphertext existed by the permanent storage block timestamp.
Integrity The plaintext body matches the committed hash and has not been altered.
Timing The content was unreadable until the drand round, which corresponds to the chosen unlock time (subject to tlock and drand security assumptions).

Ano ang HINDI pinatutunayan ng pagberipika:

Non-proof Why
Authorship The sender_label is decorative. Without sig_alg0x01, anyone could have sealed this content.
Intent The qub proves content and timing, not what the creator subjectively meant.
Pre-event timing Storage block inclusion may lag actual upload by minutes. The commitment timestamp is the block time, not the moment the user pressed “seal.”

12. Bersyon

12.1 Bersyon ng Protokol

Ang version field (u8) sa parehong SealedQub at QubEnvelope ay kumikilala sa major protocol version.

12.2 Kasaysayan ng Bersyon

Version Value Description
v1 0x01 Public text qubs (content_type 0x01), pact bilateral agreements (0x03, structured/v1 schema, ML-DSA-65 author + cosigner), tlock, SHA3-256

12.3 Forward Compatibility

Ang isang v1 viewer na nakakaharap ng isang QubEnvelope na may mga hindi kilalang opsyonal na CBOR map key (mga key na wala sa §3.2 canonical order) ay SHOULD balewalain ang mga key na iyon at magpatuloy sa pagberipika gamit ang mga kilalang field. Pinapahintulutan nito ang mga hinaharap na maliit na karagdagan (hal., bagong metadata) nang hindi nangangailangan ng pagtaas ng major version.

Ang isang v1 viewer na nakakaharap ng sig_alg = 0x01 (ML-DSA-65) ngunit walang suporta sa pagberipika ng ML-DSA-65 ay SHOULD ipakita ang nilalaman ng qub na may "signature present but not verifiable" na abiso, hindi tanggihan ang qub nang buo. Ang reference implementation ngayon ay tinatanggihan ang bawat sig_alg value maliban sa 0x00 at 0x01 dahil ang v1 registry ay walang ibang valid na algorithm — ang mahigpit na pagtanggi at soft-fail ay observationally magkapareho hanggang sa mairehistro ang ikatlong algorithm. Ang soft-fail na ugali sa itaas ay nagiging load-bearing kapag ang §9.2 ay tumanggap ng bagong entry, at ang reference viewer ay i-aapdeyt para mag-soft-fail sa puntong iyon.

12.4 Bersyon ng Outer Wrapper

Ang OuterWrapper na inilarawan sa §13 ay nagdadala ng sarili nitong version byte, independyente mula sa SealedQub.version at QubEnvelope.version. Ang dalawang version space ay umuunlad nang hiwalay: ang isang hinaharap na post-quantum-safe symmetric replacement ay nagpapataas sa wrapper byte nang hindi hinahawakan ang panloob na protocol version, at ang isang hinaharap na protocol-layer addition (hal., bagong envelope field) ay nagpapataas sa panloob na version nang hindi hinahawakan ang wrapper byte.

OUTER_WRAPPER_VERSION_* Value Algorithm Status
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM with 12-byte nonce, 16-byte authentication tag, AAD bound to qub_id v1 default
0x020xFF Reserved Future

Ang mga mambabasa ay MUST tanggihan ang mga hindi kilalang wrapper version na may malinaw na error. Sadyang pinananatili ng protokol na makitid ang wrapper version space hangga't walang kongkretong migration driver na lumitaw (hal., NIST guidance na nagsusulong ng ibang AEAD); isang 0x02 slot ang ilalaan sa parehong rebisyon na nagpapakilala ng algorithm.


13. Outer Encryption Wrapper

13.1 Katwiran

Ang mga protocol layer (QubEnvelope → tlock → SealedQub) ay gumagawang time-locked ng isang naselyong qub: ang katawan ay hindi mababasa hanggang sa unlock_at at na-publish ang drand round signature. Pagkatapos ng paghahayag, gayunpaman, ang round signature ay pampubliko at ang canonical CBOR shape ng SealedQub ay nakikilala, kaya ang isang harvester na nag-index ng mga permanenteng-storage transaction ay maaaring bulk-decrypt ang buong qub corpus.

Sinasara ng outer encryption wrapper ang channel na iyon sa pamamagitan ng paggitna ng karagdagang symmetric AEAD layer sa pagitan ng canonical SealedQubCbor at ng bytes na isinusulat sa permanenteng storage. Ang 256-bit key na K ay nabubuhay lamang sa URL fragment ng delivery URL at sa mga device ng gumagamit; ang mga browser ay hindi nagpapadala ng URL fragments sa mga server, kaya ang qub.social, bawat storage gateway, at bawat CDN sa harap ng alinman dito ay observationally bulag sa K. Ang bawat qub sa permanenteng storage ay samakatuwid ay isang opaque ciphertext na ang plaintext ay hindi mababawi nang walang URL na pinili ng tagalikha na ibahagi.

Net effect:

13.2 Layering

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)

Ang pag-selyo at paghahayag sa protocol layer (§7, §8) ay hindi nagbabago sa ilalim ng wrapper boundary; ang wrapper ay nakakabit sa call site ng seal() at nahihiwalay sa call site ng unlock().

13.3 Istruktura ng Datos ng 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
}

Mga invariant ng field.

Encoding ng CBOR. Canonical CBOR ayon sa §3, na may parehong key-ordering rule (na-sort ayon sa encoded byte length na pataas, pagkatapos ay lexicographically). Ang apat na key ay:

Key Encoded bytes Order
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Ang unang byte ng OuterWrapper CBOR ay samakatuwid ang definite-length map header para sa 4-entry map (0xA4).

13.4 AAD Binding sa qub_id

Iniuugnay ng wrapper ang qub_id bilang AEAD additional authenticated data. Ito ay ang load-bearing structural defence laban sa tatlong klase ng atake:

Attack Defence
Move ciphertext under a different qub_id field in the wrapper AAD mismatch → AEAD authentication fails
Mix the URL fragment of qub A with the permanent-storage bytes of qub B AAD mismatch → AEAD authentication fails
Tamper with the qub_id field of the wrapper after upload AAD mismatch → AEAD authentication fails

Ang pagdadala ng qub_id sa wrapper plaintext ay hindi nagpapahina sa enumeration immunity nang makabuluhan — ang qub_id mismo ay isang SHA3-256 hash ng §4.1 preimage na walang mababawing preimage mula sa digest, at ang isang enumerator na nag-harvest na ng wrapper bytes ay walang natututunan mula sa nakikitang qub_id na hindi nila mahihinuha mula sa pag-iral mismo ng upload.

13.5 Mga Wrap at Unwrap Algorithm

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

Pagbagsak ng failure-mode. Ang maling K, maling nonce, AAD mismatch, at tampered ciphertext ay lahat gumagawa ng parehong DECRYPT_FAILED error. Ito ay sadyang AEAD property: ang pagkilala sa failure mode ay magbubuo ng side channel na maaaring sundutin ng isang malayong umaatake sa pamamagitan ng pagpapadala ng malformed wrapper at pag-time ng tugon. Ang mga reference implementation ay MUST i-collapse ang lahat ng AEAD failure sa iisang error shape.

13.6 Susing Materyal at Pamamahagi

Ang wrapping key na K ay isang 256-bit uniform random value na nabuo bawat qub ng isang CSPRNG. Ang mga reference implementation ay kumukuha nito mula sa:

Pamamahagi: ang K ay MUST naka-encode bilang URL-safe base64 (RFC 4648 §5, walang padding) at idinaragdag sa delivery URL bilang fragment component:

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

Ang fragment ay hindi kailanman ipinapadala sa anumang server ng isang sumusunod na browser. Ang mga recovery channel (server-side history index, opt-in email auto-send) na nagpapanatili ng buong link sa paghahatid — kasama ang fragment — lampas sa device ng gumagamit ay isang tahasang trade laban sa default crypto-shredding posture at MUST naka-gate sa tahasang pahintulot ng gumagamit.

Pagkawala ng fragment. Kung mawawalan ang isang gumagamit ng URL fragment at walang recovery channel, ang qub ay hindi mababasa. Ito ay ang load-bearing trade-off ng disenyo at MUST ipakita sa gumagamit sa oras ng pag-selyo. Pinalalakas ng MVP ang seal-time disclosure na may tahasang "save this URL" copy at isang verified-email recovery channel para sa mga gumagamit na nag-opt in.

13.7 Wala sa Saklaw para sa Seksyong Ito

13.8 Mga Pampublikong qub (pag-alis ng wrapper)

Ang outer wrapper ay opsyonal sa delivery layer. Maaaring selyuhan ng isang tagalikha ang isang qub bilang pampubliko, kung saan ang canonical SealedQubCbor ay isinusulat sa permanenteng storage nang direkta, nang walang OuterWrapper layer at walang key na K:

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

Ang isang pampublikong qub ay time-locked ngunit hindi link-gated: nananatili itong hindi mababasa hanggang sa ma-publish ang drand round nito (ang tlock layer ay hindi nagbabago), ngunit pagkatapos ng paghahayag, sinuman ang may arweave_tx_id ay maaaring mag-decrypt nito — walang kailangang URL fragment, dahil walang K. Ito ang sinadyang trade para sa mga surface na kailangang patakbuhin ng server: ang mga reveal-notification email, mga third-party embed, at mas mayamang post-reveal SEO ay lahat nangangailangan ng isang link na gumagana nang walang lihim na hindi kailanman hawak ng server (§13.6).

Mga kahihinatnang DAPAT isaalang-alang ng isang producer:

Ang pribado (naka-wrap) ay nananatiling default; ang pampubliko ay isang tahasang per-qub na pagpili ng tagalikha.


14. Mga Test Vector

14.1 qub_id Derivation

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

Ang mga implementasyon ay MUST gumawa ng magkaparehong body_hash at qub_id na halaga para sa input na ito. Ang test vector na ito ay SHOULD ang unang unit test na isinulat. Ang mga canonical value sa itaas ay kinakalkula ng reference implementation at MUST tugma nang bit-for-bit. Mga historical na layout ng preimage (pre-launch — walang live na qub ang umaasa sa mga ito): ang 92-byte V1.0 qub_id ay 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; ang 100-byte V1.1 qub_id (pagkatapos isama ang outcome_at_or_zero) ay b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. Isinasama ng V1.2 ang drand_round at itinataas ang domain separator patungong QUB_ID_V2.

14.2 Pagmamapa ng Unlock-Round

Input:
  unlock_at           = 1735689600
  chain_genesis_time  = 1595431050
  chain_period_seconds = 30

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

drand_round = 4675285

14.3 Canonical CBOR Round-Trip

Ang mga implementasyon ay MUST magberipika na serialize(parse(serialize(qub))) == serialize(qub) para sa lahat ng valid na input. Ito ay isang property test, hindi iisang vector.

14.4 PactTerms CBOR (content_type 0x03)

Input:
  pact_version = 1
  title        = "Scooter deposit"
  terms        = [
    { key: "Item",    value: "Honda Metropolitan scooter" },
    { key: "Price",   value: "$100" },
    { key: "Deposit", value: "$10" }
  ]
  party_a      = { label: "Alice" }
  party_b      = { label: "Bob", contact: "bob@example.com" }
  notes        = absent

Canonical CBOR key order (PactTerms):
  "notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)

Canonical CBOR key order (PactTerm):
  "key"(4) < "value"(6)

Canonical CBOR key order (PartyIdentifier):
  "label"(6) < "contact"(8)

Ang canonical CBOR bytes at SHA3-256 body_hash ay kinakalkula ng reference implementation. Ang mga implementasyon ay MUST gumawa ng byte-identical na CBOR para sa input na ito.

Ang mga implementasyon ay MUST din magberipika na serialize(parse(serialize(pact))) == serialize(pact) para sa lahat ng valid na PactTerms na input (property test).

14.5 Mga Cross-Language Vector ng Outer Wrapper

Ang outer wrapper (§13) ay may hiwalay na canonical fixture sa crates/qub-core/tests/vectors/wrapper_v1.json. Bawat kaso ay nag-aayos ng isang (key, nonce, qub_id, sealed_cbor) tuple bilang opaque hex inputs at nag-aassert ng tiyak na expected_wrapper_hex output. Parehong reference implementations ay kumokonsumo ng parehong JSON file:

Ang fixture ay kasalukuyang nag-pin ng tatlong kaso:

Case Coverage
basic-text-public Smallest realistic SealedQub shape; no optional fields. Establishes the canonical wrapper shape for a v1.0-typical qub.
with-recipient-pubkey SealedQub with recipient_pubkey set (Phase 2 path). Different inner CBOR key set, different qub_id.
longer-body ~4 KiB body — exercises multi-byte CBOR length prefixes inside both the inner envelope and the outer ciphertext.

Ang mga implementasyon ay MUST gumawa ng byte-identical na expected_wrapper_hex para sa mga naitalang input. Ang pag-regenerate ng fixture ay nangangailangan ng QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors at nakalaan para sa mga sadyang pagbabago sa format.


15. Pamamahala ng Crypto Profile (Hinaharap)

Ang seksyong ito ay impormatibo para sa v1 at magiging normatibo sa unang pagkakataong pumasok ang ikalawang algorithm sa anuman sa mga kriptograpikong primitibo ng qub.

15.1 Kasalukuyang Posisyon

Ang protokol v1 ay nagbubuklod ng eksaktong isang algorithm bawat primitibo:

Kasalukuyang naka-hardcode ng mga verifier ang key at signature lengths bawat primitibo. Walang agility surface na nailantad sa wire format.

15.2 Inaasahang Hugis

Kapag pumasok ang ikalawang algorithm sa protokol, ang verifier ay iko-configure para sa isang pinangalanang CryptoProfile (hal., ExqubV1) na naglilista sa eksaktong set ng mga pinahihintulutang value bawat primitibo — sig_algs, drand chains, wrapper versions, content types. Ang profile ay nakapirmi sa verify time, hindi kailanman pinag-uusapan in-band. Ang anumang value sa labas ng aktibong profile ay tinatanggihan.

Tinitiyak nito na ang pagdaragdag ng ML-DSA-87 o pag-activate ng Ed25519 ay hindi maaaring retroactively magpahina sa mga umiiral na verifier configuration: ang isang v1 verifier ay nananatiling isang v1 verifier kahit na pagkatapos i-publish ang v2 profile.

15.3 Mga Kondisyon ng Trigger

I-promote ang §15 sa normatibong status kapag ang sumusunod ay iminungkahi:

Hanggang sa, ang §15 ay isang placeholder na nag-aayos ng migration shape upang ang mga hinaharap na PR ay mapunta sa isang kilalang target sa halip na muling pag-usapan ang negotiation surface mula sa simula.