qub Protocol Specification
Ang qub ay isang protokol para sa mga kriptograpikong pangakong nakatali sa oras: isang sistemang nagseselyo 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. |
| Unknown keys | Reject with error. Parsers MUST NOT tolerate map keys outside the type's canonical key set — two distinct canonical byte strings must never decode to the same value (encode(decode(x)) == x), and for signed payloads an extra key would be hidden content both signatures commit to. Schema evolution goes through version, never extra 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:
- Ang pagbabago ng anumang field sa QubEnvelope (body, timestamps, content type, version) ay gumagawa ng ibang qub_id.
- Ang qub_id ay kinakalkula bago ang pag-encrypt. Parehong QubEnvelope at SealedQub ay may parehong qub_id. Pinapatotohanan ng mambabasa na sila ay tugma pagkatapos ng pag-decrypt.
- Ang qub_id ay hindi nakadepende sa
sender_label,author_signature, oauthor_pubkey. Ibig sabihin nito, ang parehong nilalamang isinelyo sa parehong oras ay gumagawa ng parehong qub_id anuman ang lumagda nito. - Ang pagbabago ng
titleng SealedQub (na ang lahat ng iba ay nakapirmi) ay nagpapabago ngqub_idsa pamamagitan ngtitle_hash. Hindi nga makapagpapalit ang gateway ng plaintext title na ipinapakita sa countdown nang hindi pinawawalang-bisa ang pagkakakilanlan ng qub. - Ang pagbabago ng SealedQub
outcome_at(na ang lahat ng iba ay nakapirmi) ay nagpapabago ngqub_idsa pamamagitan ng preimage. Hindi maaaring palitan ng isang gateway ang pre-reveal verdict-on date na ipinapakita sa countdown nang hindi pinawawalang-bisa ang pagkakakilanlan ng qub. - Ang pagbabago ng
drand_round(na ang lahat ng iba ay nakapirmi) ay nagpapabago ngqub_idsa pamamagitan ng preimage. Hindi maaaring muling itali ng isang gateway ang timelock ciphertext sa ibang round nang hindi pinawawalang-bisa ang pagkakakilanlan ng qub; kasama ang §8 unlock-time stanza-round check, ang ipinapakitangunlock_atay ang round na talagang nagbabantay sa pag-decrypt.
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 kasunduang 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:
- HTTPS lamang. Ang string ay MUST magsimula sa byte sequence na
https://. Anumang ibang scheme —http,ftp,javascript,data,file, atbp. — ay tinatanggihan. - Limitasyon ng haba. ≤ 2,048 bytes (praktikal na limitasyon ng browser URL).
- NFC + hostile-codepoint check. Parehong tuntunin sa
titleatreflection— ang mga bidi-override / zero-width / tag-block / BOM / C0 / C1 codepoint ay tinatanggihan. Ang depinisyon ay tumutugma sa Rustcrate::handle::contains_hostile_text_codepointat sa TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(panatilihing magkasabay). - Walang whitespace, walang ASCII controls. Ang whitespace / DEL / sub-
0x20bytes saanman sa URL ay tinatanggihan — pinipigilan ang\n/\tinjection vector na hindi sakop ng tuntunin ng bidi. - 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
Dalawang bersyon ng preimage ang umiral. Ang lahat ng lagda ay MUST gumamit ng V2, at ang mga verifier ay MUST tumanggap lamang ng V2. Ang legacy na V1 preimage (dinodokumento sa ibaba para sa makasaysayang sanggunian) ay tinanggap bilang verification-only na fallback noong migration patungong V2; ang fallback na iyon ay inalis na at ang isang V1-only na lagda ay tinatanggihan na ngayon.
V2 (kasalukuyan — ginagawa ng lahat ng bagong paglagda ng may-akda, at ng parehong lagda ng pact staging / cosign flow):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V2" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 || // u8 (1 byte): MUST be 0x00 in v1.x
sender_label_hash || // [u8; 32]: SHA3-256(NFC(sender_label)),
// or 32 zero bytes when absent
reply_to_or_zero // [u8; 32]: parent qub_id, or 32 zero
// bytes when absent
)
// Total preimage: 155 bytes → 32-byte hash
signature = Sign(author_secret_key, sig_input)
Ang sender_label_hash ay sumusunod sa parehong absent-sentinel na kombensiyon tulad ng title_hash (§4.2.1): ang 32 zero bytes ay hindi wastong SHA3-256 output, kaya ang "absent" ay hindi kailanman maaaring mag-collide sa isang naroroong label. Ang lahat ng field ay fixed-width, kaya ang preimage ay hindi malabo kahit walang length prefix.
V1 (legacy — INALIS NA; hindi na ginagawa at hindi na tinatanggap sa pagberipika):
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
Ang V1 preimage ay nag-omit ng sender_label at reply_to. Ito ay tinanggap bilang verification-only na fallback noong migration patungong V2; ang fallback na iyon ay inalis na — ang mga verifier ay MUST tumanggap lamang ng V2 preimage. Ang depinisyon ay pinananatili dito para sa makasaysayang sanggunian at upang ipaliwanag ang domain separator sa ibaba. Ang isang lagda na nagve-verify lamang laban sa V1 ay MUST ituring bilang verification failure.
Mga domain separator: ang "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" ay tig-17 ASCII bytes ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). Walang padding. Ang naiibang separator ay nagbibigay ng domain separation sa dalawang konstruksiyon, kaya ang isang lagda sa isang preimage ay hindi kailanman maaaring mag-verify bilang ang isa pa.
org_id_present byte: ang byte na kasunod ng unlock_at 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 V2 na sig_input ay direktang nangangako sa version, qub_id, body_hash, unlock_at, sender_label, at reply_to (kasama ang nakapirming domain separator at org_id_present byte). 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 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 |
sender_label |
✓ | Direct input via sender_label_hash (V2 preimage — the only accepted form) |
reply_to |
✓ | Direct input via reply_to_or_zero (V2 preimage — the only accepted form) |
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 |
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.) |
Bakit ang V2 lamang ang tinatanggap na preimage.
- Sa ilalim ng inalis nang V1 preimage, ang isang panig na may write access sa mga naka-imbak na bytes ay maaaring magpalit ng
sender_label("Alice" → "Mallory") o mag-re-parent ngreply_to— at muling mag-encrypt pagkatapos ng round — nang hindi pinawawalang-bisa ang lagda ng may-akda, dahil wala sa alinmang field sa naka-lagdang preimage. Sinasaklaw ng V2 ang dalawa, kaya ang anumang pagbabago sa alinmang field ay nagbabago ng pagberipika patungong "nabigo". Dahil ang mga verifier ngayon ay tumatanggap lamang ng V2, ang palitan na ito ay sarado na para sa bawat lagda: ang isang lagda na hindi nagbubuklod sa alinmang field (i.e. nagve-verify lamang laban sa V1) ay tinatanggihan nang tuluyan sa halip na i-downgrade dito. - Ang
author_pubkeysa loob ng envelope ay nananatiling tunay na anchor ng pagkakakilanlan — ang mga mambabasa ay MUST kumuha ng display identity mula saauthor_pubkey(sa pamamagitan ng §9.5 attestation layer) sa halip na magtiwala sasender_label.
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 (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
only accepted form — the legacy V1 fallback is retired (§9.3), so a
signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."
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:
cosigner_pubkey: ML-DSA-65 public key ng counter-signer (Party B).cosigner_signature: Lagda sa parehongsig_inputtulad ng sa may-akda (§9.3).
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 (V2 only — the
legacy V1 fallback is retired; all pact clients produce V2 signatures).
5. Verify(cosigner_pubkey, sig_input, cosigner_signature).
6. Success → display "co-signed by [cosigner fingerprint]."
7. Failure → display "co-signature verification failed."
Mga katangian:
- Ang kasamang lumagda ay pumipirma ng magkaparehong
sig_inputtulad ng may-akda — parehong panig ay nangangako sa parehongqub_id,body_hash, atunlock_at(at, sa ilalim ng V2, ang parehongsender_label_hashatreply_to_or_zero). - Upang mabuo muli ng counter-signer ang V2 preimage nang walang access sa raw envelope bytes, ang staging service ay nagpapatupad sa stage time na ang
sender_labelng isang pact envelope ay katumbas ngpact_terms.party_a.labelat na angreply_toay wala. Parehong totoo para sa bawat reference-client na pact; ang mga lumalabag na envelope ay tinatanggihan sa staging. - Ang derivation ng
qub_id(§4.1) ay HINDI kasama ang mga field ng kasamang lumagda. Ang pagdaragdag ng kasamang lumagda sa umiiral na envelope ay hindi nagpapabago ngqub_id. - Ang isang kasunduan ay maaaring author-signed lamang (one-sided commitment), cosigner-only (hindi karaniwan), o pareho (full bilateral proof).
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
- Mga heading:
#hanggang####(walang#####o######) - Emphasis: bold (
**), italic (*), strikethrough (~~) - Mga listahan: ordered (
1.) at unordered (-,*) - Blockquotes (
>) - Code: inline spans (```) at fenced blocks (`````)
- Mga horizontal rule (
---) - Mga line break (dalawang trailing space o blangkong linya)
- Mga talata
10.2 Mga Ipinagbabawal na Elemento
| Element | Handling |
|---|---|
Raw HTML (<div>, <script>, etc.) |
Stripped entirely. No HTML passes through. |
Images () |
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:
- I-parse ang Markdown gamit ang
pulldown-cmark(o katumbas). - Lakaran ang AST at i-drop ang anumang node na wala sa allowlist (§10.1).
- Para sa mga link node: i-emit ang URL bilang nakikitang text, hindi bilang isang clickable na
<a>element. - I-convert ang filtered AST sa isang typed intermediate representation (hal., isang
MarkdownNodeenum na may mga safe variant lamang). Ang raw HTML ay structurally unrepresentable sa IR na ito. - Mag-render mula sa typed IR papunta sa target view layer (hal., reactive view components, DOM nodes). Walang HTML string concatenation o
innerHTMLsa 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
- Pinakamataas na rendered heading depth:
####(H4). Ang#####at mas malalim ay nire-render bilang bold text. - Walang limitasyon sa bilang ng talata (ang mga limitasyon sa laki ng katawan sa §6 ang nagdidikta).
- Mga fenced code block: walang syntax highlighting sa MVP. Naka-render bilang monospace preformatted text.
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_alg ≥ 0x01, 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.
- Ang mga mambabasa ay MUST tanggihan ang mga hindi kilalang major version na may malinaw na error.
- Sa loob ng isang kilalang major version, ang mga decoder ay MUST tanggihan ang mga hindi kilalang map key (§3.1) — ang schema evolution ay nangyayari sa pamamagitan ng pagpapakilala ng bagong
version, hindi sa pagdaragdag ng mga key na lalaktawan lamang ng mga umiiral na decoder. (Pinahintulutan ng mga naunang rebisyon ng spec na ito ang pagtanggap sa mga hindi kilalang opsyonal na field; binawi na ang sugnay na iyon — ginawa nitong non-injective angencode(decode(x))at nagbukas ito ng hidden-signed-content vector sa mga pact payload.) - Ang mga content type (
content_type) at signature scheme (sig_alg) ay version-gated: ang mga bagong value ay maaari lamang ipakilala kasabay ng bagong protocol version o tahasang update sa rehistro.
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 CBOR map key (mga key na wala sa §3.2 canonical order) ay MUST tanggihan ito nang may decode error (§3.1). Ang forward compatibility ay nakasandal sa version field, hindi sa key tolerance: ang mga hinaharap na karagdagan — kahit maliit na metadata — ay ipinapadala sa ilalim ng bagong version value, na tinatanggihan ng isang v1 viewer nang may malinaw na "newer protocol" na error sa halip na tahimik na ibaba ang nilalamang binibigkis ng mga signature.
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 |
| — | 0x02–0xFF |
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:
- Enumeration immunity by default. Ang wrapped bytes sa permanenteng storage ay byte-indistinguishable mula sa arbitrary ciphertext. Ang isang harvester strategy ng "GraphQL-query para sa qub-shaped uploads, bulk-decrypt with public drand signatures" ay hindi nagtatapos sa plaintext.
- Crypto-shredding privacy posture. Ang qub.social ay literal na hindi makakapag-decrypt ng sarili nitong corpus. Ang mga subpoena ay umaabot sa ciphertext, hindi sa plaintext.
- Two-tier confidentiality ladder. Default = link-controlled access (seksyong ito). Ang recipient-encrypted private qubs (isang nakareserbang feature sa Phase 2, hindi pa naitatakda) ay nag-layer sa ibabaw bilang ikalawang tier.
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.
- Ang
versionay MUST katumbas ng0x01para sa v1.0 wrapper bytes. - Ang
qub_iday MUST katumbas ngqub_idfield ng SealedQub na nabawi pagkatapos ng pag-unwrap. Ang hakbang sa pag-unwrap ay hindi direktang ipinapatupad ito (ang AEAD AAD binding ay ginagawang imposible ang byte-level tampering), ngunit ang unlock layer ay nagsusuri ng kaugnayan nang transitively: kung ang isang tagalikha ay nag-wrap ngSealedQubCborna ang panloob naqub_iday hindi tumutugma sa wrapperqub_id, ang §8 step 11 ay nabigo. - Ang
nonceay MUST 96 bits (12 bytes), nabuong sariwa ng isang CSPRNG para sa bawat wrap operation. Ang pag-reuse ng nonce sa ilalim ng parehong key ay nagpapahintulot ng mga AEAD nonce-reuse attack na nagbabawi ng plaintext; ang mga producer ay MUST tratuhin ang (key,nonce) pairs bilang one-shot. - Ang
ciphertextay ang AES-256-GCM output: ciphertext bytes na pinagsama sa 16-byte authentication tag.ciphertext.len() == SealedQubCbor.len() + 16nang eksakto.
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:
- WASM creator:
getrandom(WebCrypto sa ilalim ngwasm_jsbackend). - Caller ng server-side seal API: ang lokal nitong CSPRNG; ang caller ang naglalaan at nagpapanatili ng
Kbilangwrapper_key_b64url. Ginagamit ng Worker angKsa memorya para sa wrapper ngunit MUST NOT itong panatilihin. Nagbibigay-daan ito sa isang idempotent na retry na mabawi ang isang redacted na response gamit ang napanatiling capability ng caller sa halip na umasa sa isang one-shot na secret na nabuo ng server.
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
- Ang paglagda ng pagiging may-akda (§9) ay hindi nagbabago: ang mga lagda ay kinakalkula sa loob ng panloob na
QubEnvelopeat nababawi pagkatapos ng unwrap → tlock decrypt → CBOR parse. - Ang mga recipient-encrypted private qub (isang nakareserbang feature sa Phase 2, hindi pa naitatakda) ay nakakabit sa ibabaw ng wrapper na ito bilang pangalawang tier ng pagiging kompidensyal; pareho ang mga tier ay maaaring aktibo nang sabay.
- Ang mga kasunduan (§6, content_type
0x03) ay naka-wrap nang eksaktong tulad ng mga text qub; ang wrapper ay byte-blind sa panloob na content type.
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:
- Walang enumeration immunity. Ang mga pampublikong qub ay sa pamamagitan ng disenyo ay tinatalikuran ang §13.1 enumeration-immunity property. Ang reference upload service ay naglalagay ng
Visibility: publicpermanent-storage tag sa kanila (at sa kanila lamang) upang sila ay sadyang matuklasan; ang mga pribadong qub ay walang ganitong tag at pinananatili ang kanilang byte-indistinguishability. - Nakalantad ang plaintext title sa oras ng pag-selyo. Ang §3.2
titlefield ay plaintext sa loob ngSealedQubCbor. Sa ilalim ng wrapper ay nakatago ito hanggang magbigay ang isang mambabasa ngK; kapag walang wrapper, mababasa ito ng buong mundo sa permanenteng storage mula sa sandali ng pag-upload, bago ang paghahayag. Ang mga sumusunod na creator app ay DAPAT ipaalam ito sa oras ng pag-selyo. - Istruktural ang pagtukoy. Ang isang sumusunod na mambabasa/embed ay nakikilala ang dalawang shape sa pamamagitan ng parse: ang mga byte na nag-parse bilang
OuterWrapperay tumatahak sa unwrap-with-Kna landas; ang mga byte na nag-parse bilang isang hubad naSealedQubCboray tinatanggap nang direkta. Walang kailangang wire flag, at angqub_iday hindi nagbubuklod ng visibility — ang parehong nilalaman ay byte-identical saSealedQublayer maging selyadong pampubliko o pribado.
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:
- Rust:
crates/qub-core/tests/wrapper_vectors.rs(cargo test -p qub-core --test wrapper_vectors). - TypeScript:
workers/api/src/crypto/__tests__/wrapper.test.ts(npm test).
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:
- Lagda: ML-DSA-65 (
sig_alg = 0x01; 1952-byte public key, 3309-byte signature) at unsigned (sig_alg = 0x00). Ang §9.2 registry ay walang tinukoy na ibang value; ang isang v1 verifier ay MUST tanggihan ang bawatsig_algsa labas ng{0x00, 0x01}. Ang isang hinaharap na Ed25519 entry ay inaasahan (§15.3) ngunit hindi nakalaan sa v1. - Timelock: drand quicknet lamang — ang chain hash, public key, genesis time, at period ay nakapirming network parameters na dala ng reference na
DrandTimelockProvider::quicknet()(crates/qub-core/src/tlock.rs) atconfig/drand-endpoints.json. - Outer wrapper: AES-256-GCM v1 lamang (§13).
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:
- Ang ikalawang
sig_algbyte (Ed25519 activation, ML-DSA-87, o anumang bagong entry sa §9 registry). - Ang ikalawang drand chain sa production use.
- Ang ikalawang outer-wrapper version.
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.
16. Transparency Log at Mga Durability Tier (Disenyo — kumpleto na ang review)
Katayuan. Ang seksyong ito ay isang espesipikasyon ng disenyo. Ang mga wire format, hashing, at trust model sa ibaba ay normatibo para sa implementasyon, ngunit wala pang code ng transparency-log na naipadala. Kumpleto na ang W5 external review: itinatala ng §16.15 ang mga naresolbang desisyon at ang mga nakatatali na launch constraint na lumabas mula rito. Maaaring magpatuloy ang implementasyon sa ilalim ng mga constraint na iyon. Ang §16 ay nananatiling forward-looking sa parehong diwa ng §15 — inaayos nito ang target upang ang implementasyon ay dumapo laban sa isang settled na disenyo sa halip na muling i-derive ang trust model sa code review. Ito ay mahigpit na additive — bawat umiiral na qub ay pinananatili ang sarili nitong indibidwal na Arweave transaction at walang pagbabago sa
SealedQub/QubEnvelopewire format.
16.1 Katwiran at Mga Durability Tier
Ngayon, ang durability ng isang qub at ang temporal commitment nito ay parehong nakasalalay sa iisang per-qub na Arweave transaction (§11). Ikinakabit nito ang seal latency sa Arweave finality, ginagawang isang product cost cap (ARWEAVE_DAILY_CEILING) ang per-qub upload, at hindi nagbibigay ng tamper-evident na ordering sa mga qub. Nagdaragdag ang transparency log ng dalawang layer sa ilalim at paligid ng iisang tier na iyon:
| Tier | Pangalan | Garantiya | Kailan |
|---|---|---|---|
| T1 | R2-first synchronous ack | Durability floor — ang mga selyadong byte ay naisusulat sa durable storage bago bumalik ang seal (< 300 ms p95). |
Bawat qub, synchronously (§16.10). |
| T2 | Batched transparency-log inclusion | Universal append-only, tamper-evident commitment + total ordering, naka-anchor sa Arweave. | Bawat qub, deferred + batched (§16.5–16.7). |
| T3 | Per-qub na Arweave permanence | Isang indibidwal na Arweave transaction para sa qub. | Bayad na upsell, at ang Arweave-unavailability fallback (§16.8). |
Ginagawa ng T2 na isang pagpipilian (T3) ang per-qub na Arweave sa halip na ang tanging landas ng durability. Ang ARWEAVE_DAILY_CEILING ay tinatanggal bilang product cap at ibinababa sa isang circuit breaker sa dedikadong anchor wallet lamang (§16.7); ang mga seal ng gumagamit ay hindi kailanman tinatanggihan dahil sa paglampas dito.
Katapatan sa durability (naresolba — §16.15 Q6). Ang durability ay hindi umuurong: ang T1 R2 write ay synchronous at write-once, kaya ang isang free-tier na qub na hindi bumili ng T3 ay ganap nang durable sa sandaling bumalik ang seal. Ang lumalabo ay ang provable na upper-bound commitment time: para sa isang free na qub ito ay nagiging ang anchor block time sa halip na isang per-qub na transaction block time. Sa mababang volume — ang makatotohanang early-launch at off-peak na estado — ang buong daily cadence ay ang karaniwang floor, hindi isang bihirang edge. Ang product framing samakatuwid ay isang upper bound na may walang nakatuong numeric latency — "selyado at durable na ngayon; ang isang independyenteng pampublikong timestamp ay idinaragdag sa susunod na log anchor (karaniwang araw-araw)" — at ang exact-hour commitment proof ay isang bayad na T3 property, ipinapaalam sa tier-comparison surface at sa terms (§16.11, §16.15 Q6). Ang anumang time bound ay isang internal SLO lamang, hindi kailanman isang marketed SLA.
16.2 Istruktura ng LogLeaf (dalawang committed na hugis)
Ang isang log entry ay isang LogLeaf, na-encode bilang hand-written canonical CBOR sa ilalim ng §3.1 profile (definite-length, walang tag, walang float, shortest-form integer, NFC text, opsyonal na field na tinatanggal kapag wala, mga key na naka-order ayon sa encoded-byte length na pataas pagkatapos ay bytewise). Ang §3.1 parse → re-encode → compare canonical guard ay inilalapat sa encode path bago mag-hash (hindi lamang sa decode), kaya hindi maaaring hindi magkasundo ang dalawang implementasyon sa leaf bytes sa pamamagitan ng pagkakaiba ng integer-width o key-order. Ang lahat ng integer ay u8 / u64 / i64; ang lahat ng digest ay 32-byte byte string (bstr[32]). Ang isang nakaimbak na Arweave transaction id ay isang raw 32-byte SHA-256 digest na dinadala bilang bstr[32], hindi kailanman isang base64url text string (tumutugma sa §3.3).
Ang leaf ay may dalawang hugis na pinipili ng isang kind byte, dahil sa default na upload path ang Worker ay byte-blind: ang POST /api/v1/upload ay tumatanggap lamang ng qub_id at unlock_at bilang untrusted client assertion — ang body_hash, drand_round, created_at, at drand_chain_version ay lahat nakaselyo sa loob ng §13 outer wrapper, na ang key ay hindi kailanman hawak ng Worker. Tanging ang server-seal path (POST /api/v1/seal) ang nag-de-derive ng body_hash / drand_round mula sa plaintext. Ang isang iisang leaf shape na nagdadala ng body_hash + drand_round ay samakatuwid ay magko-commit ng mga value na hindi kailanman naberipika ng operator para sa mayorya ng mga tunay na qub. Pinananatiling tapat ng split ang bawat committed na value:
| Key | Enc. len | Type | Presence | Kahulugan |
|---|---|---|---|---|
seq |
4 | u64 |
kailangan | Global 0-based leaf index; ang posisyon na kino-commit ng inclusion proof. |
kind |
5 | u8 |
kailangan | 0x01 attested (server-seal) o 0x02 asserted (client-seal / byte-blind upload). |
ref |
4 | bstr[32] |
kailangan | Leaf reference id. Attested → raw qub_id. Asserted → ang blinded id SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
chash |
6 | bstr[32] |
kailangan | Content address SHA3-256(stored_bytes) — ang iisang content tie na laging matapat na makakalkula ng Worker, sa parehong path. |
unlock_at |
10 | i64 |
kailangan | Kinopya (attested) o asserted (asserted); na-validate na > 0 bago ito pumasok sa leaf. |
received_at |
12 | i64 |
kailangan | Worker wall-clock sa R2-ack. Non-evidentiary (operator-asserted; §16.6). Naroroon para sa self-description, hindi kailanman isang proof. Na-validate na > 0. |
body_hash |
10 | bstr[32] |
kind=0x01 lamang |
Tinatanggal sa 0x02 — wala ito sa Worker sa ilalim ng §13. |
drand_round |
12 | u64 |
kind=0x01 lamang |
Tinatanggal sa 0x02. |
Ang isang kind=0x02 na leaf ay sadyang hindi kino-commit ang body_hash o drand_round: pinatutunayan nito ang commitment at ordering ng isang opaque ciphertext sa content-address chash, na nag-aangkin ng qub_id at unlock_at — hindi ang plaintext o round nito. Ang plaintext/round na mga leg para sa isang asserted na qub ay nanggagaling sa umiiral na §11 .qub-bundle verification, hindi mula sa log (§16.11). Ang drand_chain_version ay wala sa leaf (ito ay nasa loob ng wrapper sa default path); ang chain granularity ay nananatili sa anchor (§16.7). Encoder discipline: tanggihan ang isang all-zero na ref o chash, at tanggihan ang non-positive na unlock_at / received_at, na sumasalamin sa outcome_at > 0 sentinel guard sa cbor.rs.
16.2.1 Pag-blind ng pribadong qub
Ang log ay hindi dapat maging ang enumeration oracle na pinipigilan ng §13 outer wrapper (§13.1). Para sa isang pribadong (naka-wrap) na qub, kino-commit ng asserted na leaf ang blinded na identifier SHA3-256(qub_id ‖ log_blind_secret), kung saan ang log_blind_secret ay isang server-held na lihim, at tinatanggal ang body_hash. Hindi maitatali ng isang third party ang ganitong leaf sa isang tiyak na qub_id; ang may-hawak ng qub, na may delivery URL at samakatuwid ay may qub_id, ay maaaring muling kalkulahin ang blind upang kumpirmahin ang sarili nilang inclusion. Ang isang pampublikong qub (na enumerable na, na nagdadala na ng Visibility: public Arweave tag ayon sa §13.8) ay kino-commit ang raw na qub_id. Ito ang iisang lugar kung saan sadyang nagpapaubaya ang standalone verifiability sa isang load-bearing privacy invariant; ang standalone tie para sa mga pribadong qub ay ang chash (§16.9).
Custody ng log_blind_secret (naresolba — §16.15 Q4). Pinoprotektahan ng blind ang leaf unlinkability, hindi ang plaintext confidentiality (hawak iyon nang nakapag-iisa ng §13 wrapper). Sa isang log_blind_secret compromise, para sa anumang qub_id na hawak na o muling maibubuo ng adversary (bawat qub na may bundle/URL nito, kasama ang anumang low-entropy o pampublikong qub_id), muli nitong kinakalkula ang leaf ref sa isang hash at iniuugnay ito — ito ay direktang linkage ng isang kilalang populasyon, hindi isang brute-force sa isang hindi kilalang espasyo. Iuri ang log_blind_secret bilang isang correlation/Sybil-grade na lihim sa parehong custody tier ng iba pang server secret, at i-rotate lamang nang pasulong (ang isang rotation ay muling nag-blind ng mga future leaf; hindi nito maaaring retroactively i-unlink ang mga naka-anchor na).
16.3 Leaf at Node Hashing
RFC 6962 §2.1 domain-separated hashing na may SHA-256 na pinalitan ng SHA3-256:
leaf_hash = SHA3-256(0x00 || canonical_cbor(LogLeaf))
node_hash(l,r) = SHA3-256(0x01 || l || r)
empty tree = SHA3-256("") // defined but never anchored
Ang mga domain prefix byte na 0x02 (entry chain, §16.4) at 0x03 (STH hash, §16.6) ay nakareserba at disjoint mula sa mga ito. Ang mga ito ay single byte at samakatuwid ay hindi maaaring mag-collide sa umiiral na 10-byte ASCII domain separators (QUB_ID_V2, atbp.). Ang tree ay ang RFC 6962 left-full unbalanced na tree (bawat interior split sa pinakamalaking power of two na mahigpit na mas mababa sa subtree leaf count), na nagpapahintulot sa inclusion at consistency proofs na magbahagi ng iisang audit-path algorithm. Ang reference spec ay nagdadala ng tahasang left/right derivation pseudocode at nag-pin ng isang non-power-of-two (5-leaf) test vector upang ang right-edge promotion case — na itinatago ng isang 4-leaf vector — ay ma-exercise.
16.4 Hash Chaining (internal)
Ang LogDO ay nagpapanatili ng isang internal entry chain para sa crash-consistency lamang. Ito ay hindi kailanman naipa-publish at hindi kailanman verifier-facing:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
Ang naipa-publish na append-only authority ay ang cumulative Merkle root + ang anchor nito (§16.5–16.6), hindi kailanman ang raw order kung saan nagkataong nagse-serve ang operator ng mga leaf: ang chain ay muling kinakalkula para sa anumang order na ise-serve, kaya tanging ang naka-anchor na root ang nag-pin ng canonical position.
16.5 Cumulative Merkle Tree at Batching
May iisang patuloy na lumalaking RFC 6962 tree sa lahat ng leaf sa seq order — hindi mga hiwalay na per-batch tree. (Isang carry-leaf-chained per-batch construction ang tinanggihan: hindi ito isang tunay na prefix relation, kaya ang "consistency proofs" nito ay hindi maayos.) Ang cumulative tree ay nagbibigay ng tunay na RFC 9162 consistency proofs at nagpapahintulot sa isang iisang kamakailang anchor na patunayan ang inclusion para sa anumang mas lumang qub.
Ang LogDO Durable Object ay ang iisang writer (blockConcurrencyWhile, sumasalamin sa QuotaDO / EntitlementDO) — ang pag-append sa isang shared log ay read-modify-write sa shared state at kaya MUST dumaan sa isang DO, hindi kailanman sa KV. Ina-cache nito ang right-edge frontier ng tree (O(log n) na hash) kaya ang pagsasara ng isang batch ay O(batch). Ang isang batch ay ang set ng mga leaf na naka-anchor nang magkasama; ang mga trigger nito ay configurable, hindi protocol-frozen: isang tree_size advance na hindi bababa sa LOG_BATCH_MAX_LEAVES (default 4096), o age na umaabot sa anchor cadence, o isang forced flush kapag dumapo ang isang bayad na T3 seal. Ang root_i ay ang cumulative Merkle Tree Hash sa mga leaf na 0 .. tree_size_i.
16.6 Signed Tree Head sa pamamagitan ng Arweave Anchor
Ang Arweave anchor transaction ay ang Signed Tree Head at pumapalit sa isang operator signature para sa tree head mismo: ang daily anchor ay hindi nangangailangan ng qub key dahil ang Arweave tx owner ay ang signature. Ang moat thesis ay nananatili — ang immutable substrate, hindi isang qub-held na lihim, ang load-bearing para sa naka-anchor na root.
May eksaktong iisang warm na qub signing key sa disenyo, at ito ay naka-pin: ang per-seal receipt key (§16.10). Ang public key nito ay naka-commit sa LogProfile (ipinapamahagi kasama ang verifier) at cross-signed ng anchor_owner, kaya ang isang verifier ay nag-va-validate ng isang receipt laban sa parehong naka-pin na root ng anchor. Ito ang resolusyon ng §16.15 Q2 — ang isang hindi naka-pin, operator-rotatable na receipt key ay magiging repudiable (maaaring ikaila ng operator na sa kanila ang key), na mag-vo-void sa accountability value ng receipt laban sa operator-level adversary na kung kaya umiiral ang receipt. Kaya: ang qub ay may walang hindi naka-pin na log-signing key; ang receipt key ay naka-pin at anchor_owner-cross-signed.
Ang SignedTreeHead ay canonical CBOR (mga key ayon sa encoded length): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (prior sth_hash; genesis = 32 zero byte), log_id:bstr[32], first_seq:u64, anchored_at:i64. Ang hash nito ay sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
Naka-pin na trust root. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). Ang isang conforming na verifier ay MUST mangailangan ng anchor_tx.owner == LogProfile.anchor_owner, kung saan ang anchor_owner (at ang receipt-key public key) ay naka-bake sa qub_core bilang ang LogProfile — kasama ang quicknet constants na nasa DrandTimelockProvider::quicknet() na — at ipinapamahagi kasama ang verifier binary. Ang verifier ay MUST din i-verify ang Arweave tx data → tx_id binding nang lokal sa halip na magtiwala sa isang gateway /raw/ response. Isinasara nito ang rogue-wallet equivocation hole: ang "naka-anchor sa Arweave" ay walang kahulugan hangga't hindi nag-pin ang verifier kung aling wallet.
Ang rotation ay isang §15 governance extension, hindi isang reuse (naresolba — §16.15 Q3). Ang profile surface ng §15.2 ay kasalukuyang naglilista lamang ng sig_algs / drand chains / wrapper versions / content types, at ang mga trigger ng §15.3 ay walang nililista sa mga ito — ang LogProfile / anchor_owner ay hindi pa nasa surface ng §15. Ang rotation governance samakatuwid ay dapat gawin: ang §15.3 ay pinalawak (sa ibaba) upang idagdag ang LogProfile trigger, at ang isang rotation ay isang signed LogProfile bump na ipinapadala sa isang verifier update. Ang isang planado na rotation ay nagdadala ng outgoing → incoming cross-signature; ang isang compromise-driven na rotation ay hindi makakaya (ang outgoing key ay untrusted/unavailable nang eksaktong iyon) at babagsak sa §15-governed bump, kung saan ang prev-anchor fork check (sa ibaba) ang naglilimita sa pinsala sa interim.
Equivocation window (first-class trust parameter). Ang isang leaf ay equivocation-resistant lamang kapag ang covering anchor nito ay Arweave-confirmed. Ang window ay received_at → anchor confirmation (≤ cadence + Arweave finality). Sa loob nito ang tanging mga garantiya ay ang naka-pin na seal receipt (§16.10) at ang operational integrity ng qub. Tatlong accountability artifact ang gumagawa nitong tapat sa halip na hand-waved (ang witness model ay ang resolusyon ng §16.15 Q2):
- Naka-pin na signed seal receipt — ang SCT analogue na ibinabalik sa upload response (§16.10), nilagdaan ng naka-pin,
anchor_owner-cross-signed na receipt key. Ang isang leaf na ibinaba bago ang anchor nito ay nag-iiwan sa biktima ng isang non-repudiable na receipt na maipa-publish, na nagsasara sa silent-omission hole. - Naipa-publish na monitor methodology + prev-chain walk — ang anchor
prevchain ay nilalakad head→genesis; ang isang fork (dalawang anchor sa isangsizena may magkaibangroot, o isang sirangprev) ay maipa-publish na patunay ng maling pag-asal. Ang equivocation detection ay isang nakasaad na operational commitment, hindi isang tahimik na palagay. - Dual self-published heads — bawat bagong head
{sth_hash, tree_size}ay ipina-post sa isang dedikadong qub-owned public, append-only GitHub repository (ang load-bearing tamper-evident self-publication leg), na may social post bilang best-effort corroboration lamang. Ang isang nabigong posting ay MUST mag-page (hindi tahimik na mabigo).
Honesty bound (nakatatali na constraint). Dahil kinokontrol ng qub ang parehong posting surface, ito ay self-published, hindi independyenteng nasaksihan. Walang product, marketing, o legal surface na maaaring mag-angkin na ang log ay "independyenteng nasaksihan"; ang pinapahintulutang angkin ay na ang equivocation ay natutukoy at nag-iiwan ng isang non-repudiable na receipt. Ang isang tunay na independyenteng third-party witness ay ipinagpapaliban sa isang hinaharap na §15 governance bump.
Ang received_at ay operator-asserted at walang angkin na maaaring sumandal dito — hindi ito kailanman inilalantad bilang proof o bilang dispute corroboration sa anumang product / legal / API / proof-rendering surface. Ang Arweave anchor block time na T ay ang tanging trustless na timestamp (isang upper bound sa "logged by"). Ang anumang monitor sanity check sa received_at ay MUST ihambing laban sa T, hindi laban sa operator-controlled anchored_at STH field; ang ganitong check ay isang guard laban sa clock bug ng isang tapat na operator lamang, hindi isang accountability control laban sa isang malisyosong operator (§16.15 Q5).
16.7 Format at Cadence ng Anchor Transaction
Ang AnchorBundle ay ang canonical-CBOR na Arweave transaction body, na isinusulat sa pamamagitan ng §16.8 bundler: ver:u8, sth:bstr (canonical SignedTreeHead bytes), prev_anchor:bstr (prior anchor tx id raw bytes; tinatanggal sa genesis), chain_hash:tstr (ang drand chain na umiiral — quicknet), at ang leaf-CBOR stream ng batch sa seq order kaya ang anchor ay self-contained: muling de-derive ng isang monitor ang root mula sa body nang walang qub dependency. (Kung ang leaf stream ay lumaki sa mataas na volume, ang isang hinaharap na rebisyon ay maaaring mag-commit lamang ng isang leaf range sa pamamagitan ng reference; nabanggit, hindi inampon sa v1.)
Ang mga Arweave tag ay sadyang enumerable — ang log ay nilayon na matuklasan, hindi tulad ng mga pribadong qub: App-Name: qub-tlog, Anchor-Format: 1, Log-Id: <hex>, Batch: <n>, Tree-Size: <n>, Root: <hex>, Prev-Anchor: <tx>, Content-Type: application/cbor. Ang mga tag ay untrusted hint; ang CBOR body ang tanging awtoridad.
Cadence: araw-araw bilang default, muling sinusuri ayon sa volume (ang size trigger ay awtomatikong pinaiikli ang epektibong cadence sa ilalim ng load); ang isang bayad na T3 seal ay pinipilit ang isang anchor kaya ang mga bayad na customer ay hindi kailanman naghihintay ng isang araw. Ang anchor wallet ay dedikado at low-velocity, hiwalay sa upload wallet — ito ay MUST sariling JWK (isang natatanging key, hindi isang logical role sa upload wallet) kaya ang isang upload-wallet compromise ay hindi makapag-forge ng mga anchor — na may hard per-day anchor-transaction budget (ang ibinabang ARWEAVE_DAILY_CEILING). Ang custody posture ay malinaw na nakasaad: isang narrow-scope na hot key na may mahigpit na circuit breaker at mababang balance, hindi "cold" — ang isang wallet na awtomatikong nagla-sign araw-araw ay hindi maaaring cold, at hindi ito ipinagkukunwari ng spec.
16.8 ANS-104 Bundler
Isang in-house na ANS-104 DataItem encoder at deep-hash signer, humigit-kumulang 300 linya, Web Crypto lamang, zero npm dependencies (parehong Turbo SDK ay nabibigo sa npm ci --ignore-scripts supply-chain gate). DataItem byte layout:
signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data
Ang signing ay Arweave deepHash — isang recursive na SHA-384 digest (Arweave's wire requirement, crypto.subtle.digest("SHA-384")) sa ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — pagkatapos ay RSA-PSS sa deep hash gamit ang wallet JWK sa pamamagitan ng crypto.subtle; id = base64url(SHA-256(signature)). Ang SHA-384 dito ay naka-quarantine bilang isang Arweave-wire-only primitive, hindi kailanman isang qub trust primitive (itinatala ng §15 ang fence; ang qub trust hashing ay SHA3-256 sa buong saklaw).
Isang code path ang naglilingkod sa tatlong consumer: bayad na T3 per-qub permanence, ang Arweave-unavailability fallback (i-queue ang DataItem, ibalik ang R2-first ack anuman — isinasara nito ang kasalukuyang ARWEAVE_UNAVAILABLE 503 dead-end), at ang pagsusulat ng AnchorBundle. Signature scheme (naresolba — §16.15 Q8): nagla-sign ang v1 gamit ang RSA-PSS (signature type 1) na muling ginagamit ang umiiral na Arweave wallet JWK mechanism (zero bagong long-lived key custody, naglilingkod sa "isang mas kaunting lihim" thesis); ang Ed25519 ay ipinagpapaliban sa §15 PQ-migration path.
Ang hand-rolled na deep hash ay ang pinakamataas-na-panganib, pinakamababang-natural-coverage na code sa W5, kaya ang gating nito ay hindi-mapag-uusapan (§16.15 Q8):
- Ang cross-language fixture na
tlog_v1.json(Rust + TS, ang §14.5wrapper_v1.jsonpattern) ay sumasaklaw sa deep-hash, DataItem bytes + id, leaf hashes, isang 5-leaf root + audit path, isang STH hash, isang inclusion proof, at isang consistency proof — sa parehong sign at verify na direksyon (ang verify na direksyon ay mahalaga dahil hinihila ng §16.6's local tx → tx_id check ang deep hash sa bawat standalone verifier, hindi lamang ang writer). - Isang one-time interop round-trip sa pamamagitan ng isang reference ANS-104 bundler, kinokonsumo bilang static test data lamang — hindi kailanman isang npm runtime dependency (ang Web-Crypto-only / no-install-scripts posture ay nananatili).
- Ang deep-hash + RSA-PSS path ay dapat mag-round-trip sa pamamagitan ng parehong
crypto.subtleprimitives na ginagamit ng production, kaya ang in-house encoder ay byte-compatible. - Isang patuloy na post-bundle acceptance monitor ang nagkukumpirma na ang bawat anchor / fallback DataItem ay tunay na nakakamit ng Arweave acceptance, na may alarm + circuit breaker — dahil ang deep hash ay naglilingkod din sa Arweave-unavailability fallback queue, kaya ang isang tahimik na regression ay pupunuin ang queue na iyon ng mga network-rejected item sa eksaktong outage na kung kaya umiiral ito.
16.9 Inclusion at Consistency Proofs
Pareho ay RFC 9162, SHA3-256, ini-serve bilang canonical CBOR.
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (ang eksaktong leaf CBOR — muling kinakalkula mismo ng verifier ang leaf_hash at hindi kailanman nagtitiwala sa isang supplied hash), index:u64, size:u64, audit:[bstr[32]], root:bstr[32], anchor:{ txid:bstr[32], batch:u64, sth:bstr[32], log_id:bstr[32], block_height?:u64, anchored_at?:i64 }.
ConsistencyProof — GET /api/v1/log/consistency?first=<size_a>&second=<size_b>: ver:u8, first_size:u64, second_size:u64, first_root:bstr[32], second_root:bstr[32], nodes:[bstr[32]], first_anchor, second_anchor. Isang iisang hindi malabo na key list, naka-pin ng test vector.
Standalone verification (walang qub server, pinalalawak ang §11):
1. Parse .qub bundle → SealedQub; recompute qub_id (§4.1).
2. Read leaf.kind.
3a. kind=0x01 (attested):
assert leaf.ref == qub_id
assert leaf.body_hash == SHA3-256(body)
assert leaf.drand_round == unlock_round(unlock_at)
3b. kind=0x02 (asserted):
assert leaf.ref == SHA3-256(qub_id || blind) // holder supplies blind
OR treat ref as opaque and bind via leaf.chash == SHA3-256(stored_bytes)
4. Recompute leaf_hash = SHA3-256(0x00 || leaf); fold `audit` per RFC 6962
using index/size; require derived root == proof.root.
5. Fetch anchor.txid from any gateway; verify the tx data → tx_id binding
(do not trust a gateway /raw/ response); REQUIRE anchor_tx.owner ==
LogProfile.anchor_owner.
6. Parse AnchorBundle; require committed root == proof.root and size ==
proof.size; read the Arweave block time T.
7. Emit the claim scoped by leaf.kind (§16.11).
Ang proof-serving storage ay MUST coordinate-keyed (naresolba — §16.15 Q7, blocking precondition). Ang cold-leaf proof generation ay correctness-neutral lamang kung ang R2 audit material ay isang persistent Merkle-node store na naka-key sa absolute tree coordinate (level, index) — hindi per-batch node delta. Sa isang coordinate-keyed na store, ang anumang (leaf i, size N) audit path ay isang set ng O(log N) direktang R2 GET na walang recomputation sa mga batch boundary; sa isang batch-keyed na store ay hindi, na siyang storage-layout gap na isinasara ng resolusyon na ito. Ang mga leaf body ay gayundin content-addressable ayon sa seq. Ang isang W5 test vector ay MUST patunayan ang isang genesis-era cold leaf laban sa isang mas-bandang root gamit lamang ang R2 + Arweave na may LogDO storage na nabura, kaya ang reclamation-safety claim sa §16.13 ay nasusuportahan sa halip na iginiit. Ang O(log N) sequential R2 GET ay nararapat lamang sa asynchronous proof endpoint — hindi kailanman sa seal hot path (§16.10) o isang per-tick cron.
16.10 R2-First Ack Ordering
Ang POST /api/v1/upload sequence ay nagiging:
- Front-half gates (auth, validation, idempotency shard key) — hindi nagbabago.
- Synchronously
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)— ang durability floor; isinasara din ang W1's precache race (dati ay isangctx.waitUntilpagkatapos ng Arweave submit). - Synchronously
await LogDO.append(leaf)— isang in-colo DO RPC; ang iisang writer ang nag-aassign ngseq, nag-extend ng entry chain, at nag-uupdate ng frontier. (RMW sa shared state → DO, hindi kailanman KV.) AngappendRPC ay gumagawa lamang niyan — angO(batch)Merkle batch-close na trabaho ay tumatakbo labas ng RPC na ito sa LogDO alarm, o ang append p95 ay sumisirit sa bawatLOG_BATCH_MAX_LEAVES-th na seal. - Ibalik ang ack ngayon — kasama ang seal receipt (nilagdaan ng naka-pin na receipt key, §16.6) at
{ tx_id, log_seq, anchor_status: "pending" }. Ang multi-second na Arweave fan-out ay inalis mula sa critical path. - Isang
ctx.waitUntilang nag-e-enqueue ng deferred na trabaho: ang per-qub na Arweave submit (ngayon ay best-effort / bayad; sa kabiguan ito ay ruruta sa bundler fallback queue sa halip na mag-503 sa gumagamit) kasama ang umiiral na provisional-meta writes. Ang batch close at anchoring ay tumatakbo nang nakapag-iisa mula sa LogDO alarm at sa daily anchor cron. Walangctx.waitUntilsa loob ng isang loop; ang umiiral na idempotency shard key ay napapanatili.
Latency budget (naresolba — §16.15 Q7). Ang < 300 ms p95 target ay isang measured launch gate, hindi isang palagay. Ang tapat na critical path ay ang front-half KV reads + isang R2 PUT + dalawang serialized Durable Object — ang umiiral na QuotaDO seal-quota debit at ang bagong LogDO append — kaya ang budget ay dapat isaalang-alang ang dalawang in-colo DO round-trip, hindi isa. Magpadala ng isang LogDO latency alarm na sumasalamin sa QuotaDO's at ituring ang isang p95 regression bilang isang release blocker.
16.11 Trust Model — ang tumpak na angkin, naka-scope ayon sa leaf kind
Para sa kind=0x01 (attested): "Ang content na ito — body na tumutugma sa body_hash, kinikilala ng qub_id — ay naka-commit sa append-only log ng qub sa posisyong seq at umiral nang hindi lalampas sa Arweave block time na T; ito ay cryptographically hindi mababasa hanggang drand round R = unlock_round(unlock_at)." Ito ang buong {tlock round binding + Merkle inclusion + anchored root} triple.
Para sa kind=0x02 (asserted, ang default): "Isang opaque ciphertext na may content-address chash, na nag-aangkin ng qub_id at unlock_at, ay naka-commit sa append-only log sa posisyong seq at umiral nang hindi lalampas sa Arweave block time na T." Ang round at body na mga leg ay ibinibigay ng umiiral na §11 .qub-bundle verification (qub_core::unlock), hindi ng log; ang idinadagdag ng log sa isang hubad na per-qub transaction ay tamper-evident ordering, isang trustless na upper-bound commitment time, at equivocation resistance.
Pareho ang mga angkin ay hindi kasama, ayon sa §11: authorship nang walang sig_alg ≥ 0x01, intent, at sub-anchor-granularity timing. Wala sa kanila ang nagpapahintulot sa anumang angkin na sumandal sa received_at.
Claim ceiling (nakatatali na launch constraint — naresolba §16.15 Q1). Para sa free / default (kind=0x02) na qub, ang naka-scope na kind=0x02 na angkin sa itaas ay ang ceiling sa kung ano ang maaaring igiit ng anumang product, marketing, terms, o proof-rendering surface. Walang surface na maaaring magsabi o magpahiwatig na ang log ay nagpapatunay sa content o sa unlock round ng isang default na qub — pinatutunayan ng log ang ordering + isang trustless na upper-bound commitment time ng isang opaque ciphertext. Ang content at round proof ay nanggagaling lamang sa umiiral na §11 .qub-bundle verification, na log-independent. Ito ay isang hard launch blocker sa copy, hindi isang stylistic preference; ito ang resolusyon na pinananatiling tapat ang byte-blind default path.
16.12 Versioning at W3 Coordination
Walang SealedQub wire bump at samakatuwid ay walang protocol-version bump (§12.1): ang log ay isang sidecar na nagko-commit sa umiiral na mga field at byte, kaya hindi ito pumapasok sa §12.2 protocol-version history. Ang W3's optional drand_chain_version ay hindi nagagalaw at nananatiling ang tanging optional na SealedQub field. Sa halip, ang log ay nagpapakilala ng sarili nitong mga independyenteng version space — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — na sumasalamin sa §12.4 wrapper-version independence (ang wrapper ay nagdadala ng version byte na independyente sa protocol version, at ang log versions ay sumusunod sa parehong separation).
Ang proof delivery ay fetched bilang default, na may opsyonal na ride-along. Ang isang proof ay hindi maaaring umiral sa seal time (ang anchor ay hindi pa naisusulat), kaya ang seal-time .qub bundle ay nananatiling proof-free. Ang W7's verifier ay nag-fe-fetch ng GET …/proof nang isang beses, o sa fully-offline mode ay muling binubuo ang proof mula sa pampublikong AnchorBundle sa pamamagitan ng isang Arweave query sa Log-Id. Ang .qub bundle (W7) ay nagrereserba ng isang opsyonal na inclusion_proof member — wala sa seal, pinupunan ng isang post-anchor re-export para sa cold archival — sumusunod sa parehong "opsyonal, tinatanggal bilang default, additive" pattern ng W3's drand_chain_version.
16.13 Retention
Ang mga retention window para sa LogDO open tail, ang R2 proof-serving substrate, ang anchor circuit-breaker counters, at ang bundler fallback queue ay tinukoy sa docs/DATA-RETENTION.md. Prinsipyo: ang hot per-entry storage ng log (LogDO) ay reclaimable post-anchor; ang audit material nito — ang coordinate-keyed (level, index) Merkle-node store + ang seq-addressed na leaf bodies (§16.9) + ang mga Arweave anchor — ay permanente. Ang pag-reclaim ng isang cold leaf mula sa DO ay hindi kailanman nag-iinvalidate ng isang naibigay na proof, dahil ang isang proof ay nireresolba laban sa permanenteng R2 node store na iyon at sa Arweave anchor, hindi sa DO (at pinatutunayan ito ng §16.9 wiped-DO test vector).
16.14 Mga Test Vector
Ang W5 ay nagpapadala ng cross-language fixture na tlog_v1.json (§16.8) kasama ang mga worked vector: isang kind=0x01 at isang kind=0x02 leaf → leaf_hash; ang 5-leaf cumulative root; isang inclusion proof; isang consistency proof; isang AnchorBundle; at isang DataItem id. Ang mga ito ay nananahan kasama ng §14.5 outer-wrapper vectors at ina-exercise ng parehong Rust (qub-core) at TypeScript (Worker) na implementasyon.
16.15 Mga Desisyon ng Review (W5 — naresolba)
Ang W5 external review (isang adversarial design pass + owner sign-off) ay kumpleto na. Bawat desisyon sa ibaba ay settled at naipapakita sa §16 text sa itaas; ang mga nakatatali na launch constraint ay muling inilahad sa dulo. Maaaring magpatuloy ang implementasyon sa ilalim ng mga ito.
- Default-path (
kind=0x02) leaf honesty — RESOLVED. Ipadala ang two-leaf-kind split ayon sa tinukoy: angkind=0x02ay hindi kino-commit angbody_hashodrand_round. Walang*_body_hashfield sa byte-blind path (ito ang magiging pinaka-legible na huwad na "verified" signal para sa mga integrator at isang convenience na ibinibigay na ng §11 mula sa bundle). Huwag mangailangan ng server-seal para sa mga log-attested na qub (pipilitin niyan ang plaintext sa Worker at sisira sa crypto-shredding moat). Ang anumang self-describing short-circuit ay nararapat sa.qubbundle / proof envelope bilang isang verifier-recomputed na field, hindi kailanman isang leaf field. Owner-confirmed na claim ceiling: §16.11. - Equivocation / omission accountability — RESOLVED. Ang seal-receipt key ay naka-pin sa
LogProfile+anchor_owner-cross-signed (isinasara ang naunang "walang signing key" na kontradiksyon; §16.6). Launch witness model: naka-pin na receipt + monitor methodology + prev-chain walk + dual self-published heads (qub-owned public GitHub repo, social best-effort), inilalako bilang detectable + receipted, hindi kailanman independyenteng nasaksihan. Ang isang tunay na third-party witness ay ipinagpapaliban sa isang §15 governance bump. - Naka-pin na anchor-owner trust root + rotation — RESOLVED. Ampunin ang
LogProfilepin (§16.6); sinusuri ng verifier anganchor_tx.owner == anchor_ownerat vine-verify ang tx data → tx_id binding nang lokal. Ang rotation governance ay isang §15 extension na gagawin (§15.3 trigger na idinagdag), hindi isang reuse; ang mga planadong rotation ay nag-cross-sign, ang mga compromise-driven na rotation ay babagsak sa §15 bump kasama ang fork check na naglilimita sa pinsala. - Pag-blind ng leaf ng pribadong qub — RESOLVED. Panatilihin ang pag-blind para sa mga pribadong qub (
ref = SHA3-256(qub_id ‖ log_blind_secret)), rawqub_idpara sa mga pampublikong qub (nasa §16.2.1 na), angchashbilang ang standalone tie. Anglog_blind_secretay isang correlation/Sybil-grade na lihim, rotate-forward lamang (§16.2.1). received_at— RESOLVED. Panatilihin ito sa leaf, naka-commit ngunit tahasang non-evidentiary; hindi kailanman inilalantad bilang proof o dispute corroboration sa anumang surface. Ang anumang monitor sanity check ay naghahambing laban sa Arweave block time naT, hindi laban sa operator-controlledanchored_at(§16.6).- Free-tier provable-timing — RESOLVED (owner sign-off). Ang durability ay hindi umuurong; tanging ang provable na upper-bound commitment time ang lumalabo sa anchor block time. Ang free-tier copy ay walang ginagamit na numeric SLA ("…idinagdag sa susunod na log anchor, karaniwang araw-araw"); ang exact-hour proof ay isang bayad na T3 property, ipinapaalam sa tier-comparison surface + sa terms (§16.1).
- Cumulative tree sa Workers — RESOLVED. Iisang cumulative RFC 9162 tree + frontier-cached na single-writer LogDO (kumportableng headroom laban sa ~1k writes/sec DO ceiling; ipagpaliban ang Merkle-of-shard-roots sharding hanggang malapit dito). Blocking precondition: coordinate-keyed
(level, index)R2 node store + ang wiped-DO cold-leaf test vector (§16.9); ang< 300 msay isang measured launch gate sa dalawang serialized DO (§16.10). - ANS-104 signature scheme + deep-hash — RESOLVED. RSA-PSS (sig type 1, muling ginagamit ang dedikadong anchor-wallet JWK); ang Ed25519 ay ipinagpaliban sa §15 PQ path. Ang hand-rolled na SHA-384 deep hash ay naka-gate sa both-directions cross-impl fixture, isang static-only reference-bundler interop check, ang shared-
crypto.subtleround-trip, at ang post-bundle Arweave-acceptance monitor (§16.8).
Mga nakatatali na launch constraint (dalhin sa implementasyon + product/legal review):
- Claim ceiling (Q1/Q6). Walang surface na maaaring magsabi na pinatutunayan ng log ang content o unlock round ng isang default na qub; ang pinapahintulutang angkin ay naka-order, tamper-evidently, na may isang trustless na upper-bound commitment time. Ang free-tier timestamp copy ay walang dalang numeric latency; ang exact-hour proof ay bayad na T3 lamang.
- Witness honesty (Q2). Ilako ang equivocation bilang detectable + receipted, hindi kailanman independyenteng nasaksihan.
- Receipt + anchor keys (Q2/Q8). Ang receipt key ay naka-pin + cross-signed; ang anchor wallet ay sarili nitong JWK na natatangi sa upload wallet.
- Deep-hash gate (Q8). Walang anchor o T3 tx na ipinapadala hangga't hindi pumapasa ang both-directions fixture + interop check; ang acceptance monitor ay nag-pe-page sa kabiguan.
- Storage precondition (Q7). Ang coordinate-keyed na node store + wiped-DO cold-leaf vector ay mga kinakailangan para sa "ang reclamation ay hindi kailanman nag-iinvalidate ng isang proof" na garantiya.