Đặc tả giao thức qub

qub là một giao thức cho các cam kết thời gian bằng mật mã: một hệ thống để niêm phong các từ ngữ đến một ngày trong tương lai và chứng minh, khi ngày đó đến, chính xác những gì đã được nói và khi nào.

Ba nguyên thủy làm cho nó hoạt động. drand là một beacon ngẫu nhiên phi tập trung — ngày công bố được thực thi bởi vật lý, không phải bởi thiện chí của bất kỳ bên nào. Nơi lưu trữ công khai vĩnh viễn là một kho công khai chống can thiệp — không bên nào có thể chỉnh sửa hoặc xóa một qub sau khi nó đã được niêm phong. ML-DSA-65 là một chữ ký số hậu lượng tử — mỗi qub được gắn với một cặp khóa mà bí mật của nó không bao giờ rời khỏi thiết bị của tác giả.

Cùng nhau, các nguyên thủy này tạo ra một tuyên bố bị khóa thời gian, chống giả mạo và có thể quy gán — một biên nhận có giá trị tăng lên khi khả năng giả mạo quá khứ của thế giới được cải thiện.

Phần còn lại của tài liệu này là đặc tả quy phạm cần thiết cho các triển khai có thể tương tác.


Đặc tả giao thức qub

Trường Giá trị
Phiên bản 1.0 (phiên bản giao thức 0x01, phiên bản bao bọc bên ngoài 0x01)
Ngày 2026-05-01
Trạng thái Bản thảo
Đã rà soát đến 2026-05-01

Tài liệu này là đặc tả giao thức quy phạm cho hệ thống cam kết theo thời gian qub. Nó định nghĩa các cấu trúc dữ liệu, các quy tắc tuần tự hóa, các công thức suy dẫn và các quy trình xác minh cần thiết cho các triển khai có thể tương tác.

Phạm vi: lớp giao thức được cố ý làm trung lập về ngôn ngữ — phần thân của qub là các byte văn bản thuần / markdown / giao ước không rõ ràng, và việc hiển thị có nhận biết ngôn ngữ là trách nhiệm của người xem (ứng dụng web qub.social, iframe <qub-embed>, các trình khách MCP, v.v.).


1. Ký hiệu và quy ước

Ký hiệu Ý nghĩa
u8, u64, i64 Số nguyên không dấu/có dấu với độ rộng bit được chỉ định
[u8; N] Mảng byte có độ dài cố định N byte
Vec<u8> Mảng byte có độ dài thay đổi
Option<T> Giá trị kiểu T, hoặc vắng mặt
String Chuỗi văn bản UTF-8, đã chuẩn hóa NFC
`
SHA3-256(x) Hàm băm NIST SHA3-256 của chuỗi byte x (FIPS 202)
ceil(x) Hàm trần: số nguyên nhỏ nhất ≥ x
CBOR Biểu diễn đối tượng nhị phân súc tích (RFC 8949)
big-endian Byte có ý nghĩa nhất đứng trước

Tất cả các số nguyên trong các cấu trúc tiền ảnh được mã hóa thành mảng byte có độ rộng cố định big-endian (i64 → 8 byte, u8 → 1 byte) trừ khi có quy định khác.

Tất cả các dấu thời gian là giây Unix theo UTC.


2. Cấu trúc dữ liệu

2.1 ComposeQub (trạng thái trong bộ nhớ của người tạo)

Không tuần tự hóa thành CBOR. Không ghi vào nơi lưu trữ vĩnh viễn. Cục bộ trong ứng dụng của người tạo.

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 (tải trọng đã giải mã)

Tuần tự hóa bằng CBOR chuẩn (§3). Được mã hóa bên trong SealedQub. Đây là cấu trúc chứng minh tính toàn vẹn của nội dung sau khi giải mã.

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

Cơ sở (qub văn bản không ký): version = 0x01, content_type = 0x01, sig_alg = 0x00, tất cả các trường Option vắng mặt.

Các cấu hình v1 khác: content_type = 0x03 (thân giao ước, xem §6.1); sig_alg = 0x01 (ML-DSA-65) với author_signatureauthor_pubkey hiện diện (xem §9.3); cosigner_pubkeycosigner_signature hiện diện cùng nhau cho các giao ước đồng ký (xem §9.7); reply_to được đặt thành qub_id của qub cha cho các qub chuỗi trả lời (xem §9.3 để biết các hệ quả về phạm vi chữ ký).

2.3 SealedQub (định dạng wire chuẩn)

Tuần tự hóa bằng CBOR chuẩn (§3). Được ghi vào nơi lưu trữ vĩnh viễn. Đây là tạo phẩm trên chuỗi.

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

2.4 RevealedQub (trạng thái ứng dụng của người xem)

Không tuần tự hóa thành CBOR. Cục bộ trong ứng dụng của người xem. Được xây dựng sau khi giải mã và xác minh thành công.

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 — chuyển tiếp từ QubEnvelope.outcome_at / SealedQub.outcome_at; điều khiển khối theo dõi phán quyết trên trang công bố (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. Hồ sơ CBOR chuẩn

Tất cả việc tuần tự hóa SealedQubQubEnvelope PHẢI tuân thủ hồ sơ này. Hai triển khai khi nhận cùng một cấu trúc logic PHẢI tạo ra các byte giống hệt nhau.

3.1 Quy tắc mã hóa

Quy tắc Đặc tả
Chuẩn RFC 8949 §4.2.1 (Yêu cầu mã hóa xác định cốt lõi)
Thứ tự khóa của map Sắp xếp theo độ dài byte đã mã hóa trước (ngắn hơn trước dài hơn), sau đó theo từ điển (từng byte đối với các mã hóa cùng độ dài)
Mã hóa số nguyên Dạng ngắn nhất: 0–23 trong byte đầu; 24–255 trong 2 byte; 256–65535 trong 3 byte; v.v.
Mã hóa độ dài Chỉ độ dài xác định. Không có mảng, map, chuỗi byte hoặc chuỗi văn bản độ dài không xác định (thông tin bổ sung = 31 bị cấm).
Tags Không có tags CBOR (kiểu chính 6 bị cấm).
Dấu phẩy động Không có float (kiểu chính 7 giá trị 0xF9–0xFB bị cấm).
Chuỗi văn bản Mã hóa UTF-8, đã chuẩn hóa NFC (Unicode Normalization Form C).
Chuỗi byte Byte thô. Không có mã hóa base64 ở lớp CBOR.
Khóa trùng lặp Từ chối kèm lỗi. Các bộ phân tích cú pháp KHÔNG ĐƯỢC chấp nhận thầm lặng các khóa map trùng lặp.
Giá trị đơn Chỉ true (0xF5), false (0xF4) và null (0xF6) được cho phép.
Trường tùy chọn Các trường tùy chọn vắng mặt bị bỏ qua hoàn toàn khỏi map CBOR (không được mã hóa thành null). Các trường tùy chọn hiện diện được bao gồm theo thứ tự khóa đã sắp xếp.

3.2 Thứ tự khóa chuẩn đã xác minh

Các thứ tự khóa này là quy phạm. Các triển khai PHẢI phát các khóa chính xác theo thứ tự này. Các khẳng định debug NÊN xác minh thứ tự trong các bản dựng không phải release.

QubEnvelope (phiên bản 0x01, không ký, tất cả các trường tùy chọn vắng mặt):

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

Suy dẫn thứ tự khóa QubEnvelope: mỗi khóa là một chuỗi văn bản CBOR. Độ dài đã mã hóa = 1 byte header + độ dài chuỗi (cho các chuỗi dưới 24 byte). Sắp xếp theo tổng độ dài đã mã hóa trước, sau đó theo từ điển cho các khóa cùng độ dài.

SealedQub (phiên bản 0x01, công khai, không người nhận):

"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 (thân giao ước, 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 (hàng của mảng terms):

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

PartyIdentifier (map party_a / party_b):

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

3.3 Tham chiếu mã hóa byte

Kiểu Mã hóa CBOR Ví dụ
Hàm băm SHA3-256 (32 byte) 0x58 0x20 + 32 byte body_hash, qub_id
Dấu thời gian (i64) Kiểu chính 0 (dương) hoặc 1 (âm), mã hóa ngắn nhất giây Unix
Phiên bản (u8, giá trị 1) 0x01 (một byte)
Loại nội dung (u8, giá trị 1) 0x01 (một byte)
sig_alg (u8, giá trị 0) 0x00 (một byte)
Chữ ký ML-DSA-65 (3.309 byte) 0x59 0x0C 0xED + 3.309 byte author_signature, cosigner_signature
Khóa công khai ML-DSA-65 (1.952 byte) 0x59 0x07 0xA0 + 1.952 byte author_pubkey, cosigner_pubkey

4. Các suy dẫn quy phạm

4.1 qub_id

qub_id xác định duy nhất một qub và liên kết QubEnvelope với SealedQub. Nó được suy dẫn một cách xác định từ nội dung của envelope.

qub_id = SHA3-256(
    "QUB_ID_V2"          ||  // domain separator: ASCII bytes [0x51 0x55 0x42 0x5F 0x49 0x44 0x5F 0x56 0x32] (9 bytes) + 0x00 padding (1 byte) = 10 bytes
    version              ||  // u8 (1 byte)
    content_type         ||  // u8 (1 byte)
    created_at           ||  // i64 big-endian (8 bytes)
    unlock_at            ||  // i64 big-endian (8 bytes)
    outcome_at_or_zero   ||  // i64 big-endian (8 bytes; 0 when outcome_at is absent)
    drand_round          ||  // u64 big-endian (8 bytes)
    body_hash            ||  // [u8; 32] (32 bytes)
    title_hash               // [u8; 32] (32 bytes; absent-sentinel = [0u8; 32])
)
// Total preimage: 108 bytes → 32-byte output

Mã hóa dấu phân cách miền: Chuỗi "QUB_ID_V2" là 9 byte ASCII. Một byte đệm 0x00 đơn được nối thêm để đạt 10 byte cho việc căn chỉnh. Các triển khai PHẢI sử dụng chính xác 10 byte này: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].

Mã hóa outcome_at: V1.1 đã mở rộng tiền ảnh từ 92 lên 100 byte để gấp trường outcome_at tùy chọn vào liên kết. outcome_at vắng mặt được mã hóa thành 8 byte zero; các bộ xác thực giao thức từ chối outcome_at <= 0 ở mọi nơi nên trị canh gác này không thể trùng với một giá trị hợp lệ. Xem §3.2 (định dạng wire) và tài liệu trong cây nguồn tasks/verdict-uplift-plan.md để biết cơ chế verdict thúc đẩy trường này.

Mã hóa drand_round: V1.2 đã mở rộng tiền ảnh từ 100 lên 108 byte để gấp drand_round (vòng drand mục tiêu, §4.3) vào liên kết, và nâng dấu phân cách miền lên QUB_ID_V2. Điều này liên kết vòng khóa thời gian vào danh tính qub: một cổng không thể tái liên kết bản mã với một vòng khác (ví dụ một vòng đã qua) so với vòng mà unlock_at được hiển thị ngụ ý. Thủ tục mở khóa (§8) còn xác minh thêm rằng vòng được nung vào stanza bản mã tlock khớp với unlock_round(unlock_at), nên thời điểm mở khóa được hiển thị có thể chứng minh được là vòng thực sự kiểm soát việc giải mã.

Tính chất:

4.2 body_hash

body_hash = SHA3-256(body)

Trong đó body là tải trọng nội dung Vec<u8> thô. Đối với các qub văn bản, đây là thân qub được mã hóa UTF-8.

4.2.1 title_hash

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

Trong đó title là tiêu đề văn bản thuần tùy chọn được hiển thị trên đồng hồ đếm ngược của người xem trước khi công bố (xem §3.2). Chuẩn hóa NFC chạy tại thời điểm băm để bản tóm tắt ổn định trên các chuỗi điểm mã tương đương về mặt thị giác. Trị canh gác toàn số 0 được dành riêng cho trường hợp vắng mặt; một chuỗi rỗng bị từ chối ở ranh giới CBOR chuẩn như một mã hóa không chuẩn của "vắng mặt" (mã hóa chuẩn loại bỏ hoàn toàn trường này).

4.3 Ánh xạ vòng mở khóa

drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
Tham số Nguồn Ví dụ
unlock_at Giây Unix UTC do người dùng chọn 1735689600 (2025-01-01 00:00:00 UTC)
chain_genesis_time thông tin chuỗi drand (genesis_time) 1595431050
chain_period_seconds thông tin chuỗi drand (period) 30

Thao tác ceil() chọn vòng drand đầu tiên có thời gian công bố ≥ unlock_at. Điều này đảm bảo qub không trở nên có thể giải mã được trước thời điểm mở khóa đã chọn.

Trường hợp biên: nếu (unlock_at - chain_genesis_time) chia hết chính xác cho chain_period_seconds, kết quả là chính vòng đó — qub mở khóa chính xác tại thời điểm công bố của vòng đó.

Xác thực: unlock_at PHẢI ở trong tương lai tại thời điểm niêm phong. unlock_at KHÔNG ĐƯỢC vượt quá 10 năm kể từ created_at (để hạn chế rủi ro phụ thuộc drand dài hạn; UI NÊN cảnh báo cho các ngày mở khóa vượt quá 2 năm).


5. Newtype định dạng wire

Các newtype định dạng wire cung cấp sự an toàn tại thời điểm biên dịch chống lại việc nhầm lẫn các byte CBOR với JSON, văn bản thuần thô, hoặc các mã hóa byte khác.

Kiểu Chứa Được tạo bởi Được tiêu thụ bởi
SealedQubCbor CBOR chuẩn của SealedQub serialize_sealed_qub() tải lên nơi lưu trữ vĩnh viễn, người xem fetch
QubEnvelopeCbor CBOR chuẩn của QubEnvelope serialize_qub_envelope() đầu vào mã hóa tlock, đầu ra giải mã tlock

5.1 Quy tắc xây dựng

// 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 Xác thực khi xây dựng

from_encoded() NÊN xác thực rằng đầu vào bắt đầu bằng một header map CBOR hợp lệ. Việc xác thực cấu trúc đầy đủ xảy ra tại thời điểm phân tích, không phải thời điểm xây dựng, để tránh phân tích kép.


6. Sổ đăng ký loại nội dung

Giá trị Loại Kích thước thân tối đa Ghi chú
0x00 Dành riêng (không hợp lệ) KHÔNG ĐƯỢC sử dụng
0x01 Văn bản thuần (UTF-8, Markdown hạn chế) 50 KB trả phí / 10 KB miễn phí Xem §10 để biết quy tắc hiển thị. Phân chia miễn phí / trả phí được thực thi bởi dịch vụ tải lên; trần cứng ở lớp giao thức là 50 KB.
0x02 Dành riêng (tương lai) Được cấp phát cho một loại nội dung trong tương lai; không hợp lệ trong v1. Người xem PHẢI từ chối theo quy tắc bên dưới.
0x03 Giao ước (thỏa thuận song phương, thân CBOR) 100 KB Thân là CBOR chuẩn PactTerms (§6.1). Ký bởi người đồng ký theo §9.7.
0x04 Phán quyết (người tạo tự chấm điểm, thân CBOR) 8 KB Thân là CBOR chuẩn VerdictBody (§6.2). Chỉ được phát ra bởi intent phía hệ thống verdict. Quan hệ với qub cha nằm ở tag Arweave Parent-Tx-Id, không nằm trong thân. Xem verdict-uplift-plan §3.4.

Người xem PHẢI từ chối các loại nội dung không xác định kèm theo lỗi rõ ràng cho người dùng. Người xem KHÔNG ĐƯỢC cố gắng hiển thị các loại không xác định dưới dạng văn bản.

6.1 Thân giao ước (content_type = 0x03)

Một thân giao ước là mã hóa CBOR chuẩn của giá trị PactTerms:

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

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

Các thứ tự khóa CBOR chuẩn cho cả ba map được nêu trong §3.2. Tổng CBOR giao ước đã tuần tự hóa KHÔNG ĐƯỢC vượt quá 100 KB (khớp với §6).

Bộ phân biệt schema. Hàng đầu tiên trong terms cho một giao ước structured/v1 PHẢI là { key: "pact_schema", value: "structured/v1" }. Các hàng không có dấu hiệu này là các giao ước "tùy chỉnh" và không nhận được xác thực cấu trúc hoặc hiển thị nhận biết schema.

Các vị trí xác nhận đã đóng băng. Các giao ước structured/v1 mang chính xác bốn hàng xác nhận dưới các khóa này:

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

value cho mỗi cái là một trong tám chuỗi tiếng Anh đã đóng băng được chọn bởi cặp (role, kind), trong đó role ∈ { seller, buyer, provider, client }kind ∈ { standard, capacity }. Bản thân các chuỗi là dữ liệu giao thức quy phạm — các chữ ký ML-DSA-65 của cả hai bên cam kết với các byte chính xác thông qua body_hash. Chúng KHÔNG được bản địa hóa; thân được ký là trung lập về ngôn ngữ. Bất kỳ thay đổi từ ngữ nào đều yêu cầu một phiên bản schema mới (structured/v2).

Tám chuỗi, cách tra cứu của chúng (acknowledgement_for(role, kind)), và lý do căn bản cho mỗi cái được ghim bởi triển khai tham chiếu. Các triển khai tuân thủ PHẢI phát ra các giá trị xác nhận giống hệt byte; các kiểm tra body-hash SHA3-256 cố định bao phủ cả bốn tổ hợp vai trò bắt được mọi sự trôi dạt.

Thứ tự hiển thị của người xem. Các chuỗi xác nhận chứa các cụm từ như "described above", giả định rằng các hàng mô tả / phạm vi hiển thị trước các xác nhận. Người xem PHẢI hiển thị mảng terms theo thứ tự CBOR; sắp xếp lại sẽ phá vỡ ngữ nghĩa văn bản.

Liên hệ của bên đối tác. Khi contact của Bên B là một địa chỉ email hợp lệ, dịch vụ tải lên qub tự động gửi một email mời xem xét / đồng ký tại thời điểm staging và ràng buộc đồng ký cuối cùng với việc xác minh cùng một địa chỉ đó (§9.7). Các giao ước mà liên hệ Bên B vắng mặt vẫn có thể được đồng ký, nhưng chỉ qua một kênh ngoài băng — dịch vụ từ chối các yêu cầu đồng ký không thể tạo ra một dấu hiệu xác minh email 15 phút khớp.

6.2 Thân phán quyết (content_type = 0x04)

Một thân phán quyết là mã hóa CBOR chuẩn của giá trị VerdictBody:

VerdictBody {
    verdict_version: u8,                  // 0x01 for structured/v1
    outcome:         u8,                  // 1=Right · 2=Partial · 3=Wrong · 4=Unfalsifiable
    reflection:      Option<String>,      // ≤ 2,000 bytes NFC; "what changed, what did you learn"
    evidence_url:    Option<String>,      // ≤ 2,048 bytes; HTTPS only; absent key when omitted
}

Thứ tự khóa CBOR chuẩn:

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

Tổng CBOR phán quyết đã tuần tự hóa KHÔNG ĐƯỢC vượt quá 8 KB (khớp với hàng đăng ký bên trên).

Enum kết quả. Byte trên wire là trung lập về intent; bốn nhóm Right / Partial / Wrong / Unfalsifiable bao phủ không gian kết quả của mọi intent có phán quyết. Nhãn theo từng intent ("Đoán trúng" / "Đã giữ lời" / "Hoàn thành" / "Đã được xác nhận" cho Right, v.v.) là vấn đề hiển thị phía người xem, được phân giải dựa vào intent của qub cha — wire vẫn trung lập về ngôn ngữ và intent. Các giá trị ngoài khoảng 1..=4 PHẢI bị từ chối khi giải mã.

Liên kết với qub cha. Một qub phán quyết KHÔNG mang tham chiếu tới qub cha trong thân của nó. Mã giao dịch Arweave của qub cha được phát ra dưới dạng tag lưu trữ Parent-Tx-Id tại thời điểm tải lên (§7 lớp tag lưu trữ). Điều này giữ cho thân là một tuyên bố tự đánh giá đã ký, độc lập; chuỗi kiểm toán ("đúng về điều gì?") được thiết lập qua việc tra cứu tag Arweave.

An toàn URL bằng chứng (quy phạm). Khi evidence_url hiện diện, các trình xác thực (phía compose, phía wire, edge Worker) PHẢI thực thi:

  1. Chỉ HTTPS. Chuỗi PHẢI bắt đầu bằng dãy byte https://. Bất kỳ scheme nào khác — http, ftp, javascript, data, file, v.v. — đều bị từ chối.
  2. Giới hạn độ dài. ≤ 2.048 byte (giới hạn thực tế của URL trình duyệt).
  3. Kiểm tra NFC và điểm mã thù địch. Cùng quy tắc như titlereflection — các điểm mã bidi-override / zero-width / tag-block / BOM / C0 / C1 bị từ chối. Định nghĩa khớp với Rust crate::handle::contains_hostile_text_codepoint và TS workers/api/src/utils/unicode.ts::isHostileCodepoint (giữ đồng bộ).
  4. Không khoảng trắng, không ký tự điều khiển ASCII. Khoảng trắng / DEL / byte dưới 0x20 ở bất kỳ vị trí nào trong URL đều bị từ chối — đóng vector tấn công \n/\t mà quy tắc bidi không bao phủ.
  5. Phân đoạn host không rỗng. Mọi thứ giữa https:// và ký tự /, ? hoặc # đầu tiên PHẢI không rỗng.

Không lấy nội dung từ phía máy chủ. Worker KHÔNG ĐƯỢC proxy, fetch, hoặc xem trước URL. Giao thức lưu một chuỗi; việc hiển thị diễn ra phía người xem với rel="nofollow noopener noreferrer" target="_blank" và một host nhìn thấy được kèm theo văn bản liên kết.

Phản tư. Văn bản phản tư tuỳ chọn do người tạo viết ("điều gì đã thay đổi, bạn đã học được gì"). Cùng kiểm tra NFC và điểm mã thù địch như title. Đầu vào rỗng / chỉ có khoảng trắng sẽ gập lại thành vắng mặt tại thời điểm xây dựng.

Phiên bản schema. v1 chỉ hỗ trợ verdict_version = 0x01. Các bản sửa schema trong tương lai sẽ tăng byte này và xuất hiện cùng với một phiên bản giao thức mới theo §12.


7. Giao thức niêm phong

Trình tự niêm phong hoàn chỉnh. Mỗi bước là quy phạm.

 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.

Lớp tag lưu trữ (ngoài băng). Dịch vụ tải lên qub gắn một tập hợp cố ý nhỏ các tag giao dịch lưu trữ cùng với tải trọng đã bao bọc. Content-Type=application/octet-stream được quy định bắt buộc. Dịch vụ tham chiếu bổ sung gắn ba tag tùy chọn khi người tạo chọn hiển thị chúng: Intent (ý định soạn được xác thực theo danh sách cho phép — ví dụ: quote, reply, commitment), Author (vân tay khóa công khai §9.3 của người tạo dưới dạng hex chữ thường 64 ký tự), và Parent-Tx-Id (ID giao dịch lưu trữ của qub cha cho các chuỗi trả lời, 43 ký tự base64url).

Tag Authorchọn tham gia trên mỗi qub: ứng dụng người tạo tham chiếu chỉ gắn nó khi người dùng kích hoạt rõ ràng quy gán công khai tại thời điểm niêm phong. Khi công tắc tắt — mặc định — không có tag Author nào được ghi và qub không được quy gán trên chuỗi: không có gì trong nơi lưu trữ vĩnh viễn liên kết bản tải lên với handle, email, hoặc các qub khác của người tạo. Khi công tắc bật, vân tay Author phân giải đến @handle đã chọn của người tạo thông qua chuỗi chứng thực §9.5. Các mối quan hệ chuỗi trả lời và Intent không có tính nhận dạng. Lớp bao bọc bên ngoài (§13) bảo vệ thân bên trong khỏi sự tương quan của ciphertext — ngăn người thu thập nhận diện và giải mã hàng loạt các bản tải lên hình dạng qub sau khi vòng drand của chúng được công bố.

Dịch vụ tham chiếu cố ý KHÔNG gắn các tag App-Name, App-Version, hoặc Type: bất kỳ bộ lọc giá trị đơn nào như vậy sẽ trả về toàn bộ kho qub cho một truy vấn GraphQL, điều này không nhất quán với phạm vi bảo mật chỉ-thân của lớp bao bọc.

Một trình xác minh tuân thủ KHÔNG ĐƯỢC phụ thuộc vào bất kỳ tag lưu trữ nào cho việc xác minh bên thứ ba §11; body hash / qub_id / chữ ký chỉ cam kết với CBOR bên trong, không bao giờ với tập hợp tag.


8. Giao thức mở khóa

Trình tự mở khóa hoàn chỉnh. Mỗi bước là quy phạm.

 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. Ký quyền tác giả

9.1 Lý do căn bản

Các qub được lưu trữ trong nơi lưu trữ vĩnh viễn. Các chữ ký quyền tác giả phải vẫn không thể giả mạo vô thời hạn, đó là lý do tại sao v1.0 sử dụng sơ đồ hậu lượng tử ML-DSA-65 (FIPS 204) thay vì một sơ đồ cổ điển mà tính bảo mật có thể suy giảm trong vòng đời vĩnh viễn của qub.

9.2 Sổ đăng ký thuật toán

sig_alg Sơ đồ Kích thước khóa Kích thước chữ ký
0x00 Không có chữ ký (không ký)
0x01 ML-DSA-65 (FIPS 204) 1.952 byte 3.309 byte

Người xem PHẢI từ chối các giá trị sig_alg không xác định.

9.3 Xây dựng tiền ảnh đã ký

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

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

signature = Sign(author_secret_key, sig_input)

Dấu phân cách miền: "QUB_AUTHOR_SIG_V1" là 17 byte ASCII: [0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31]. Không có byte đệm.

Byte cuối: byte tiền ảnh thứ 91 PHẢI là 0x00. Triển khai tham chiếu hiển thị điều này dưới dạng hằng số ORG_ID_PRESENT_INDIVIDUAL = 0x00 trong crates/qub-core/src/signing.rs; người xem tái tạo sig_input để xác minh PHẢI phát ra cùng một byte.

Phạm vi chữ ký — những gì được và không được bao phủ. sig_input cam kết với bốn trường của envelope: version, qub_id, body_hash, unlock_at (cộng với dấu phân cách miền cố định và byte org_id_present). Ba trong số bốn đó là các bất biến cấu trúc: qub_id chính nó được suy dẫn từ version, content_type, created_at, unlock_at, outcome_at, drand_round, và body_hash thông qua tiền ảnh §4.1, vì vậy bất kỳ thay đổi nào đối với các trường đó đều tạo ra một qub_id khác và làm mất hiệu lực chữ ký một cách bắc cầu. Do đó bề mặt được xác thực trực tiếp là:

Trường Được xác thực bởi chữ ký Cách thức
version Đầu vào trực tiếp vào sig_input
qub_id Đầu vào trực tiếp
body_hash Đầu vào trực tiếp
unlock_at Đầu vào trực tiếp
content_type Bắc cầu, qua tiền ảnh qub_id
created_at Bắc cầu, qua tiền ảnh qub_id
outcome_at Bắc cầu, qua tiền ảnh qub_id
drand_round Bắc cầu, qua tiền ảnh qub_id (V1.2)
body Bắc cầu, qua body_hash = SHA3-256(body)
author_pubkey — (ngầm định) Khóa đã xác minh chữ ký chính là tác giả, theo định nghĩa
sender_label Văn bản chỉ để hiển thị; có thể thay đổi mà không phá vỡ chữ ký
reply_to Con trỏ luồng; có thể thay đổi mà không phá vỡ chữ ký
cosigner_pubkey / cosigner_signature Được ký độc lập trên cùng sig_input (xem §9.7)
drand_chain_id, tlock_ciphertext, visibility Các trường SealedQub bên ngoài, không nằm trong envelope — được bao phủ bởi các bất biến cấu trúc riêng của chúng (tính nhất quán vòng / chuỗi) nhưng không bởi chữ ký tác giả. (drand_round hiện được liên kết một cách bắc cầu qua tiền ảnh qub_id — xem ở trên.)

Hệ quả bảo mật của các trường không được xác thực.

Các triển khai hiển thị sender_label hoặc reply_to cho người dùng cuối PHẢI hiển thị danh tính đã xác thực (vân tay khóa công khai, chứng thực) làm tín hiệu danh tính chính, không phải nhãn.

9.4 Quy trình xác minh

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

Xác minh chữ ký là thao tác đắt nhất (đặc biệt là ML-DSA-65). Nó NÊN được thực hiện sau khi tất cả các kiểm tra rẻ hơn (hash, qub_id, unlock_at) đã đạt.

9.5 Chứng thực danh tính

Chứng thực danh tính — ánh xạ author_pubkey đến các tuyên bố danh tính có thể nhận biết bởi con người như handle qub, địa chỉ email, handle mạng xã hội, hoặc thông tin xác thực passkey — là một cải tiến tiến bộ phía người xemkhông bắt buộc cho việc xác minh chữ ký. Người xem phân giải chứng thực thành một danh tính hiển thị PHẢI áp dụng thứ tự ưu tiên:

handle > email > social > fingerprint

Dấu vân tay dự phòng là hex chữ thường của SHA3-256(author_pubkey); nó luôn có sẵn cho bất kỳ qub đã ký nào. Người xem CÓ THỂ rút gọn nó để hiển thị — trình xem tham chiếu hiển thị qub: theo sau là bốn byte đầu và bốn byte cuối (qub:<8 hex>…<8 hex>).

Một trình xác minh tuân thủ có thể hoàn thành mọi kiểm tra trong §9.4 mà không cần liên hệ với API qub, không cần mạng nào ngoài nơi lưu trữ vĩnh viễn và drand, và không cần tra cứu phía máy chủ. Phân giải chứng thực là một bước nỗ lực tốt nhất riêng biệt chỉ được thực hiện sau khi việc xác minh chữ ký đã thành công.

9.6 Tác động kích thước

Ed25519 ML-DSA-65
Chữ ký 64 byte 3.309 byte
Khóa công khai 32 byte 1.952 byte
Tổng cộng cho mỗi qub 96 byte 5.261 byte
Chênh lệch chi phí lưu trữ (ở mức ~$5/MB) ~$0.0005 ~$0.026

Đối với một qub văn bản 500–2.000 byte, ML-DSA-65 tăng kích thước lưu trữ lên khoảng ba lần. Chi phí tuyệt đối là không đáng kể.

9.7 Xác minh người đồng ký (thỏa thuận song phương giao ước)

Đối với các thỏa thuận song phương (content_type = 0x03), một lớp chữ ký thứ hai chứng minh cả hai bên đã đồng ý với các điều khoản giống nhau.

Các trường envelope:

Cả hai trường PHẢI hiện diện cùng nhau hoặc cả hai vắng mặt. Nếu chính xác một trường hiện diện, người xem PHẢI báo cáo lỗi toàn vẹn.

Quy trình xác minh:

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

Tính chất:

Cổng ràng buộc email (vận hành). Khi một giao ước đã staging mang liên hệ email Bên B (§6.1), dịch vụ tải lên qub PHẢI từ chối yêu cầu đồng ký trừ khi tồn tại một dấu hiệu xác minh email tồn tại ngắn khớp với cả id staging và mã băm email đã chuẩn hóa của liên hệ đó. Dấu hiệu được ghi bởi /api/v1/auth/verify khi mã thông báo magic-link mang một staging_id và địa chỉ đã xác minh khớp với SHA-256(normalise_email(party_b.contact)) — trong đó normalise_email(addr) bảo toàn chữ hoa chữ thường của phần local và chỉ chuyển phần domain thành chữ thường (theo RFC 5321 §2.3.11), và SHA-256 ở đây là hàm băm NIST FIPS 180-4 (khác với SHA3-256 được sử dụng trong các suy dẫn §4) — và hết hạn 900 giây (15 phút) sau khi cấp. Đây là một cổng vận hành chống mạo danh, KHÔNG phải là một phần của bằng chứng qub trên chuỗi — một trình xác minh bên thứ ba phát lại §11 chỉ cần nơi lưu trữ vĩnh viễn và drand, không cần bất kỳ tra cứu phía máy chủ nào. Dấu hiệu chỉ tồn tại phía máy chủ và không bao giờ là một phần của thân được ký.

Tác động kích thước (tác giả ML-DSA-65 + người đồng ký):

Thành phần Kích thước
Chữ ký tác giả 3.309 byte
Khóa công khai tác giả 1.952 byte
Chữ ký người đồng ký 3.309 byte
Khóa công khai người đồng ký 1.952 byte
Tổng chi phí mật mã 10.522 byte
Chênh lệch chi phí lưu trữ ~$0.05

10. Hiển thị và làm sạch Markdown

Phần này quan trọng về mặt bảo mật. Người xem hiển thị các qub văn bản (content_type = 0x01) sử dụng một tập hợp con Markdown bị hạn chế.

10.1 Các phần tử được phép

10.2 Các phần tử bị cấm

Phần tử Xử lý
HTML thô (<div>, <script>, v.v.) Loại bỏ hoàn toàn. Không có HTML nào đi qua.
Hình ảnh (![alt](url)) Loại bỏ. Cú pháp hình ảnh bị xóa khỏi đầu ra.
Liên kết ([text](url)) URL được hiển thị dưới dạng văn bản thuần nhìn thấy. Không tự động liên kết. Không thể nhấp mà không có hành động rõ ràng từ người dùng.
Các sơ đồ URL nguy hiểm javascript:, data:, vbscript:, file: — loại bỏ.
Iframe, embed, object Loại bỏ.
Thực thể HTML Chỉ giải mã thành ký tự hiển thị nếu an toàn.

10.3 Triển khai

Các triển khai PHẢI sử dụng bộ phân tích danh sách cho phép nghiêm ngặt, không phải danh sách chặn. Cách tiếp cận được khuyến nghị:

  1. Phân tích Markdown bằng pulldown-cmark (hoặc tương đương).
  2. Duyệt AST và bỏ bất kỳ node nào không có trong danh sách cho phép (§10.1).
  3. Đối với các node liên kết: phát URL dưới dạng văn bản nhìn thấy, không phải dưới dạng phần tử <a> có thể nhấp.
  4. Chuyển đổi AST đã lọc thành một biểu diễn trung gian có kiểu (ví dụ, một enum MarkdownNode chỉ có các biến thể an toàn). HTML thô về mặt cấu trúc không thể biểu diễn được trong IR này.
  5. Hiển thị từ IR có kiểu đến lớp view đích (ví dụ, các thành phần view phản ứng, các node DOM). Không có sự ghép chuỗi HTML hoặc innerHTML tại bất kỳ thời điểm nào.

Các cách tiếp cận danh sách chặn rất mong manh vì các phần mở rộng Markdown mới hoặc các đặc tính riêng của bộ phân tích có thể đưa vào các phần tử không được lọc. Cách tiếp cận AST có kiểu làm cho XSS về mặt cấu trúc là không thể — không có biến thể nào có thể mang HTML tùy ý.

10.4 Giới hạn kích thước và cấu trúc


11. Xác minh bên thứ ba

Bất kỳ bên thứ ba nào cũng có thể xác minh một qub công khai mà không cần sự hợp tác của qub. Quy trình xác minh:

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.

Những gì xác minh chứng minh:

Bằng chứng Điều nó thiết lập
Cam kết Ciphertext đã tồn tại trước dấu thời gian khối lưu trữ.
Toàn vẹn Thân văn bản thuần khớp với mã băm đã cam kết và chưa bị thay đổi.
Định thời Nội dung không thể đọc được cho đến vòng drand, tương ứng với thời gian mở khóa đã chọn (tùy thuộc vào các giả định bảo mật của tlock và drand).

Những gì xác minh KHÔNG chứng minh:

Không-chứng minh Lý do
Quyền tác giả sender_label chỉ để trang trí. Không có sig_alg0x01, bất kỳ ai cũng có thể đã niêm phong nội dung này.
Ý định qub chứng minh nội dung và định thời, không phải những gì người tạo có ý nghĩa chủ quan.
Định thời trước sự kiện Việc đưa vào khối lưu trữ có thể chậm hơn so với việc tải lên thực tế vài phút. Dấu thời gian cam kết là thời gian khối, không phải thời điểm người dùng nhấn "niêm phong."

12. Phiên bản hóa

12.1 Phiên bản giao thức

Trường version (u8) trong cả SealedQubQubEnvelope xác định phiên bản chính của giao thức.

12.2 Lịch sử phiên bản

Phiên bản Giá trị Mô tả
v1 0x01 Các qub văn bản công khai (content_type 0x01), các thỏa thuận song phương giao ước (0x03, schema structured/v1, tác giả ML-DSA-65 + người đồng ký), tlock, SHA3-256

12.3 Tương thích thuận

Một người xem v1 gặp một QubEnvelope với các khóa map CBOR tùy chọn không xác định (các khóa không trong thứ tự chuẩn §3.2) NÊN bỏ qua các khóa đó và tiến hành xác minh bằng các trường đã biết. Điều này cho phép các bổ sung nhỏ trong tương lai (ví dụ, siêu dữ liệu mới) mà không yêu cầu tăng phiên bản chính.

Một người xem v1 gặp sig_alg = 0x01 (ML-DSA-65) nhưng thiếu hỗ trợ xác minh ML-DSA-65 NÊN hiển thị nội dung qub với thông báo "chữ ký hiện diện nhưng không thể xác minh", không từ chối qub hoàn toàn. Triển khai tham chiếu hiện tại từ chối mọi giá trị sig_alg ngoài 0x000x01 vì sổ đăng ký v1 không chứa thuật toán hợp lệ nào khác — từ chối nghiêm ngặt và soft-fail là không thể phân biệt được về mặt quan sát cho đến khi một thuật toán thứ ba được đăng ký. Hành vi soft-fail ở trên trở nên quan trọng một khi §9.2 chấp nhận một mục mới, và người xem tham chiếu sẽ được cập nhật để soft-fail tại thời điểm đó.

12.4 Phiên bản bao bọc bên ngoài

OuterWrapper được mô tả trong §13 mang byte version riêng của nó, độc lập với SealedQub.versionQubEnvelope.version. Hai không gian phiên bản phát triển riêng biệt: một thay thế đối xứng an toàn hậu lượng tử trong tương lai sẽ tăng byte bao bọc mà không động đến phiên bản giao thức bên trong, và một bổ sung ở lớp giao thức trong tương lai (ví dụ, một trường envelope mới) tăng phiên bản bên trong mà không động đến byte bao bọc.

OUTER_WRAPPER_VERSION_* Giá trị Thuật toán Trạng thái
OUTER_WRAPPER_VERSION_1 0x01 AES-256-GCM với nonce 12 byte, thẻ xác thực 16 byte, AAD ràng buộc với qub_id mặc định v1
0x020xFF Dành riêng Tương lai

Người xem PHẢI từ chối các phiên bản bao bọc không xác định kèm theo lỗi rõ ràng. Giao thức cố ý giữ không gian phiên bản bao bọc hẹp cho đến khi một động cơ di chuyển cụ thể xuất hiện (ví dụ, hướng dẫn NIST ưa chuộng một AEAD khác); một khe 0x02 sẽ được phân bổ trong cùng bản sửa đổi giới thiệu thuật toán.


13. Lớp bao bọc mã hóa bên ngoài

13.1 Lý do căn bản

Các lớp giao thức (QubEnvelope → tlock → SealedQub) làm cho một qub đã niêm phong bị khóa thời gian: thân không thể đọc được cho đến unlock_at và chữ ký vòng drand đã được công bố. Tuy nhiên, sau khi mở khóa, chữ ký vòng là công khai và hình dạng CBOR chuẩn của SealedQub có thể nhận diện được, vì vậy một người thu thập đã lập chỉ mục các giao dịch lưu trữ vĩnh viễn có thể giải mã hàng loạt toàn bộ kho qub.

Lớp bao bọc mã hóa bên ngoài đóng kênh đó bằng cách chèn một lớp AEAD đối xứng bổ sung giữa SealedQubCbor chuẩn và các byte được ghi vào nơi lưu trữ vĩnh viễn. Khóa 256 bit K chỉ tồn tại trong đoạn fragment URL của liên kết bàn giao và trên các thiết bị của người dùng; các trình duyệt không truyền các fragment URL đến các máy chủ, vì vậy qub.social, mọi cổng lưu trữ, và mọi CDN ở phía trước của một trong hai đều mù quan sát đối với K. Do đó, mỗi qub trong nơi lưu trữ vĩnh viễn là một ciphertext mờ mà bản rõ không thể khôi phục được mà không có URL mà người tạo đã chọn để chia sẻ.

Hiệu ứng ròng:

13.2 Phân lớp

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)

Niêm phong và mở khóa ở lớp giao thức (§7, §8) không thay đổi bên dưới ranh giới của lớp bao bọc; lớp bao bọc gắn vào tại điểm gọi của seal() và tách ra tại điểm gọi của unlock().

13.3 Cấu trúc dữ liệu 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
}

Các bất biến của trường.

Mã hóa CBOR. CBOR chuẩn theo §3, với cùng quy tắc thứ tự khóa (sắp xếp theo độ dài byte đã mã hóa tăng dần, sau đó theo từ điển). Bốn khóa là:

Khóa Byte đã mã hóa Thứ tự
nonce 6 1
qub_id 7 2
version 8 3
ciphertext 11 4

Do đó byte đầu tiên của CBOR OuterWrapper là header map độ dài xác định cho một map 4 mục (0xA4).

13.4 Ràng buộc AAD với qub_id

Lớp bao bọc ràng buộc qub_id làm dữ liệu được xác thực bổ sung AEAD. Đây là phòng thủ cấu trúc quan trọng chống lại ba lớp tấn công:

Tấn công Phòng thủ
Di chuyển ciphertext dưới một trường qub_id khác trong lớp bao bọc AAD không khớp → xác thực AEAD thất bại
Trộn fragment URL của qub A với các byte lưu trữ vĩnh viễn của qub B AAD không khớp → xác thực AEAD thất bại
Giả mạo trường qub_id của lớp bao bọc sau khi tải lên AAD không khớp → xác thực AEAD thất bại

Việc mang qub_id trong văn bản thuần của lớp bao bọc không làm suy yếu miễn nhiễm liệt kê đáng kể — qub_id chính nó là một hàm băm SHA3-256 của tiền ảnh §4.1 không có tiền ảnh có thể khôi phục được từ bản tóm tắt, và một người liệt kê đã thu thập các byte của lớp bao bọc không học được gì từ qub_id nhìn thấy được mà họ không thể suy luận từ chính sự tồn tại của bản tải lên.

13.5 Thuật toán bao bọc và tháo bao bọc

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

Sự sụp đổ chế độ thất bại. K sai, nonce sai, AAD không khớp, và ciphertext bị giả mạo đều tạo ra cùng một lỗi DECRYPT_FAILED. Đây là một tính chất AEAD có chủ đích: phân biệt chế độ thất bại sẽ tạo ra một kênh phụ mà kẻ tấn công từ xa có thể dò bằng cách gửi các lớp bao bọc bị lỗi và đo thời gian phản hồi. Các triển khai tham chiếu PHẢI gộp tất cả các thất bại AEAD thành một hình dạng lỗi duy nhất.

13.6 Vật liệu khóa và phân phối

Khóa bao bọc K là một giá trị ngẫu nhiên đều 256 bit được tạo cho mỗi qub bởi một CSPRNG. Các triển khai tham chiếu lấy nó từ:

Phân phối: K PHẢI được mã hóa dưới dạng base64 an toàn cho URL (RFC 4648 §5, không đệm) và được nối vào URL bàn giao dưới dạng thành phần fragment:

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

Fragment không bao giờ được truyền đến bất kỳ máy chủ nào bởi một trình duyệt tuân thủ. Các kênh khôi phục (chỉ mục lịch sử phía máy chủ, tự động gửi email chọn tham gia) duy trì URL bàn giao đầy đủ — bao gồm fragment — vượt ra ngoài thiết bị của người dùng là một sự đánh đổi rõ ràng chống lại thế crypto-shredding mặc định và PHẢI được kiểm soát bằng sự đồng ý rõ ràng của người dùng.

Mất fragment. Nếu một người dùng mất fragment URL và không có kênh khôi phục, qub không thể đọc được. Đây là sự đánh đổi quan trọng của thiết kế và PHẢI được công bố cho người dùng tại thời điểm niêm phong. MVP củng cố công bố tại thời điểm niêm phong với văn bản "lưu URL này" rõ ràng và một kênh khôi phục email đã xác minh cho những người dùng chọn tham gia.

13.7 Ngoài phạm vi cho phần này

13.8 Các qub công khai (lược bỏ lớp bao bọc)

Lớp bao bọc bên ngoài là tùy chọn ở lớp bàn giao. Một người tạo có thể niêm phong một qub dưới dạng công khai, trong trường hợp đó SealedQubCbor chuẩn được ghi vào nơi lưu trữ vĩnh viễn trực tiếp, không có lớp OuterWrapper và không có khóa K:

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

Một qub công khai là bị khóa thời gian nhưng không bị kiểm soát bằng liên kết: nó vẫn không thể đọc được cho đến khi vòng drand của nó được công bố (lớp tlock không thay đổi), nhưng sau khi mở khóa, bất kỳ ai có arweave_tx_id đều có thể giải mã nó — không cần fragment URL, vì không có K. Đây là sự đánh đổi có chủ đích cho các bề mặt mà máy chủ phải điều khiển: email thông báo công bố, các nhúng của bên thứ ba, và SEO sau-công-bố phong phú hơn đều cần một liên kết hoạt động được mà không có một bí mật mà máy chủ không bao giờ nắm giữ (§13.6).

Các hệ quả mà một nhà sản xuất PHẢI tính đến:

Riêng tư (đã bao bọc) vẫn là mặc định; công khai là một lựa chọn rõ ràng của người tạo trên mỗi qub.


14. Các vector kiểm tra

14.1 Suy dẫn qub_id

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

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

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

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

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

Các triển khai PHẢI tạo ra các giá trị body_hashqub_id giống hệt cho đầu vào này. Vector kiểm tra này NÊN là bài kiểm tra đơn vị đầu tiên được viết. Các giá trị chuẩn ở trên được tính toán bởi triển khai tham chiếu và PHẢI khớp từng bit. Các bố cục tiền ảnh lịch sử (trước ra mắt — không có qub trực tiếp nào phụ thuộc vào chúng): qub_id V1.0 với tiền ảnh 92 byte là 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; qub_id V1.1 với tiền ảnh 100 byte (sau khi gấp outcome_at_or_zero) là b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. V1.2 gấp drand_round vào và nâng dấu phân cách miền lên QUB_ID_V2.

14.2 Ánh xạ vòng mở khóa

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 Vòng lặp CBOR chuẩn

Các triển khai PHẢI xác minh rằng serialize(parse(serialize(qub))) == serialize(qub) cho tất cả các đầu vào hợp lệ. Đây là một kiểm tra thuộc tính, không phải một vector đơn lẻ.

14.4 CBOR PactTerms (content_type 0x03)

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

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

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

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

Các byte CBOR chuẩn và body_hash SHA3-256 được tính toán bởi triển khai tham chiếu. Các triển khai PHẢI tạo ra CBOR giống hệt byte cho đầu vào này.

Các triển khai cũng PHẢI xác minh rằng serialize(parse(serialize(pact))) == serialize(pact) cho tất cả các đầu vào PactTerms hợp lệ (kiểm tra thuộc tính).

14.5 Các vector lớp bao bọc bên ngoài đa ngôn ngữ

Lớp bao bọc bên ngoài (§13) có một fixture chuẩn riêng tại crates/qub-core/tests/vectors/wrapper_v1.json. Mỗi trường hợp cố định một bộ (key, nonce, qub_id, sealed_cbor) làm đầu vào hex mờ và khẳng định một đầu ra expected_wrapper_hex cụ thể. Cả hai triển khai tham chiếu đều tiêu thụ cùng một file JSON:

Fixture hiện tại ghim ba trường hợp:

Trường hợp Phạm vi bao phủ
basic-text-public Hình dạng SealedQub thực tế nhỏ nhất; không có trường tùy chọn. Thiết lập hình dạng lớp bao bọc chuẩn cho một qub điển hình v1.0.
with-recipient-pubkey SealedQub với recipient_pubkey được đặt (đường dẫn Giai đoạn 2). Bộ khóa CBOR bên trong khác nhau, qub_id khác nhau.
longer-body Thân ~4 KiB — luyện tập các tiền tố độ dài CBOR đa byte bên trong cả envelope bên trong và ciphertext bên ngoài.

Các triển khai PHẢI tạo ra expected_wrapper_hex giống hệt byte cho các đầu vào đã ghi. Việc tái tạo fixture yêu cầu QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors và được dành riêng cho các thay đổi định dạng có chủ ý.


15. Quản trị hồ sơ mật mã (Tương lai)

Phần này có tính thông tin cho v1 và trở thành quy phạm lần đầu tiên khi một thuật toán thứ hai bước vào bất kỳ nguyên thủy mật mã nào của qub.

15.1 Thế hiện tại

Giao thức v1 ràng buộc chính xác một thuật toán cho mỗi nguyên thủy:

Các trình xác minh hiện tại cứng mã hóa độ dài khóa và chữ ký cho mỗi nguyên thủy. Không có bề mặt linh hoạt nào được hiển thị bởi định dạng wire.

15.2 Hình dạng dự định

Khi một thuật toán thứ hai bước vào giao thức, trình xác minh sẽ được cấu hình cho một CryptoProfile có tên (ví dụ, ExqubV1) liệt kê tập hợp chính xác các giá trị được phép cho mỗi nguyên thủy — sig_algs, các chuỗi drand, các phiên bản lớp bao bọc, các loại nội dung. Hồ sơ được cố định tại thời điểm xác minh, không bao giờ được thương lượng trong băng. Bất kỳ giá trị nào ngoài hồ sơ đang hoạt động đều bị từ chối.

Điều này đảm bảo rằng việc thêm ML-DSA-87 hoặc kích hoạt Ed25519 không thể làm suy yếu hồi tố các cấu hình trình xác minh hiện có: một trình xác minh v1 vẫn là một trình xác minh v1 ngay cả sau khi một hồ sơ v2 được công bố.

15.3 Điều kiện kích hoạt

Thăng cấp §15 lên trạng thái quy phạm khi bất kỳ điều nào sau đây được đề xuất:

Cho đến lúc đó §15 là một chỗ giữ chỗ cố định hình dạng di chuyển để các PR tương lai đáp xuống một mục tiêu đã biết thay vì tái thẩm bề mặt thương lượng từ đầu.