ข้อกำหนดโปรโตคอล qub
qub คือโปรโตคอลสำหรับข้อผูกพันเชิงเวลาทางการเข้ารหัส: ระบบสำหรับผนึกข้อความให้กับวันที่ในอนาคต และเมื่อวันนั้นมาถึง ก็สามารถพิสูจน์ได้อย่างแม่นยำว่าพูดอะไรไว้และพูดเมื่อใด
มีองค์ประกอบพื้นฐานสามอย่างที่ทำให้ระบบนี้ทำงานได้ drand เป็นบีคอนความสุ่มแบบกระจายศูนย์ — วันเปิดเผยถูกบังคับใช้ด้วยฟิสิกส์ ไม่ใช่ด้วยเจตนาดีของฝ่ายใด พื้นที่เก็บถาวรสาธารณะ เป็นที่จัดเก็บสาธารณะที่ป้องกันการแก้ไข — ไม่มีฝ่ายใดสามารถแก้ไขหรือลบ qub ได้เมื่อผนึกแล้ว ML-DSA-65 เป็นลายเซ็นดิจิทัลแบบหลังควอนตัม — qub แต่ละชิ้นผูกกับคู่กุญแจที่ความลับไม่เคยออกจากอุปกรณ์ของผู้เขียน
เมื่อรวมกันแล้ว องค์ประกอบเหล่านี้สร้างคำกล่าวที่ล็อกเวลาได้ ตรวจจับการดัดแปลงได้ และระบุที่มาได้ — ใบเสร็จที่มูลค่าเพิ่มขึ้นเมื่อความสามารถของโลกในการปลอมแปลงอดีตดีขึ้น
ส่วนที่เหลือของเอกสารนี้คือข้อกำหนดเชิงบรรทัดฐานที่จำเป็นสำหรับการใช้งานที่ทำงานร่วมกันได้
ข้อกำหนดโปรโตคอล qub
| ฟิลด์ | ค่า |
|---|---|
| เวอร์ชัน | 1.0 (เวอร์ชันโปรโตคอล 0x01 เวอร์ชันห่อหุ้มภายนอก 0x01) |
| วันที่ | 2026-05-01 |
| สถานะ | ฉบับร่าง |
| ตรวจทานถึง | 2026-05-01 |
เอกสารนี้เป็นข้อกำหนดโปรโตคอลเชิงบรรทัดฐานสำหรับระบบข้อผูกพันเชิงเวลา qub กำหนดโครงสร้างข้อมูล กฎการเรียงลำดับบิต สูตรการได้มาซึ่งค่า และขั้นตอนการตรวจสอบที่จำเป็นสำหรับการใช้งานที่ทำงานร่วมกันได้
ขอบเขต: ชั้นโปรโตคอลถูกออกแบบให้เป็นกลางทางภาษาโดยเจตนา — เนื้อหา qub เป็นข้อความ / มาร์กดาวน์ / ไบต์สัญญาที่ทึบแสง และการเรนเดอร์ตามภาษาท้องถิ่นเป็นความรับผิดชอบของผู้ชม (เว็บแอป qub.social, iframe <qub-embed>, ไคลเอนต์ MCP เป็นต้น)
1. สัญลักษณ์และข้อตกลง
| สัญลักษณ์ | ความหมาย |
|---|---|
u8, u64, i64 |
จำนวนเต็มไม่มีเครื่องหมาย/มีเครื่องหมายตามความกว้างบิตที่ระบุ |
[u8; N] |
อาร์เรย์ไบต์ความยาวคงที่ N ไบต์ |
Vec<u8> |
อาร์เรย์ไบต์ความยาวแปรผัน |
Option<T> |
ค่าประเภท T หรือไม่มีอยู่ |
String |
สตริงข้อความ UTF-8 ที่ผ่านการนอร์มัลไลซ์แบบ NFC |
| ` | |
SHA3-256(x) |
แฮช NIST SHA3-256 ของสตริงไบต์ x (FIPS 202) |
ceil(x) |
ฟังก์ชันปัดขึ้น: จำนวนเต็มที่น้อยที่สุดที่ ≥ x |
| CBOR | Concise Binary Object Representation (RFC 8949) |
| big-endian | ไบต์ที่มีนัยสำคัญที่สุดมาก่อน |
จำนวนเต็มทั้งหมดในการสร้างพรีอิมเมจถูกเข้ารหัสเป็นอาร์เรย์ไบต์ความกว้างคงที่ big-endian (i64 → 8 ไบต์, u8 → 1 ไบต์) ยกเว้นที่ระบุไว้เป็นอย่างอื่น
ไทม์สแตมป์ทั้งหมดเป็น วินาที Unix ใน UTC
2. โครงสร้างข้อมูล
2.1 ComposeQub (สถานะในหน่วยความจำของผู้สร้าง)
ไม่ได้เรียงลำดับบิตเป็น CBOR ไม่ได้เขียนลงพื้นที่เก็บถาวร อยู่เฉพาะในแอปผู้สร้าง
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 (ข้อมูลโหลดที่ถอดรหัสแล้ว)
เรียงลำดับบิตโดยใช้ CBOR มาตรฐาน (§3) ถูกเข้ารหัสไว้ภายใน SealedQub นี่คือโครงสร้างที่พิสูจน์ความสมบูรณ์ของเนื้อหาหลังการถอดรหัส
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
}
ค่าพื้นฐาน (qub ข้อความที่ไม่ลงนาม): version = 0x01, content_type = 0x01, sig_alg = 0x00, ฟิลด์ Option ทั้งหมดไม่มีอยู่
การตั้งค่า v1 อื่น ๆ: content_type = 0x03 (เนื้อหาสัญญา ดู §6.1); sig_alg = 0x01 (ML-DSA-65) โดยมี author_signature และ author_pubkey ปรากฏอยู่ (ดู §9.3); cosigner_pubkey และ cosigner_signature ปรากฏร่วมกันสำหรับสัญญาที่ลงนามร่วม (ดู §9.7); reply_to ตั้งค่าเป็น qub_id ของ qub แม่สำหรับ qub แบบสายตอบกลับ (ดู §9.3 สำหรับผลกระทบด้านขอบเขตของลายเซ็น)
2.3 SealedQub (รูปแบบการสื่อสารมาตรฐาน)
เรียงลำดับบิตโดยใช้ CBOR มาตรฐาน (§3) เขียนลงพื้นที่เก็บถาวร นี่คืออาร์ติแฟกต์บนเชน
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 (สถานะแอปพลิเคชันของผู้ชม)
ไม่ได้เรียงลำดับบิตเป็น CBOR อยู่เฉพาะในแอปผู้ชม สร้างขึ้นหลังจากการถอดรหัสและการตรวจสอบสำเร็จ
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 — สืบทอดมาจาก QubEnvelope.outcome_at / SealedQub.outcome_at; ขับเคลื่อนบล็อกรอดูคำตัดสินบนหน้าเปิดเผย (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. โปรไฟล์ CBOR มาตรฐาน
การเรียงลำดับบิต SealedQub และ QubEnvelope ทั้งหมดต้องเป็นไปตามโปรไฟล์นี้ การใช้งานสองแบบที่ได้รับโครงสร้างเชิงตรรกะเดียวกันต้องสร้างไบต์ที่เหมือนกัน
3.1 กฎการเข้ารหัส
| กฎ | ข้อกำหนด |
|---|---|
| มาตรฐาน | RFC 8949 §4.2.1 (ข้อกำหนดการเข้ารหัสแบบกำหนดได้แกนกลาง) |
| การเรียงลำดับกุญแจ map | เรียงตาม ความยาวไบต์ที่เข้ารหัสแล้ว ก่อน (สั้นก่อนยาว) แล้วจึง เรียงตามพจนานุกรม (เปรียบเทียบไบต์ต่อไบต์สำหรับการเข้ารหัสที่มีความยาวเท่ากัน) |
| การเข้ารหัสจำนวนเต็ม | รูปแบบสั้นที่สุด: 0–23 ในไบต์เริ่มต้น; 24–255 ใน 2 ไบต์; 256–65535 ใน 3 ไบต์ และอื่น ๆ |
| การเข้ารหัสความยาว | ความยาวแบบกำหนดได้เท่านั้น ไม่อนุญาตอาร์เรย์ map ไบต์สตริง หรือสตริงข้อความที่มีความยาวไม่กำหนด (additional info = 31 ถูกห้าม) |
| แท็ก | ไม่มีแท็ก CBOR (major type 6 ถูกห้าม) |
| ทศนิยมลอย | ไม่มี float (major types 7 values 0xF9–0xFB ถูกห้าม) |
| สตริงข้อความ | เข้ารหัส UTF-8 นอร์มัลไลซ์ NFC (Unicode Normalization Form C) |
| สตริงไบต์ | ไบต์ดิบ ไม่มีการเข้ารหัส base64 ที่ชั้น CBOR |
| กุญแจซ้ำ | ปฏิเสธพร้อมข้อผิดพลาด ตัวแยกวิเคราะห์ต้องไม่ยอมรับกุญแจ map ซ้ำอย่างเงียบ ๆ |
| กุญแจที่ไม่รู้จัก | ปฏิเสธพร้อมข้อผิดพลาด ตัวแยกวิเคราะห์ต้องไม่ยอมรับกุญแจ map นอกชุดกุญแจมาตรฐานของประเภทนั้น — สตริงไบต์มาตรฐานที่แตกต่างกันสองสตริงต้องไม่ถอดรหัสไปเป็นค่าเดียวกัน (encode(decode(x)) == x) และสำหรับ payload ที่ลงนาม กุญแจส่วนเกินจะเป็นเนื้อหาที่ซ่อนอยู่ซึ่งลายเซ็นทั้งสองผูกพันด้วย วิวัฒนาการของสคีมาดำเนินผ่าน version ไม่เคยผ่านกุญแจส่วนเกิน |
| ค่าเรียบง่าย | อนุญาตเฉพาะ true (0xF5), false (0xF4) และ null (0xF6) เท่านั้น |
| ฟิลด์ที่เลือกได้ | ฟิลด์ที่เลือกได้ที่ไม่มีอยู่จะถูก ละเว้น จาก map CBOR ทั้งหมด (ไม่ได้เข้ารหัสเป็น null) ฟิลด์ที่เลือกได้ที่มีอยู่จะรวมอยู่ในลำดับกุญแจที่เรียงแล้ว |
3.2 ลำดับกุญแจมาตรฐานที่ตรวจสอบแล้ว
ลำดับกุญแจเหล่านี้เป็นเชิงบรรทัดฐาน การใช้งานต้องส่งกุญแจในลำดับนี้พอดี การ assertion ระหว่างดีบักควรตรวจสอบลำดับในบิลด์ที่ไม่ใช่รีลีส
QubEnvelope (เวอร์ชัน 0x01, ไม่ลงนาม, ฟิลด์ที่เลือกได้ทั้งหมดไม่มีอยู่):
"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)
การหาลำดับกุญแจของ QubEnvelope: กุญแจแต่ละตัวเป็น CBOR text string ความยาวที่เข้ารหัส = 1 ไบต์เฮดเดอร์ + ความยาวสตริง (สำหรับสตริงต่ำกว่า 24 ไบต์) เรียงตามความยาวที่เข้ารหัสรวมก่อน แล้วจึงเรียงตามพจนานุกรมสำหรับกุญแจที่มีความยาวเท่ากัน
SealedQub (เวอร์ชัน 0x01, สาธารณะ, ไม่มีผู้รับ):
"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 (เนื้อหาสัญญา, 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 (แถวของอาร์เรย์ 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 อ้างอิงการเข้ารหัสไบต์
| ประเภท | การเข้ารหัส CBOR | ตัวอย่าง |
|---|---|---|
| แฮช SHA3-256 (32 ไบต์) | 0x58 0x20 + 32 ไบต์ |
body_hash, qub_id |
| ไทม์สแตมป์ (i64) | Major type 0 (บวก) หรือ 1 (ลบ) การเข้ารหัสสั้นที่สุด | วินาที Unix |
| เวอร์ชัน (u8, ค่า 1) | 0x01 (ไบต์เดียว) |
|
| ประเภทเนื้อหา (u8, ค่า 1) | 0x01 (ไบต์เดียว) |
|
| sig_alg (u8, ค่า 0) | 0x00 (ไบต์เดียว) |
|
| ลายเซ็น ML-DSA-65 (3,309 ไบต์) | 0x59 0x0C 0xED + 3,309 ไบต์ |
author_signature, cosigner_signature |
| กุญแจสาธารณะ ML-DSA-65 (1,952 ไบต์) | 0x59 0x07 0xA0 + 1,952 ไบต์ |
author_pubkey, cosigner_pubkey |
4. การหาค่าเชิงบรรทัดฐาน
4.1 qub_id
qub_id ระบุ qub เฉพาะตัวและผูก QubEnvelope เข้ากับ SealedQub มันถูกหาได้แบบกำหนดได้จากเนื้อหาของซอง
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
การเข้ารหัสตัวแยกโดเมน: สตริง "QUB_ID_V2" คือ 9 ไบต์ ASCII มีการเพิ่มไบต์ padding 0x00 หนึ่งไบต์เพื่อให้ครบ 10 ไบต์เพื่อการจัดเรียง การใช้งานต้องใช้ 10 ไบต์เหล่านี้พอดี: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
การเข้ารหัส outcome_at: V1.1 ขยายพรีอิมเมจจาก 92 เป็น 100 ไบต์เพื่อรวมฟิลด์ outcome_at ที่เลือกได้เข้าในการผูกพัน เมื่อ outcome_at ไม่มีอยู่จะเข้ารหัสเป็น 8 ไบต์ศูนย์ ตัวตรวจสอบโปรโตคอลปฏิเสธ outcome_at <= 0 ในทุกที่ จึงทำให้ค่า sentinel นี้ไม่สามารถชนกับค่าที่ถูกต้องได้ ดู §3.2 (รูปแบบการสื่อสาร) และ tasks/verdict-uplift-plan.md ในต้นไม้ซอร์สสำหรับกลไก verdict ที่เป็นเหตุผลของฟิลด์นี้
การเข้ารหัส drand_round: V1.2 ขยายพรีอิมเมจจาก 100 เป็น 108 ไบต์เพื่อรวม drand_round (รอบ drand เป้าหมาย, §4.3) เข้าในการผูกพัน และเลื่อนตัวแยกโดเมนเป็น QUB_ID_V2 การกระทำนี้ผูกรอบไทม์ล็อกเข้ากับเอกลักษณ์ qub: เกตเวย์ไม่สามารถผูก ciphertext ซ้ำเข้ากับรอบที่แตกต่าง (เช่น รอบที่ผ่านไปแล้ว) จาก unlock_at ที่แสดงได้ ขั้นตอนการปลดล็อก (§8) ยังตรวจสอบเพิ่มเติมว่ารอบที่ฝังอยู่ใน stanza ของ tlock ciphertext ตรงกับ unlock_round(unlock_at) ดังนั้นเวลาปลดล็อกที่แสดงจึงพิสูจน์ได้ว่าเป็นรอบที่ควบคุมการถอดรหัส
คุณสมบัติ:
- การเปลี่ยนแปลงฟิลด์ใด ๆ ใน QubEnvelope (body, ไทม์สแตมป์, ประเภทเนื้อหา, เวอร์ชัน) จะสร้าง qub_id ที่แตกต่างกัน
- qub_id ถูกคำนวณก่อนการเข้ารหัส ทั้ง QubEnvelope และ SealedQub มี qub_id เดียวกัน ผู้ชมตรวจสอบว่าตรงกันหลังจากการถอดรหัส
- qub_id ไม่ขึ้นกับ
sender_label,author_signatureหรือauthor_pubkeyซึ่งหมายความว่าเนื้อหาเดียวกันที่ผนึกในเวลาเดียวกันจะสร้าง qub_id เดียวกันโดยไม่คำนึงถึงผู้ลงนาม - การเปลี่ยน
titleของ SealedQub (โดยฟิลด์อื่นทั้งหมดคงที่) จะเปลี่ยนqub_idผ่านtitle_hashดังนั้นเกตเวย์จึงไม่สามารถสลับชื่อข้อความธรรมดาที่แสดงในการนับถอยหลังโดยไม่ทำให้เอกลักษณ์ qub เสียได้ - การเปลี่ยน
outcome_atของ SealedQub (โดยฟิลด์อื่นทั้งหมดคงที่) จะเปลี่ยนqub_idผ่านพรีอิมเมจ เกตเวย์ไม่สามารถสลับวันที่ verdict-on ก่อนการเปิดเผยที่แสดงในการนับถอยหลังโดยไม่ทำให้เอกลักษณ์ qub เสียได้ - การเปลี่ยน
drand_round(โดยฟิลด์อื่นทั้งหมดคงที่) จะเปลี่ยนqub_idผ่านพรีอิมเมจ เกตเวย์ไม่สามารถผูก timelock ciphertext ซ้ำเข้ากับรอบที่แตกต่างโดยไม่ทำให้เอกลักษณ์ qub เสียได้ เมื่อรวมกับการตรวจสอบ stanza-round เวลาปลดล็อกใน §8unlock_atที่แสดงจึงเป็นรอบที่ควบคุมการถอดรหัสจริง
4.2 body_hash
body_hash = SHA3-256(body)
โดยที่ body คือข้อมูลโหลดเนื้อหา Vec<u8> ดิบ สำหรับ qub ข้อความ นี่คือเนื้อหา qub ที่เข้ารหัส 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
โดยที่ title คือชื่อข้อความธรรมดาที่เลือกได้ซึ่งแสดงในการนับถอยหลังของผู้ชมก่อนการเปิดเผย (ดู §3.2) การนอร์มัลไลซ์ NFC ทำงานในเวลาแฮชเพื่อให้ไดเจสต์มีเสถียรภาพข้ามลำดับโค้ดพอยต์ที่เทียบเท่ากันทางการมองเห็น ค่า sentinel ที่เป็นศูนย์ทั้งหมดถูกสงวนไว้สำหรับกรณีไม่มี สตริงว่างถูกปฏิเสธที่ขอบเขต CBOR มาตรฐานเนื่องจากเป็นการเข้ารหัส "ไม่มี" ที่ไม่เป็นมาตรฐาน (การเข้ารหัสมาตรฐานละเว้นฟิลด์ทั้งหมด)
4.3 การแมปรอบปลดล็อก
drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| พารามิเตอร์ | แหล่งที่มา | ตัวอย่าง |
|---|---|---|
unlock_at |
วินาที Unix 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 |
การดำเนินการ ceil() เลือก รอบ drand แรกที่มีเวลาเปิดเผย ≥ unlock_at สิ่งนี้ทำให้มั่นใจว่า qub ไม่สามารถถอดรหัสได้ก่อนเวลาปลดล็อกที่เลือก
กรณีขอบ: ถ้า (unlock_at - chain_genesis_time) หารด้วย chain_period_seconds ลงตัวพอดี ผลลัพธ์จะเป็นรอบนั้นพอดี — qub ปลดล็อกที่เวลาเปิดเผยของรอบนั้นแน่นอน
การตรวจสอบ: unlock_at ต้องอยู่ในอนาคตในเวลาผนึก unlock_at ต้องไม่เกิน 10 ปีนับจาก created_at (เพื่อจำกัดความเสี่ยงในการพึ่งพา drand ในระยะยาว; UI ควรเตือนสำหรับวันที่ปลดล็อกที่เกิน 2 ปี)
5. Newtype รูปแบบการสื่อสาร
Newtype รูปแบบการสื่อสารให้ความปลอดภัยในเวลาคอมไพล์ป้องกันความสับสนของไบต์ CBOR กับ JSON ข้อความธรรมดาดิบ หรือการเข้ารหัสไบต์อื่น ๆ
| ประเภท | ประกอบด้วย | สร้างโดย | บริโภคโดย |
|---|---|---|---|
SealedQubCbor |
CBOR มาตรฐานของ SealedQub | serialize_sealed_qub() |
การอัปโหลดสู่พื้นที่เก็บถาวร, การดึงข้อมูลโดยผู้ชม |
QubEnvelopeCbor |
CBOR มาตรฐานของ QubEnvelope | serialize_qub_envelope() |
อินพุตการเข้ารหัส tlock, เอาต์พุตการถอดรหัส tlock |
5.1 กฎการสร้าง
// 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 การตรวจสอบเมื่อสร้าง
from_encoded() ควรตรวจสอบว่าอินพุตเริ่มต้นด้วยเฮดเดอร์ map CBOR ที่ถูกต้อง การตรวจสอบโครงสร้างเต็มรูปแบบเกิดขึ้นในเวลาแยกวิเคราะห์ ไม่ใช่ในเวลาสร้าง เพื่อหลีกเลี่ยงการแยกวิเคราะห์ซ้ำ
6. ทะเบียนประเภทเนื้อหา
| ค่า | ประเภท | ขนาด Body สูงสุด | หมายเหตุ |
|---|---|---|---|
0x00 |
สงวน (ไม่ถูกต้อง) | — | ต้องไม่ใช้ |
0x01 |
ข้อความธรรมดา (UTF-8, Markdown จำกัด) | 50 KB ชำระเงิน / 10 KB ฟรี | ดู §10 สำหรับกฎการเรนเดอร์ การแบ่งฟรี / ชำระเงินถูกบังคับใช้โดยบริการอัปโหลด เพดานแข็งระดับโปรโตคอลคือ 50 KB |
0x02 |
สงวน (อนาคต) | — | จัดสรรไว้สำหรับประเภทเนื้อหาในอนาคต; ไม่ถูกต้องใน v1 ผู้ชมต้องปฏิเสธตามกฎด้านล่าง |
0x03 |
สัญญา (ข้อตกลงทวิภาคี, เนื้อหา CBOR) | 100 KB | Body คือ canonical CBOR PactTerms (§6.1) การลงนามผู้ลงนามร่วมตาม §9.7 |
0x04 |
คำตัดสิน (การให้คะแนนตัวเองของผู้สร้าง, เนื้อหา CBOR) | 8 KB | Body คือ canonical CBOR VerdictBody (§6.2) ส่งออกได้เฉพาะ intent ฝั่งระบบ verdict เท่านั้น ความสัมพันธ์กับ qub แม่อยู่บน Arweave tag Parent-Tx-Id ไม่ได้อยู่บน body ดู verdict-uplift-plan §3.4 |
ผู้ชมต้องปฏิเสธประเภทเนื้อหาที่ไม่รู้จักด้วยข้อผิดพลาดที่ผู้ใช้มองเห็นได้ชัดเจน ผู้ชมต้องไม่พยายามเรนเดอร์ประเภทที่ไม่รู้จักเป็นข้อความ
6.1 Body ของสัญญา (content_type = 0x03)
Body ของสัญญาคือการเข้ารหัส CBOR มาตรฐานของค่า 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)> }
ลำดับกุญแจ CBOR มาตรฐานสำหรับ map ทั้งสามรายการอยู่ใน §3.2 CBOR สัญญาที่เรียงลำดับบิตทั้งหมดต้องไม่เกิน 100 KB (ตรงกับ §6)
ตัวแยกประเภทสคีมา แถวแรกใน terms สำหรับสัญญา structured/v1 ต้องเป็น { key: "pact_schema", value: "structured/v1" } แถวที่ไม่มีเครื่องหมายนี้เป็นสัญญา "กำหนดเอง" และไม่ได้รับการตรวจสอบเชิงโครงสร้างหรือการเรนเดอร์ที่ตระหนักถึงสคีมา
ช่องการรับทราบที่ถูกตรึง สัญญา structured/v1 มีแถวการรับทราบสี่แถวพอดีภายใต้กุญแจเหล่านี้:
"initiator_standard_terms"
"initiator_capacity_terms"
"counterparty_standard_terms"
"counterparty_capacity_terms"
value สำหรับแต่ละแถวเป็นหนึ่งในแปดสตริงภาษาอังกฤษที่ถูกตรึง ซึ่งเลือกโดยคู่ (role, kind) โดยที่ role ∈ { seller, buyer, provider, client } และ kind ∈ { standard, capacity } สตริงเหล่านี้เป็น ข้อมูลโปรโตคอลเชิงบรรทัดฐาน — ลายเซ็น ML-DSA-65 ของทั้งสองฝ่ายผูกพันกับไบต์ที่แน่นอนผ่าน body_hash สตริงเหล่านี้ไม่ได้แปลเป็นภาษาท้องถิ่น; body ที่ลงนามแล้วเป็นกลางทางภาษา การเปลี่ยนถ้อยคำใด ๆ ต้องมีเวอร์ชันสคีมาใหม่ (structured/v2)
แปดสตริง การค้นหา (acknowledgement_for(role, kind)) และเหตุผลสำหรับแต่ละสตริงถูกตรึงโดยการใช้งานอ้างอิง การใช้งานที่สอดคล้องต้องส่งค่าการรับทราบที่เป็นไบต์เหมือนกัน การทดสอบ golden-fixture SHA3-256 body-hash ที่ครอบคลุมการรวมบทบาททั้งสี่จับการเลื่อนใด ๆ
ลำดับการแสดงผลของผู้ชม สตริงการรับทราบมีวลีเช่น "described above" ซึ่งสันนิษฐานว่าแถวคำอธิบาย / ขอบเขตเรนเดอร์ก่อนการรับทราบ ผู้ชมต้องเรนเดอร์อาร์เรย์ terms ตามลำดับ CBOR; การจัดเรียงใหม่ทำลายความหมายของข้อความ
ผู้ติดต่อของคู่สัญญา เมื่อ contact ของฝ่าย B เป็นที่อยู่อีเมลที่ถูกต้อง บริการอัปโหลด qub จะส่งอีเมลคำเชิญรีวิว / ลงนามร่วมโดยอัตโนมัติในเวลาจัดเตรียม และผูกการลงนามร่วมเอาในที่สุดเข้ากับการยืนยันที่อยู่เดียวกันนั้น (§9.7) สัญญาที่ผู้ติดต่อฝ่าย B ไม่มีอยู่ยังสามารถลงนามร่วมได้ แต่ผ่านช่องทางนอกแบนด์เท่านั้น — บริการปฏิเสธคำขอลงนามร่วมที่ไม่สามารถสร้างเครื่องหมายยืนยันอีเมล 15 นาทีที่ตรงกันได้
6.2 Body ของคำตัดสิน (content_type = 0x04)
Body ของคำตัดสินคือการเข้ารหัส canonical CBOR ของค่า 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
}
ลำดับกุญแจ canonical CBOR:
"outcome" (8 encoded bytes)
"reflection" (11 encoded bytes) ← only if present
"evidence_url" (13 encoded bytes) ← only if present
"verdict_version" (16 encoded bytes)
CBOR ของคำตัดสินที่ทำให้เป็นอนุกรมทั้งหมดต้องไม่เกิน 8 KB (ตรงกับแถวรีจิสทรีด้านบน)
Enum ของผลลัพธ์ ไบต์บนสายเป็นกลางทางเจตนา; สี่กลุ่ม Right / Partial / Wrong / Unfalsifiable ครอบคลุมพื้นที่ผลลัพธ์ของทุก intent ที่มีคำตัดสิน ป้ายเฉพาะ intent ("Called it" / "Kept it" / "Shipped" / "Confirmed" สำหรับ Right เป็นต้น) เป็นเรื่องการเรนเดอร์ฝั่งผู้ชมที่แก้ไขเทียบกับ intent ของ qub แม่ — สายยังคงเป็นกลางทางภาษาและ intent ค่าที่อยู่นอก 1..=4 ต้องถูกปฏิเสธในขั้นตอนถอดรหัส
การเชื่อมโยงกับ qub แม่ qub ของคำตัดสินไม่ได้ใส่การอ้างอิงไปยัง qub แม่ใน body รหัสธุรกรรม Arweave ของ qub แม่จะส่งออกเป็นแท็กการจัดเก็บ Parent-Tx-Id ในเวลาอัปโหลด (§7 ชั้นแท็กการจัดเก็บ) วิธีนี้ทำให้ body เป็นคำกล่าวที่ลงนามครบในตัวเองของการประเมินตนเอง ห่วงโซ่ตรวจสอบ ("ทำนายอะไรถูก") ถูกสร้างขึ้นผ่านการค้นหาแท็ก Arweave
ความปลอดภัยของ URL หลักฐาน (เชิงบรรทัดฐาน) เมื่อมี evidence_url อยู่ ตัวตรวจสอบ (ฝั่ง compose, ฝั่งสาย, ขอบ Worker) ต้องบังคับใช้:
- HTTPS เท่านั้น สตริงต้องขึ้นต้นด้วยลำดับไบต์
https://สคีมาอื่นใด —http,ftp,javascript,data,fileฯลฯ — จะถูกปฏิเสธ - เพดานความยาว ≤ 2,048 ไบต์ (ขีดจำกัด URL เชิงปฏิบัติของเบราว์เซอร์)
- ตรวจ NFC + จุดรหัสที่เป็นอันตราย กฎเดียวกับ
titleและreflection— จุดรหัส bidi-override / zero-width / tag-block / BOM / C0 / C1 จะถูกปฏิเสธ คำจำกัดความตรงกับ Rustcrate::handle::contains_hostile_text_codepointและ TSworkers/api/src/utils/unicode.ts::isHostileCodepoint(รักษาให้สอดคล้องกัน) - ไม่มีช่องว่าง ไม่มี ASCII control ช่องว่าง / DEL / ไบต์ต่ำกว่า
0x20ที่ใดก็ตามใน URL จะถูกปฏิเสธ — ปิดช่องโหว่การฉีด\n/\tที่กฎ bidi ไม่ครอบคลุม - ส่วน host ต้องไม่ว่างเปล่า ทุกอย่างระหว่าง
https://กับ/,?, หรือ#ตัวแรกต้องไม่ว่างเปล่า
ไม่มีการดึงข้อมูลฝั่งเซิร์ฟเวอร์ Worker ต้องไม่ proxy, fetch, หรือพรีวิว URL โปรโตคอลเก็บเพียงสตริง การเรนเดอร์เกิดขึ้นฝั่งผู้ชมด้วย rel="nofollow noopener noreferrer" target="_blank" และแสดงโฮสต์ที่มองเห็นได้ข้างข้อความลิงก์
การสะท้อนคิด ข้อความสะท้อนคิดที่ผู้สร้างเขียนไว้ตามทางเลือก ("อะไรเปลี่ยนไป คุณเรียนรู้อะไร") การตรวจสอบ NFC + จุดรหัสที่เป็นอันตรายเดียวกับ title อินพุตที่ว่างเปล่า / มีแต่ช่องว่างจะยุบเป็นไม่มีในเวลาก่อสร้าง
เวอร์ชันสคีมา v1 รองรับเฉพาะ verdict_version = 0x01 การแก้ไขสคีมาในอนาคตจะเพิ่มไบต์นี้และมาพร้อมเวอร์ชันโปรโตคอลใหม่ตาม §12
7. โปรโตคอลผนึก
ลำดับการผนึกที่สมบูรณ์ แต่ละขั้นตอนเป็นเชิงบรรทัดฐาน
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.
ชั้นแท็กการจัดเก็บ (นอกแบนด์) บริการอัปโหลด qub แนบชุดแท็กธุรกรรมการจัดเก็บขนาดเล็กโดยเจตนาควบคู่กับข้อมูลโหลดที่ห่อหุ้ม Content-Type=application/octet-stream จำเป็นเชิงบรรทัดฐาน บริการอ้างอิงแนบแท็กเลือกได้สามรายการเพิ่มเติมเมื่อผู้สร้างเลือกที่จะแสดง: Intent (เจตนาในการแต่งที่ผ่านการตรวจสอบ allowlist เช่น quote, reply, commitment), Author (ลายนิ้วมือกุญแจสาธารณะ §9.3 ของผู้สร้างเป็นเลขฐานสิบหก 64 ตัวอักษรพิมพ์เล็ก) และ Parent-Tx-Id (ID ธุรกรรมการจัดเก็บของ qub แม่สำหรับสายตอบกลับ, base64url 43 ตัวอักษร)
แท็ก Author เป็น เลือกได้ต่อ qub: แอปผู้สร้างอ้างอิงแนบเฉพาะเมื่อผู้ใช้เปิดใช้งานการระบุที่มาสาธารณะอย่างชัดเจนในเวลาผนึก เมื่อสวิตช์ปิด — ค่าเริ่มต้น — ไม่มีการเขียนแท็ก Author และ qub ไม่มีการระบุที่มาบนเชน: ไม่มีอะไรในพื้นที่เก็บถาวรเชื่อมโยงการอัปโหลดเข้ากับ handle อีเมล หรือ qub อื่น ๆ ของผู้สร้าง เมื่อสวิตช์เปิด ลายนิ้วมือ Author จะแปลงเป็น @handle ที่ผู้สร้างเลือกผ่านสายการยืนยันใน §9.5 ความสัมพันธ์สายตอบกลับและ Intent ไม่ระบุตัวตน ตัวห่อหุ้มภายนอก (§13) ปกป้อง body ภายในจากการเชื่อมโยงไซเฟอร์เท็กซ์ — ป้องกันผู้เก็บเกี่ยวจากการรู้จักและถอดรหัสจำนวนมากของการอัปโหลดที่มีรูปร่าง qub หลังจากที่รอบ drand ของพวกเขาเผยแพร่
บริการอ้างอิงจงใจไม่แนบแท็ก App-Name, App-Version หรือ Type: ตัวกรองค่าเดียวใด ๆ จะส่งคืนคลังข้อมูล qub ทั้งหมดให้กับการสอบถาม GraphQL ซึ่งไม่สอดคล้องกับขอบเขตการรักษาความลับเฉพาะ body ของตัวห่อหุ้ม
ตัวตรวจสอบที่สอดคล้องต้องไม่พึ่งพาแท็กการจัดเก็บใด ๆ สำหรับการตรวจสอบของบุคคลที่สามตาม §11; body hash / qub_id / ลายเซ็นผูกพันกับ CBOR ภายในเท่านั้น ไม่เคยผูกกับชุดแท็ก
8. โปรโตคอลปลดล็อก
ลำดับการปลดล็อกที่สมบูรณ์ แต่ละขั้นตอนเป็นเชิงบรรทัดฐาน
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. การลงนามผู้เขียน
9.1 เหตุผล
qubs ถูกจัดเก็บในพื้นที่เก็บถาวร ลายเซ็นผู้เขียนต้องไม่สามารถปลอมแปลงได้อย่างไม่มีกำหนด ซึ่งเป็นเหตุผลที่ v1.0 ใช้รูปแบบหลังควอนตัม ML-DSA-65 (FIPS 204) แทนรูปแบบคลาสสิกที่ความปลอดภัยอาจเสื่อมลงภายในอายุการใช้งานถาวรของ qub
9.2 ทะเบียนอัลกอริทึม
sig_alg |
รูปแบบ | ขนาดกุญแจ | ขนาดลายเซ็น |
|---|---|---|---|
0x00 |
ไม่มีลายเซ็น (ไม่ลงนาม) | — | — |
0x01 |
ML-DSA-65 (FIPS 204) | 1,952 ไบต์ | 3,309 ไบต์ |
ผู้ชมต้องปฏิเสธค่า sig_alg ที่ไม่รู้จัก
9.3 การสร้างพรีอิมเมจที่ลงนาม
พรีอิมเมจมีอยู่สองเวอร์ชัน ลายเซ็นทั้งหมดต้องใช้ V2 และตัวตรวจสอบต้องยอมรับ V2 เท่านั้น พรีอิมเมจ V1 ดั้งเดิม (บันทึกไว้ด้านล่างเพื่อการอ้างอิงเชิงประวัติ) เคยได้รับการยอมรับเป็นค่าสำรองเฉพาะการตรวจสอบระหว่างการย้ายไปยัง V2 ค่าสำรองนั้นถูกยกเลิกแล้ว และลายเซ็นที่ใช้ V1 เท่านั้นจะถูกปฏิเสธในตอนนี้
V2 (ปัจจุบัน — สร้างโดยการลงนามผู้เขียนใหม่ทั้งหมด และโดยลายเซ็นทั้งสองของขั้นตอนการจัดเตรียม / ลงนามร่วมของสัญญา):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V2" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 || // u8 (1 byte): MUST be 0x00 in v1.x
sender_label_hash || // [u8; 32]: SHA3-256(NFC(sender_label)),
// or 32 zero bytes when absent
reply_to_or_zero // [u8; 32]: parent qub_id, or 32 zero
// bytes when absent
)
// Total preimage: 155 bytes → 32-byte hash
signature = Sign(author_secret_key, sig_input)
sender_label_hash ใช้แบบแผนค่าเซนติเนลสำหรับกรณีไม่มีค่าเดียวกันกับ title_hash (§4.2.1): 32 ไบต์ศูนย์ไม่ใช่เอาต์พุต SHA3-256 ที่ถูกต้อง ดังนั้น "ไม่มีค่า" จะไม่มีวันชนกับป้ายที่มีอยู่ ทุกฟิลด์มีความกว้างคงที่ ดังนั้นพรีอิมเมจจึงไม่กำกวมโดยไม่ต้องมีคำนำหน้าความยาว
V1 (ดั้งเดิม — ยกเลิกแล้ว; ไม่ถูกสร้างขึ้นอีกต่อไปและไม่ได้รับการยอมรับในการตรวจสอบอีกต่อไป):
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
พรีอิมเมจ V1 ละเว้น sender_label และ reply_to มันเคยได้รับการยอมรับเป็นค่าสำรองเฉพาะการตรวจสอบระหว่างการย้ายไปยัง V2 ค่าสำรองนั้นถูกยกเลิกไปแล้ว — ตัวตรวจสอบต้องยอมรับพรีอิมเมจ V2 เท่านั้น นิยามนี้ยังคงไว้ที่นี่เพื่อการอ้างอิงเชิงประวัติและเพื่ออธิบายตัวแยกโดเมนด้านล่าง ลายเซ็นที่ตรวจสอบผ่านกับ V1 เท่านั้นต้องถือว่าเป็นการตรวจสอบที่ล้มเหลว
ตัวแยกโดเมน: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" คือ 17 ไบต์ ASCII แต่ละตัว ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]) ไม่มี padding ตัวแยกที่ต่างกันแยกโดเมนของโครงสร้างทั้งสอง ดังนั้นลายเซ็นบนพรีอิมเมจหนึ่งจะไม่มีวันตรวจสอบผ่านเป็นอีกอันหนึ่งได้
ไบต์ org_id_present: ไบต์ที่ตามหลัง unlock_at ต้องเป็น 0x00 การใช้งานอ้างอิงเปิดเผยสิ่งนี้เป็นค่าคงที่ ORG_ID_PRESENT_INDIVIDUAL = 0x00 ใน crates/qub-core/src/signing.rs; ผู้ชมที่สร้าง sig_input ใหม่สำหรับการตรวจสอบต้องส่งไบต์เดียวกัน
ขอบเขตลายเซ็น — สิ่งที่ครอบคลุมและไม่ครอบคลุม sig_input ของ V2 ผูกพันโดยตรงกับ version, qub_id, body_hash, unlock_at, sender_label และ reply_to (บวกตัวแยกโดเมนคงที่และไบต์ org_id_present) qub_id เองได้มาจาก version, content_type, created_at, unlock_at, outcome_at, drand_round และ body_hash ผ่านพรีอิมเมจ §4.1 ดังนั้นการเปลี่ยนแปลงฟิลด์เหล่านั้นใด ๆ จะสร้าง qub_id ที่แตกต่างกันและทำให้ลายเซ็นไม่ถูกต้องโดยอ้อม พื้นผิวที่รับรองความถูกต้องจึงเป็น:
| ฟิลด์ | ตรวจสอบโดยลายเซ็น | อย่างไร |
|---|---|---|
version |
✓ | อินพุตโดยตรงไปยัง sig_input |
qub_id |
✓ | อินพุตโดยตรง |
body_hash |
✓ | อินพุตโดยตรง |
unlock_at |
✓ | อินพุตโดยตรง |
sender_label |
✓ | อินพุตโดยตรงผ่าน sender_label_hash (พรีอิมเมจ V2 — รูปแบบเดียวที่ยอมรับ) |
reply_to |
✓ | อินพุตโดยตรงผ่าน reply_to_or_zero (พรีอิมเมจ V2 — รูปแบบเดียวที่ยอมรับ) |
content_type |
✓ | โดยอ้อมผ่านพรีอิมเมจ qub_id |
created_at |
✓ | โดยอ้อมผ่านพรีอิมเมจ qub_id |
outcome_at |
✓ | โดยอ้อมผ่านพรีอิมเมจ qub_id |
drand_round |
✓ | โดยอ้อมผ่านพรีอิมเมจ qub_id (V1.2) |
body |
✓ | โดยอ้อมผ่าน body_hash = SHA3-256(body) |
author_pubkey |
— (โดยปริยาย) | กุญแจที่ตรวจสอบลายเซ็นคือผู้เขียนตามนิยาม |
cosigner_pubkey / cosigner_signature |
— | ลงนามอย่างอิสระบน sig_input เดียวกัน (ดู §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | ฟิลด์ภายนอก SealedQub ไม่อยู่ในซอง — ครอบคลุมโดยค่าคงที่เชิงโครงสร้างของตนเอง (ความสอดคล้องของรอบ / เชน) แต่ไม่ใช่โดยลายเซ็นผู้เขียน (drand_round ตอนนี้ถูกผูกโดยอ้อมผ่านพรีอิมเมจ qub_id — ดูด้านบน) |
เหตุใด V2 จึงเป็นพรีอิมเมจเดียวที่ยอมรับ
- ภายใต้พรีอิมเมจ V1 ที่ยกเลิกแล้ว ฝ่ายที่มีสิทธิ์เขียนไบต์ที่จัดเก็บไว้สามารถสลับ
sender_label("Alice" → "Mallory") หรือเปลี่ยนพ่อแม่ของreply_to— และเข้ารหัสใหม่หลังรอบ — โดยไม่ทำให้ลายเซ็นผู้เขียนไม่ถูกต้อง เพราะไม่มีฟิลด์ใดอยู่ในพรีอิมเมจที่ลงนาม V2 ครอบคลุมทั้งสองฟิลด์ ดังนั้นการเปลี่ยนแปลงฟิลด์ใดฟิลด์หนึ่งจะพลิกการตรวจสอบเป็น "ล้มเหลว" เนื่องจากตัวตรวจสอบยอมรับ V2 เท่านั้นในตอนนี้ การสลับนี้จึงถูกปิดสำหรับทุกลายเซ็น: ลายเซ็นที่ไม่ผูกฟิลด์ใดเลย (กล่าวคือ ตรวจสอบผ่านกับ V1 เท่านั้น) จะถูกปฏิเสธทันทีแทนที่จะถูกยอมรับด้วยการลดระดับลงมา author_pubkeyภายในซองยังคงเป็นจุดยึดเอกลักษณ์ที่แท้จริง — ผู้ชมต้องดึงเอกลักษณ์การแสดงผลจากauthor_pubkey(ผ่านชั้นการยืนยัน §9.5) แทนที่จะเชื่อsender_label
การใช้งานที่แสดง sender_label หรือ reply_to ให้ผู้ใช้ปลายทางต้องแสดงเอกลักษณ์ที่รับรองความถูกต้อง (ลายนิ้วมือกุญแจสาธารณะ การยืนยัน) เป็นสัญญาณเอกลักษณ์หลัก ไม่ใช่ป้าย
9.4 ขั้นตอนการตรวจสอบ
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."
การตรวจสอบลายเซ็นเป็นการดำเนินการที่มีค่าใช้จ่ายมากที่สุด (โดยเฉพาะ ML-DSA-65) ควรดำเนินการหลังจากที่การตรวจสอบที่ถูกกว่าทั้งหมด (แฮช, qub_id, unlock_at) ผ่านแล้ว
9.5 การยืนยันเอกลักษณ์
การยืนยันเอกลักษณ์ — การแมป author_pubkey กับการอ้างเอกลักษณ์ที่มนุษย์สามารถจดจำได้ เช่น qub handle ที่อยู่อีเมล social handle หรือข้อมูลรับรอง passkey — เป็น การปรับปรุงที่ก้าวหน้าฝั่งผู้ชม และ ไม่จำเป็น สำหรับการตรวจสอบลายเซ็น ผู้ชมที่แปลงการยืนยันเป็นเอกลักษณ์การแสดงผลต้องใช้ลำดับความสำคัญ:
handle > email > social > fingerprint
ค่าสำรองลายนิ้วมือคือเลขฐานสิบหกพิมพ์เล็กของ SHA3-256(author_pubkey); มีอยู่เสมอสำหรับ qub ที่ลงนามใด ๆ ผู้ชมอาจย่อค่านี้สำหรับการแสดงผล — ผู้ชมอ้างอิงเรนเดอร์ qub: ตามด้วยสี่ไบต์แรกและสี่ไบต์สุดท้าย (qub:<8 hex>…<8 hex>)
ตัวตรวจสอบที่สอดคล้องสามารถดำเนินการตรวจสอบทุกข้อใน §9.4 ได้อย่างสมบูรณ์โดยไม่ต้องติดต่อ API qub โดยไม่ต้องใช้เครือข่ายเกินกว่าพื้นที่เก็บถาวรและ drand และโดยไม่ต้องค้นหาฝั่งเซิร์ฟเวอร์ใด ๆ การแปลงการยืนยันเป็นขั้นตอนพยายามที่ดีที่สุดแยกต่างหากที่ดำเนินการเฉพาะหลังจากการตรวจสอบลายเซ็นสำเร็จแล้ว
9.6 ผลกระทบด้านขนาด
| Ed25519 | ML-DSA-65 | |
|---|---|---|
| ลายเซ็น | 64 ไบต์ | 3,309 ไบต์ |
| กุญแจสาธารณะ | 32 ไบต์ | 1,952 ไบต์ |
| รวมต่อ qub | 96 ไบต์ | 5,261 ไบต์ |
| ค่าใช้จ่ายการจัดเก็บเพิ่ม (ที่ ~$5/MB) | ~$0.0005 | ~$0.026 |
สำหรับ qub ข้อความขนาด 500–2,000 ไบต์ ML-DSA-65 ทำให้ขนาดที่จัดเก็บเพิ่มขึ้นประมาณสามเท่า ค่าใช้จ่ายสัมบูรณ์ไม่มีนัยสำคัญ
9.7 การตรวจสอบผู้ลงนามร่วม (ข้อตกลงทวิภาคีของสัญญา)
สำหรับข้อตกลงทวิภาคี (content_type = 0x03) ชั้นลายเซ็นที่สองพิสูจน์ว่าทั้งสองฝ่ายยินยอมตามเงื่อนไขเดียวกัน
ฟิลด์ซอง:
cosigner_pubkey: กุญแจสาธารณะ ML-DSA-65 ของผู้ลงนามคู่สัญญา (ฝ่าย B)cosigner_signature: ลายเซ็นบนsig_inputเดียวกันกับผู้เขียน (§9.3)
ทั้งสองฟิลด์ต้องอยู่ร่วมกันหรือไม่อยู่ร่วมกัน หากมีฟิลด์เดียวอยู่ ผู้ชมต้องรายงานข้อผิดพลาดความสมบูรณ์
ขั้นตอนการตรวจสอบ:
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."
คุณสมบัติ:
- ผู้ลงนามร่วมลงนาม
sig_inputที่เหมือนกันกับผู้เขียน — ทั้งสองฝ่ายผูกพันกับqub_id,body_hashและunlock_atเดียวกัน (และภายใต้ V2 คือsender_label_hashและreply_to_or_zeroเดียวกัน) - เพื่อให้ผู้ลงนามคู่สัญญาสามารถสร้างพรีอิมเมจ V2 ใหม่ได้โดยไม่ต้องเข้าถึงไบต์ซองดิบ บริการจัดเตรียมบังคับใช้ในเวลาจัดเตรียมว่า
sender_labelของซองสัญญาต้องเท่ากับpact_terms.party_a.labelและreply_toต้องไม่มีค่า ทั้งสองข้อเป็นจริงสำหรับสัญญาของไคลเอนต์อ้างอิงทุกฉบับ ซองที่ละเมิดจะถูกปฏิเสธในขั้นจัดเตรียม - การได้มาของ
qub_id(§4.1) ไม่รวมฟิลด์ผู้ลงนามร่วม การเพิ่มผู้ลงนามร่วมในซองที่มีอยู่ไม่เปลี่ยนqub_id - สัญญาสามารถลงนามโดยผู้เขียนเท่านั้น (ข้อผูกพันฝ่ายเดียว) ลงนามโดยผู้ลงนามร่วมเท่านั้น (ไม่ปกติ) หรือทั้งสอง (หลักฐานทวิภาคีเต็มรูปแบบ)
เกตการผูกอีเมล (การดำเนินงาน) เมื่อสัญญาที่จัดเตรียมไว้มีผู้ติดต่ออีเมลฝ่าย B (§6.1) บริการอัปโหลด qub ต้องปฏิเสธคำขอลงนามร่วมเว้นแต่จะมีเครื่องหมายยืนยันอีเมลอายุสั้นที่ตรงกับทั้ง staging id และแฮชอีเมลที่นอร์มัลไลซ์ของผู้ติดต่อนั้น เครื่องหมายนี้ถูกเขียนโดย /api/v1/auth/verify เมื่อโทเค็น magic-link มี staging_id และที่อยู่ที่ตรวจสอบแล้วตรงกับ SHA-256(normalise_email(party_b.contact)) — โดยที่ normalise_email(addr) รักษาตัวพิมพ์ของ local-part และทำให้ส่วนโดเมนเป็นตัวพิมพ์เล็กเท่านั้น (ตาม RFC 5321 §2.3.11) และ SHA-256 ที่นี่คือแฮช NIST FIPS 180-4 (แตกต่างจาก SHA3-256 ที่ใช้ในการหาค่า §4) — และหมดอายุ 900 วินาที (15 นาที) หลังจากออก นี่เป็นเกตป้องกันการปลอมตัวเชิงการดำเนินงาน ไม่ใช่ส่วนหนึ่งของหลักฐาน qub บนเชน — ตัวตรวจสอบของบุคคลที่สามที่เล่นซ้ำ §11 ต้องการเพียงพื้นที่เก็บถาวรและ drand โดยไม่ต้องค้นหาฝั่งเซิร์ฟเวอร์ใด ๆ เครื่องหมายมีอยู่เฉพาะฝั่งเซิร์ฟเวอร์เท่านั้นและไม่เคยเป็นส่วนหนึ่งของ body ที่ลงนาม
ผลกระทบด้านขนาด (ML-DSA-65 ผู้เขียน + ผู้ลงนามร่วม):
| องค์ประกอบ | ขนาด |
|---|---|
| ลายเซ็นผู้เขียน | 3,309 ไบต์ |
| กุญแจสาธารณะผู้เขียน | 1,952 ไบต์ |
| ลายเซ็นผู้ลงนามร่วม | 3,309 ไบต์ |
| กุญแจสาธารณะผู้ลงนามร่วม | 1,952 ไบต์ |
| ภาระการเข้ารหัสรวม | 10,522 ไบต์ |
| ค่าใช้จ่ายการจัดเก็บเพิ่ม | ~$0.05 |
10. การเรนเดอร์ Markdown และการทำความสะอาด
ส่วนนี้สำคัญต่อความปลอดภัย ผู้ชมเรนเดอร์ qub ข้อความ (content_type = 0x01) โดยใช้ชุดย่อย Markdown ที่จำกัด
10.1 องค์ประกอบที่อนุญาต
- หัวเรื่อง:
#ถึง####(ไม่มี#####หรือ######) - การเน้น: ตัวหนา (
**) ตัวเอียง (*) ขีดทับ (~~) - รายการ: เรียงลำดับ (
1.) และไม่เรียงลำดับ (-,*) - บล็อกอ้างอิง (
>) - โค้ด: ช่วงในบรรทัด (```) และบล็อกที่มีรั้ว (`````)
- เส้นแนวนอน (
---) - ตัวแบ่งบรรทัด (ช่องว่างปิดท้ายสองช่องหรือบรรทัดว่าง)
- ย่อหน้า
10.2 องค์ประกอบที่ห้าม
| องค์ประกอบ | การจัดการ |
|---|---|
HTML ดิบ (<div>, <script> เป็นต้น) |
ตัดออกทั้งหมด ไม่มี HTML ผ่านได้ |
รูปภาพ () |
ตัดออก ไวยากรณ์รูปภาพถูกลบออกจากเอาต์พุต |
ลิงก์ ([text](url)) |
URL เรนเดอร์เป็นข้อความธรรมดาที่มองเห็นได้ ไม่ลิงก์อัตโนมัติ คลิกไม่ได้โดยไม่มีการกระทำที่ชัดเจนของผู้ใช้ |
| รูปแบบ URL ที่อันตราย | javascript:, data:, vbscript:, file: — ตัดออก |
| Iframe, embed, object | ตัดออก |
| HTML entity | ถอดรหัสเป็นอักขระแสดงผลเฉพาะเมื่อปลอดภัย |
10.3 การใช้งาน
การใช้งานต้องใช้ ตัวแยกวิเคราะห์ allowlist ที่เข้มงวด ไม่ใช่ blocklist แนวทางที่แนะนำ:
- แยกวิเคราะห์ Markdown โดยใช้
pulldown-cmark(หรือเทียบเท่า) - เดิน AST และตัดโหนดใด ๆ ที่ไม่อยู่ใน allowlist (§10.1)
- สำหรับโหนดลิงก์: ส่ง URL เป็นข้อความที่มองเห็นได้ ไม่ใช่เป็นองค์ประกอบ
<a>ที่คลิกได้ - แปลง AST ที่กรองแล้วเป็น การแทนกลางที่กำหนดประเภท (เช่น enum
MarkdownNodeที่มีเฉพาะตัวแปรที่ปลอดภัย) HTML ดิบไม่สามารถแทนได้ในเชิงโครงสร้างใน IR นี้ - เรนเดอร์จาก IR ที่กำหนดประเภทไปยังชั้นมุมมองเป้าหมาย (เช่น คอมโพเนนต์มุมมองแบบรีแอกทีฟ โหนด DOM) ไม่มีการต่อสตริง HTML หรือ
innerHTMLในจุดใดก็ตาม
แนวทาง blocklist เปราะบางเนื่องจากส่วนขยาย Markdown ใหม่หรือลักษณะเฉพาะของตัวแยกวิเคราะห์อาจแนะนำองค์ประกอบที่ไม่ได้กรอง แนวทาง AST ที่กำหนดประเภททำให้ XSS เป็นไปไม่ได้ในเชิงโครงสร้าง — ไม่มีตัวแปรใดที่สามารถถือ HTML ตามอำเภอใจได้
10.4 ข้อจำกัดด้านขนาดและโครงสร้าง
- ความลึกหัวเรื่องที่เรนเดอร์สูงสุด:
####(H4)#####และลึกกว่าถูกเรนเดอร์เป็นข้อความตัวหนา - ไม่มีข้อจำกัดในจำนวนย่อหน้า (ข้อจำกัดขนาด body ใน §6 เป็นข้อจำกัด)
- บล็อกโค้ดที่มีรั้ว: ไม่มีการเน้นไวยากรณ์ใน MVP เรนเดอร์เป็นข้อความที่จัดรูปแบบไว้ล่วงหน้าแบบ monospace
11. การตรวจสอบของบุคคลที่สาม
บุคคลที่สามใด ๆ สามารถตรวจสอบ qub สาธารณะได้โดยไม่ต้องร่วมมือกับ qub ขั้นตอนการตรวจสอบ:
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.
สิ่งที่การตรวจสอบพิสูจน์:
| หลักฐาน | สิ่งที่ยืนยัน |
|---|---|
| ข้อผูกพัน | ไซเฟอร์เท็กซ์มีอยู่ภายในไทม์สแตมป์ของบล็อกการจัดเก็บ |
| ความสมบูรณ์ | body ข้อความธรรมดาตรงกับแฮชที่ผูกพันและไม่ถูกแก้ไข |
| เวลา | เนื้อหาไม่สามารถอ่านได้จนถึงรอบ drand ซึ่งตรงกับเวลาปลดล็อกที่เลือก (ขึ้นอยู่กับสมมติฐานด้านความปลอดภัยของ tlock และ drand) |
สิ่งที่การตรวจสอบไม่พิสูจน์:
| ไม่ใช่หลักฐาน | เหตุผล |
|---|---|
| ผู้เขียน | sender_label เป็นการตกแต่ง หากไม่มี sig_alg ≥ 0x01 ใคร ๆ ก็สามารถผนึกเนื้อหานี้ได้ |
| เจตนา | qub พิสูจน์เนื้อหาและเวลา ไม่ใช่สิ่งที่ผู้สร้างหมายถึงในเชิงอัตวิสัย |
| เวลาก่อนเหตุการณ์ | การรวมบล็อกการจัดเก็บอาจล่าช้ากว่าการอัปโหลดจริงเป็นนาที ไทม์สแตมป์ข้อผูกพันคือเวลาบล็อก ไม่ใช่ขณะที่ผู้ใช้กด "ผนึก" |
12. การกำหนดเวอร์ชัน
12.1 เวอร์ชันโปรโตคอล
ฟิลด์ version (u8) ใน SealedQub และ QubEnvelope ระบุเวอร์ชันโปรโตคอลหลัก
- ผู้ชมต้องปฏิเสธเวอร์ชันหลักที่ไม่รู้จักด้วยข้อผิดพลาดที่ชัดเจน
- ภายในเวอร์ชันหลักที่รู้จัก ตัวถอดรหัสต้องปฏิเสธกุญแจ map ที่ไม่รู้จัก (§3.1) — วิวัฒนาการของสคีมาเกิดขึ้นโดยการนำเสนอ
versionใหม่ ไม่ใช่โดยการเพิ่มกุญแจที่ตัวถอดรหัสที่มีอยู่จะข้ามไป (รุ่นก่อนหน้าของข้อกำหนดนี้เคยอนุญาตให้ยอมรับฟิลด์เลือกได้ที่ไม่รู้จัก; ข้อความดังกล่าวถูกถอนออกแล้ว — มันทำให้encode(decode(x))ไม่เป็นแบบหนึ่งต่อหนึ่ง และเปิดช่องทางให้เนื้อหาลงนามที่ซ่อนอยู่บน payload ของสัญญา) - ประเภทเนื้อหา (
content_type) และรูปแบบลายเซ็น (sig_alg) ถูกควบคุมโดยเวอร์ชัน: ค่าใหม่อาจถูกนำเสนอควบคู่กับเวอร์ชันโปรโตคอลใหม่หรือการอัปเดตทะเบียนที่ชัดเจนเท่านั้น
12.2 ประวัติเวอร์ชัน
| เวอร์ชัน | ค่า | คำอธิบาย |
|---|---|---|
| v1 | 0x01 |
qub ข้อความสาธารณะ (content_type 0x01) ข้อตกลงทวิภาคีของสัญญา (0x03, สคีมา structured/v1, ML-DSA-65 ผู้เขียน + ผู้ลงนามร่วม), tlock, SHA3-256 |
12.3 ความเข้ากันได้ในอนาคต
ผู้ชม v1 ที่พบ QubEnvelope ที่มีกุญแจ CBOR map ที่ไม่รู้จัก (กุญแจที่ไม่อยู่ในลำดับมาตรฐาน §3.2) ต้องปฏิเสธด้วยข้อผิดพลาดการถอดรหัส (§3.1) ความเข้ากันได้ในอนาคตอาศัยฟิลด์ version ไม่ใช่การยอมรับกุญแจ: การเพิ่มในอนาคต — แม้แต่เมตาดาต้าเล็กน้อย — จะจัดส่งภายใต้ค่า version ใหม่ ซึ่งผู้ชม v1 จะปฏิเสธด้วยข้อผิดพลาด "โปรโตคอลใหม่กว่า" ที่ชัดเจน แทนที่จะทิ้งเนื้อหาที่ลายเซ็นผูกพันด้วยอย่างเงียบ ๆ
ผู้ชม v1 ที่พบ sig_alg = 0x01 (ML-DSA-65) แต่ขาดการสนับสนุนการตรวจสอบ ML-DSA-65 ควรแสดงเนื้อหา qub พร้อมประกาศ "มีลายเซ็นแต่ไม่สามารถตรวจสอบได้" ไม่ใช่ปฏิเสธ qub ทั้งหมด การใช้งานอ้างอิงในปัจจุบันปฏิเสธค่า sig_alg ทุกค่ายกเว้น 0x00 และ 0x01 เนื่องจากทะเบียน v1 ไม่มีอัลกอริทึมที่ถูกต้องอื่น — การปฏิเสธอย่างเข้มงวดและการล้มเหลวอย่างนุ่มนวลเหมือนกันในเชิงสังเกตจนกว่าจะมีการลงทะเบียนอัลกอริทึมที่สาม พฤติกรรมการล้มเหลวอย่างนุ่มนวลด้านบนกลายเป็นสำคัญเมื่อ §9.2 ยอมรับรายการใหม่ และผู้ชมอ้างอิงจะถูกอัปเดตให้ล้มเหลวอย่างนุ่มนวลในจุดนั้น
12.4 เวอร์ชันห่อหุ้มภายนอก
OuterWrapper ที่อธิบายไว้ใน §13 มีไบต์ version ของตัวเอง เป็นอิสระ จาก SealedQub.version และ QubEnvelope.version พื้นที่เวอร์ชันสองอย่างวิวัฒนาการแยกกัน: การแทนที่สมมาตรที่ปลอดภัยหลังควอนตัมในอนาคตจะเพิ่มไบต์ตัวห่อหุ้มโดยไม่แตะเวอร์ชันโปรโตคอลภายใน และการเพิ่มชั้นโปรโตคอลในอนาคต (เช่น ฟิลด์ซองใหม่) จะเพิ่มเวอร์ชันภายในโดยไม่แตะไบต์ตัวห่อหุ้ม
OUTER_WRAPPER_VERSION_* |
ค่า | อัลกอริทึม | สถานะ |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM พร้อม nonce 12 ไบต์ แท็กรับรองความถูกต้อง 16 ไบต์ AAD ผูกกับ qub_id |
ค่าเริ่มต้น v1 |
| — | 0x02–0xFF |
สงวน | อนาคต |
ผู้ชมต้องปฏิเสธเวอร์ชันตัวห่อหุ้มที่ไม่รู้จักด้วยข้อผิดพลาดที่ชัดเจน โปรโตคอลจงใจรักษาพื้นที่เวอร์ชันตัวห่อหุ้มให้แคบจนกว่าจะมีแรงผลักดันการย้ายที่ชัดเจนปรากฏ (เช่น คำแนะนำของ NIST ที่สนับสนุน AEAD ที่แตกต่างกัน); ช่อง 0x02 จะถูกจัดสรรในการแก้ไขเดียวกันที่นำเสนออัลกอริทึม
13. ตัวห่อหุ้มการเข้ารหัสภายนอก
13.1 เหตุผล
ชั้นโปรโตคอล (QubEnvelope → tlock → SealedQub) ทำให้ qub ที่ผนึกแล้ว ล็อกเวลา: body ไม่สามารถอ่านได้จนถึง unlock_at และลายเซ็นรอบ drand ได้รับการเผยแพร่ อย่างไรก็ตามหลังจากปลดล็อก ลายเซ็นรอบเป็นสาธารณะและรูปร่าง CBOR มาตรฐานของ SealedQub สามารถจดจำได้ ดังนั้นผู้เก็บเกี่ยวที่จัดทำดัชนีธุรกรรมพื้นที่เก็บถาวรจึงสามารถถอดรหัสคลังข้อมูล qub ทั้งหมดเป็นจำนวนมากได้
ตัวห่อหุ้มการเข้ารหัสภายนอกปิดช่องนั้นโดยแทรกชั้น AEAD แบบสมมาตรเพิ่มเติมระหว่าง SealedQubCbor มาตรฐานและไบต์ที่เขียนลงพื้นที่เก็บถาวร กุญแจ 256 บิต K อยู่ เฉพาะ ใน URL fragment ของลิงก์ส่งและบนอุปกรณ์ผู้ใช้; เบราว์เซอร์ไม่ส่ง URL fragment ไปยังเซิร์ฟเวอร์ ดังนั้น qub.social, ทุกเกตเวย์การจัดเก็บ และทุก CDN ที่อยู่หน้าทั้งสองจึงมองไม่เห็น K ในเชิงสังเกต ดังนั้นทุก qub ในพื้นที่เก็บถาวรจึงเป็นไซเฟอร์เท็กซ์ทึบแสงซึ่งข้อความธรรมดาไม่สามารถกู้คืนได้โดยไม่มี URL ที่ผู้สร้างเลือกที่จะแชร์
ผลสุทธิ:
- ภูมิคุ้มกันการระบุค่าเริ่มต้น ไบต์ที่ห่อหุ้มในพื้นที่เก็บถาวรไม่สามารถแยกแยะได้ในเชิงไบต์จากไซเฟอร์เท็กซ์ตามอำเภอใจ กลยุทธ์ผู้เก็บเกี่ยวของ "การสอบถาม GraphQL สำหรับการอัปโหลดที่มีรูปร่าง qub ถอดรหัสจำนวนมากด้วยลายเซ็น drand สาธารณะ" ไม่จบลงด้วยข้อความธรรมดา
- ท่าทีความเป็นส่วนตัวแบบทำลายการเข้ารหัส qub.social ไม่สามารถถอดรหัสคลังข้อมูลของตนเองได้อย่างแท้จริง การส่งหมายเรียกถึงไซเฟอร์เท็กซ์ ไม่ใช่ข้อความธรรมดา
- บันไดความลับสองชั้น ค่าเริ่มต้น = การเข้าถึงที่ควบคุมโดยลิงก์ (ส่วนนี้) qub ส่วนตัวที่เข้ารหัสผู้รับ (ฟีเจอร์ที่สงวนไว้สำหรับระยะที่ 2 ยังไม่ได้ระบุ) ซ้อนทับด้านบนเป็นชั้นที่สอง
13.2 การจัดชั้น
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)
การผนึกและปลดล็อกที่ชั้นโปรโตคอล (§7, §8) ไม่เปลี่ยนแปลงด้านล่างขอบเขตของตัวห่อหุ้ม; ตัวห่อหุ้มแนบที่จุดเรียกของ seal() และถอดออกที่จุดเรียกของ unlock()
13.3 โครงสร้างข้อมูล 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
}
ค่าคงที่ของฟิลด์
versionต้องเท่ากับ0x01สำหรับไบต์ตัวห่อหุ้ม v1.0qub_idต้องเท่ากับฟิลด์qub_idของ SealedQub ที่กู้คืนหลังการแกะ ขั้นตอนแกะไม่บังคับใช้สิ่งนี้โดยตรง (การผูก AEAD AAD ทำให้การดัดแปลงระดับไบต์เป็นไปไม่ได้) แต่ชั้นปลดล็อกตรวจสอบความสัมพันธ์โดยอ้อม: หากผู้สร้างห่อหุ้มSealedQubCborที่qub_idภายในไม่ตรงกับqub_idของตัวห่อหุ้ม §8 ขั้นตอน 11 จะล้มเหลวnonceต้องเป็น 96 บิต (12 ไบต์) สร้างใหม่โดย CSPRNG สำหรับการดำเนินการห่อทุกครั้ง การใช้ nonce ซ้ำภายใต้กุญแจเดียวกันอนุญาตการโจมตี AEAD nonce-reuse ที่กู้คืนข้อความธรรมดา; ผู้ผลิตต้องถือว่าคู่ (key,nonce) เป็นแบบยิงครั้งเดียวciphertextคือเอาต์พุต AES-256-GCM: ไบต์ไซเฟอร์เท็กซ์ต่อกันกับแท็กรับรองความถูกต้อง 16 ไบต์ciphertext.len() == SealedQubCbor.len() + 16พอดี
การเข้ารหัส CBOR CBOR มาตรฐานตาม §3 ด้วยกฎการเรียงลำดับกุญแจเดียวกัน (เรียงตามความยาวไบต์ที่เข้ารหัสจากน้อยไปมาก แล้วเรียงตามพจนานุกรม) กุญแจทั้งสี่คือ:
| กุญแจ | ไบต์ที่เข้ารหัส | ลำดับ |
|---|---|---|
nonce |
6 | 1 |
qub_id |
7 | 2 |
version |
8 | 3 |
ciphertext |
11 | 4 |
ดังนั้นไบต์แรกของ CBOR OuterWrapper คือเฮดเดอร์ map ความยาวกำหนดได้สำหรับ map 4 รายการ (0xA4)
13.4 การผูก AAD เข้ากับ qub_id
ตัวห่อหุ้มผูก qub_id เป็นข้อมูลรับรองความถูกต้องเพิ่มเติมของ AEAD นี่คือการป้องกันเชิงโครงสร้างที่สำคัญต่อการโจมตีสามประเภท:
| การโจมตี | การป้องกัน |
|---|---|
ย้ายไซเฟอร์เท็กซ์ภายใต้ฟิลด์ qub_id ที่แตกต่างในตัวห่อหุ้ม |
AAD ไม่ตรงกัน → การรับรองความถูกต้อง AEAD ล้มเหลว |
| ผสม URL fragment ของ qub A กับไบต์พื้นที่เก็บถาวรของ qub B | AAD ไม่ตรงกัน → การรับรองความถูกต้อง AEAD ล้มเหลว |
ดัดแปลงฟิลด์ qub_id ของตัวห่อหุ้มหลังการอัปโหลด |
AAD ไม่ตรงกัน → การรับรองความถูกต้อง AEAD ล้มเหลว |
การพก qub_id ในข้อความธรรมดาของตัวห่อหุ้มไม่ลดภูมิคุ้มกันการระบุอย่างมีนัยสำคัญ — qub_id เองเป็นแฮช SHA3-256 ของพรีอิมเมจ §4.1 โดยไม่มีพรีอิมเมจที่สามารถกู้คืนจากไดเจสต์ได้ และผู้ระบุที่เก็บเกี่ยวไบต์ตัวห่อหุ้มไว้แล้วจะไม่เรียนรู้อะไรจาก qub_id ที่มองเห็นได้ที่พวกเขาไม่สามารถอนุมานได้จากการมีอยู่ของการอัปโหลดเอง
13.5 อัลกอริทึมห่อและแกะ
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
การยุบโหมดล้มเหลว K ผิด nonce ผิด AAD ไม่ตรงกัน และไซเฟอร์เท็กซ์ที่ถูกดัดแปลง ทั้งหมดสร้างข้อผิดพลาด DECRYPT_FAILED เดียวกัน นี่เป็นคุณสมบัติ AEAD โดยเจตนา: การแยกแยะโหมดล้มเหลวจะสร้างช่องด้านข้างที่ผู้โจมตีระยะไกลสามารถสำรวจได้โดยส่งตัวห่อหุ้มที่ผิดรูปแบบและจับเวลาการตอบสนอง การใช้งานอ้างอิงต้องยุบความล้มเหลว AEAD ทั้งหมดเป็นรูปแบบข้อผิดพลาดเดียว
13.6 วัสดุกุญแจและการแจกจ่าย
กุญแจห่อหุ้ม K เป็นค่าสุ่มสม่ำเสมอ 256 บิตที่สร้างต่อ qub โดย CSPRNG การใช้งานอ้างอิงดึงจาก:
- ผู้สร้าง WASM:
getrandom(WebCrypto ภายใต้แบ็กเอนด์wasm_js) - ผู้เรียก API ผนึกฝั่งเซิร์ฟเวอร์: CSPRNG ในเครื่องของตน โดยผู้เรียกเป็นผู้จัดหาและเก็บรักษา
Kในรูปwrapper_key_b64urlWorker ใช้Kในหน่วยความจำสำหรับตัวห่อหุ้มแต่ต้องไม่คงเก็บไว้ สิ่งนี้ทำให้การลองซ้ำแบบ idempotent สามารถกู้คืนการตอบสนองที่ถูกกลบข้อมูลได้โดยใช้ความสามารถที่ผู้เรียกเก็บรักษาไว้ แทนที่จะพึ่งพาความลับที่เซิร์ฟเวอร์สร้างแบบยิงครั้งเดียว
การแจกจ่าย: K ต้องถูกเข้ารหัสเป็น base64 ที่ปลอดภัยสำหรับ URL (RFC 4648 §5 ไม่มี padding) และต่อท้ายลิงก์ส่งเป็นองค์ประกอบ fragment:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
Fragment ไม่ถูกส่งไปยังเซิร์ฟเวอร์ใด ๆ โดยเบราว์เซอร์ที่สอดคล้อง ช่องทางการกู้คืน (ดัชนีประวัติฝั่งเซิร์ฟเวอร์ การส่งอีเมลอัตโนมัติแบบเลือกได้) ที่คงลิงก์ส่งทั้งหมด — รวมถึง fragment — เกินกว่าอุปกรณ์ของผู้ใช้เป็นการแลกเปลี่ยนที่ชัดเจนกับท่าทีทำลายการเข้ารหัสเริ่มต้นและต้องมีการยินยอมที่ชัดเจนของผู้ใช้
การสูญหายของ Fragment หากผู้ใช้สูญเสีย URL fragment และไม่มีช่องทางการกู้คืน qub ไม่สามารถอ่านได้ นี่คือการแลกเปลี่ยนที่สำคัญของการออกแบบและต้องเปิดเผยให้ผู้ใช้ทราบในเวลาผนึก MVP เสริมการเปิดเผยในเวลาผนึกด้วยข้อความ "บันทึก URL นี้" ที่ชัดเจนและช่องทางการกู้คืนอีเมลที่ตรวจสอบแล้วสำหรับผู้ใช้ที่เลือกรับ
13.7 นอกขอบเขตของส่วนนี้
- การลงนามผู้เขียน (§9) ไม่เปลี่ยนแปลง: ลายเซ็นถูกคำนวณภายใน
QubEnvelopeภายในและกู้คืนหลังจากแกะ → ถอดรหัส tlock → แยกวิเคราะห์ CBOR - qub ส่วนตัวที่เข้ารหัสผู้รับ (ฟีเจอร์ที่สงวนไว้สำหรับระยะที่ 2 ยังไม่ได้ระบุ) ประกอบทับตัวห่อหุ้มนี้เป็นชั้นความลับที่สอง; ทั้งสองชั้นสามารถทำงานพร้อมกันได้
- สัญญา (§6, content_type
0x03) ถูกห่อหุ้มเหมือน qub ข้อความ; ตัวห่อหุ้มมองไม่เห็นไบต์ของประเภทเนื้อหาภายใน
13.8 qub สาธารณะ (การละเว้นตัวห่อหุ้ม)
ตัวห่อหุ้มภายนอกเป็น ตัวเลือกที่ชั้นการส่ง ผู้สร้างอาจผนึก qub เป็นแบบ สาธารณะ ซึ่งในกรณีนี้ SealedQubCbor มาตรฐานจะถูกเขียนลงพื้นที่เก็บถาวร โดยตรง โดยไม่มีชั้น OuterWrapper และไม่มีกุญแจ K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
qub สาธารณะเป็นแบบ ล็อกเวลาแต่ไม่จำกัดด้วยลิงก์: ยังคงอ่านไม่ได้จนกว่ารอบ drand จะเผยแพร่ (ชั้น tlock ไม่เปลี่ยนแปลง) แต่หลังปลดล็อก ใครก็ตามที่มี arweave_tx_id สามารถถอดรหัสได้ — ไม่ต้องใช้ URL fragment เพราะไม่มี K นี่คือการแลกเปลี่ยนโดยเจตนาสำหรับพื้นผิวที่เซิร์ฟเวอร์ต้องขับเคลื่อน: อีเมลแจ้งเตือนการเปิดเผย, การฝังจากบุคคลที่สาม และ SEO หลังการเปิดเผยที่สมบูรณ์ยิ่งขึ้น ล้วนต้องการลิงก์ที่ใช้งานได้โดยไม่มีความลับที่เซิร์ฟเวอร์ไม่เคยถือครอง (§13.6)
ผลที่ผู้ผลิตต้องคำนึงถึง:
- ไม่มีภูมิคุ้มกันการระบุ qub สาธารณะสละคุณสมบัติภูมิคุ้มกันการระบุของ §13.1 โดยโครงสร้าง บริการอัปโหลดอ้างอิงประทับแท็กพื้นที่เก็บถาวร
Visibility: publicลงบนพวกมัน (และเฉพาะพวกมัน) เพื่อให้สามารถค้นพบได้โดยเจตนา; qub ส่วนตัวไม่มีแท็กดังกล่าวและคงความไม่สามารถแยกแยะได้ในเชิงไบต์ไว้ - title ข้อความธรรมดาถูกเปิดเผยในเวลาผนึก ฟิลด์
titleของ §3.2 เป็นข้อความธรรมดาภายในSealedQubCborภายใต้ตัวห่อหุ้มมันถูกซ่อนไว้จนกว่าผู้ชมจะให้K; โดยไม่มีตัวห่อหุ้มมันอ่านได้ทั่วโลกบนพื้นที่เก็บถาวร ตั้งแต่ขณะอัปโหลด ก่อนการปลดล็อก แอปผู้สร้างที่สอดคล้องต้องเปิดเผยสิ่งนี้ในเวลาผนึก - การตรวจจับเป็นเชิงโครงสร้าง ผู้ชม/การฝังที่สอดคล้องแยกแยะรูปร่างทั้งสองด้วยการแยกวิเคราะห์: ไบต์ที่แยกวิเคราะห์เป็น
OuterWrapperเดินเส้นทางแกะด้วยK; ไบต์ที่แยกวิเคราะห์เป็นSealedQubCborเปล่าจะถูกยอมรับโดยตรง ไม่ต้องมีแฟล็กบนสาย และqub_idไม่ผูกความเป็นสาธารณะ — เนื้อหาเดียวกันเหมือนกันในเชิงไบต์ที่ชั้นSealedQubไม่ว่าจะผนึกเป็นสาธารณะหรือส่วนตัว
ส่วนตัว (ห่อหุ้ม) ยังคงเป็นค่าเริ่มต้น; สาธารณะเป็นทางเลือกของผู้สร้างต่อ qub อย่างชัดเจน
14. เวกเตอร์ทดสอบ
14.1 การหาค่า 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
การใช้งานต้องสร้างค่า body_hash และ qub_id ที่เหมือนกันสำหรับอินพุตนี้ เวกเตอร์ทดสอบนี้ควรเป็น unit test แรกที่เขียน ค่ามาตรฐานด้านบนถูกคำนวณโดยการใช้งานอ้างอิงและต้องตรงกันแบบบิตต่อบิต เลย์เอาต์พรีอิมเมจในอดีต (ก่อนเปิดตัว — ไม่มี qub ที่ใช้งานจริงพึ่งพาค่าเหล่านี้): qub_id V1.0 แบบ 92 ไบต์ คือ 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0; qub_id V1.1 แบบ 100 ไบต์ (หลังรวม outcome_at_or_zero) คือ b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed V1.2 รวม drand_round เข้าไปและเลื่อนตัวแยกโดเมนเป็น QUB_ID_V2
14.2 การแมปรอบปลดล็อก
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 การไปกลับของ CBOR มาตรฐาน
การใช้งานต้องตรวจสอบว่า serialize(parse(serialize(qub))) == serialize(qub) สำหรับอินพุตที่ถูกต้องทั้งหมด นี่คือการทดสอบคุณสมบัติ ไม่ใช่เวกเตอร์เดียว
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)
ไบต์ CBOR มาตรฐานและ SHA3-256 body_hash ถูกคำนวณโดยการใช้งานอ้างอิง การใช้งานต้องสร้าง CBOR ที่เป็นไบต์เหมือนกันสำหรับอินพุตนี้
การใช้งานต้องตรวจสอบด้วยว่า serialize(parse(serialize(pact))) == serialize(pact) สำหรับอินพุต PactTerms ที่ถูกต้องทั้งหมด (การทดสอบคุณสมบัติ)
14.5 เวกเตอร์ข้ามภาษาของตัวห่อหุ้มภายนอก
ตัวห่อหุ้มภายนอก (§13) มี fixture มาตรฐานแยกที่ crates/qub-core/tests/vectors/wrapper_v1.json แต่ละกรณีกำหนด tuple (key, nonce, qub_id, sealed_cbor) เป็นอินพุตเลขฐานสิบหกทึบแสงและยืนยันเอาต์พุต expected_wrapper_hex ที่เฉพาะเจาะจง การใช้งานอ้างอิงทั้งสองบริโภคไฟล์ JSON เดียวกัน:
- 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)
ปัจจุบัน fixture ตรึงสามกรณี:
| กรณี | การครอบคลุม |
|---|---|
basic-text-public |
รูปร่าง SealedQub ที่สมจริงน้อยที่สุด; ไม่มีฟิลด์ที่เลือกได้ สร้างรูปร่างตัวห่อหุ้มมาตรฐานสำหรับ qub v1.0 ทั่วไป |
with-recipient-pubkey |
SealedQub ที่ตั้งค่า recipient_pubkey (เส้นทาง Phase 2) ชุดกุญแจ CBOR ภายในที่แตกต่าง qub_id ที่แตกต่าง |
longer-body |
body ~4 KiB — ออกกำลังกายคำนำหน้าความยาว CBOR หลายไบต์ทั้งในซองภายในและไซเฟอร์เท็กซ์ภายนอก |
การใช้งานต้องสร้าง expected_wrapper_hex ที่เป็นไบต์เหมือนกันสำหรับอินพุตที่บันทึกไว้ การสร้าง fixture ใหม่ต้องใช้ QUB_REGEN_VECTORS=1 cargo test -p qub-core --test wrapper_vectors และสงวนไว้สำหรับการเปลี่ยนแปลงรูปแบบโดยเจตนา
15. การกำกับดูแลโปรไฟล์การเข้ารหัส (อนาคต)
ส่วนนี้เป็นข้อมูลสำหรับ v1 และกลายเป็นเชิงบรรทัดฐานในครั้งแรกที่อัลกอริทึมที่สองเข้าสู่หนึ่งในองค์ประกอบพื้นฐานทางการเข้ารหัสของ qub
15.1 ท่าทีปัจจุบัน
โปรโตคอล v1 ผูกอัลกอริทึมหนึ่งตัวต่อหนึ่งองค์ประกอบพื้นฐานพอดี:
- ลายเซ็น: ML-DSA-65 (
sig_alg = 0x01; กุญแจสาธารณะ 1952 ไบต์ ลายเซ็น 3309 ไบต์) และไม่ลงนาม (sig_alg = 0x00) ทะเบียน §9.2 ไม่ได้กำหนดค่าอื่นใด; ตัวตรวจสอบ v1 ต้องปฏิเสธทุกsig_algที่อยู่นอก{0x00, 0x01}รายการ Ed25519 ในอนาคตได้รับการคาดการณ์ไว้ (§15.3) แต่ยังไม่ได้จัดสรรใน v1 - ล็อกเวลา: drand quicknet เท่านั้น — chain hash, กุญแจสาธารณะ, เวลา genesis และคาบเวลาเป็นพารามิเตอร์เครือข่ายคงที่ที่ถูกพกพาโดย
DrandTimelockProvider::quicknet()อ้างอิง (crates/qub-core/src/tlock.rs) และconfig/drand-endpoints.json - ตัวห่อหุ้มภายนอก: AES-256-GCM v1 เท่านั้น (§13)
ตัวตรวจสอบในปัจจุบัน hard-code ความยาวกุญแจและลายเซ็นต่อองค์ประกอบพื้นฐาน ไม่มีพื้นผิวความคล่องตัวเปิดเผยโดยรูปแบบการสื่อสาร
15.2 รูปร่างที่ตั้งใจ
เมื่ออัลกอริทึมที่สองเข้าสู่โปรโตคอล ตัวตรวจสอบจะถูกกำหนดค่าสำหรับ CryptoProfile ที่มีชื่อ (เช่น ExqubV1) ที่แสดงรายชุดค่าที่อนุญาตที่แม่นยำต่อองค์ประกอบพื้นฐาน — sig_algs, drand chain, เวอร์ชันตัวห่อหุ้ม, ประเภทเนื้อหา โปรไฟล์ถูกแก้ไขที่เวลาตรวจสอบ ไม่เคยถูกเจรจาในแบนด์ ค่าใดก็ตามที่อยู่นอกโปรไฟล์ที่ใช้งานจะถูกปฏิเสธ
สิ่งนี้รับประกันว่าการเพิ่ม ML-DSA-87 หรือการเปิดใช้งาน Ed25519 ไม่สามารถลดความเข้มแข็งของการตั้งค่าตัวตรวจสอบที่มีอยู่ย้อนหลังได้: ตัวตรวจสอบ v1 ยังคงเป็นตัวตรวจสอบ v1 แม้หลังจากที่โปรไฟล์ v2 ถูกเผยแพร่
15.3 เงื่อนไขการกระตุ้น
ยกระดับ §15 เป็นสถานะเชิงบรรทัดฐานเมื่อมีการเสนอข้อใดข้อหนึ่งต่อไปนี้:
- ไบต์
sig_algที่สอง (การเปิดใช้งาน Ed25519, ML-DSA-87 หรือรายการใหม่ใด ๆ ในทะเบียน §9) - drand chain ที่สองในการใช้งานจริง
- เวอร์ชันตัวห่อหุ้มภายนอกที่สอง
จนกว่าจะถึงเวลานั้น §15 เป็นตัวยึดที่กำหนดรูปร่างการย้ายเพื่อให้ PR ในอนาคตลงบนเป้าหมายที่รู้จักแทนที่จะอภิปรายซ้ำเกี่ยวกับพื้นผิวการเจรจาตั้งแต่ต้น
16. บันทึกความโปร่งใสและระดับความคงทน (การออกแบบ — ตรวจทานเสร็จสมบูรณ์)
สถานะ ส่วนนี้เป็น ข้อกำหนดการออกแบบ รูปแบบการสื่อสาร การแฮช และแบบจำลองความเชื่อถือด้านล่างเป็นเชิงบรรทัดฐาน สำหรับการใช้งาน แต่ยังไม่มีโค้ดบันทึกความโปร่งใสที่จัดส่งแล้ว การตรวจทานภายนอก W5 เสร็จสมบูรณ์แล้ว: §16.15 บันทึกการตัดสินใจที่ได้ข้อสรุปและ ข้อจำกัดการเปิดตัวที่ผูกพัน ที่ได้จากการตรวจทานนั้น การใช้งานอาจดำเนินการได้ภายใต้ข้อจำกัดเหล่านั้น §16 ยังคงมองไปข้างหน้าในความหมายเดียวกับ §15 — มันตรึงเป้าหมายไว้เพื่อให้การใช้งานลงบนการออกแบบที่ได้ข้อสรุปแล้วแทนที่จะหาค่าแบบจำลองความเชื่อถือซ้ำในการตรวจทานโค้ด มันเป็นการเพิ่มเติมล้วน ๆ — qub ที่มีอยู่ทุกชิ้นยังคงมีธุรกรรม Arweave เฉพาะตัว และ ไม่มีการเปลี่ยนแปลงรูปแบบการสื่อสาร
SealedQub/QubEnvelope
16.1 เหตุผลและระดับความคงทน
ปัจจุบันความคงทนของ qub และข้อผูกพันเชิงเวลาของมันต่างก็วางอยู่บนธุรกรรม Arweave ต่อ qub เพียงหนึ่งเดียว (§11) สิ่งนี้ผูกหน่วงเวลาการผนึกเข้ากับการเสร็จสิ้นของ Arweave ทำให้การอัปโหลดต่อ qub กลายเป็นเพดานต้นทุนผลิตภัณฑ์ (ARWEAVE_DAILY_CEILING) และไม่ให้ การจัดลำดับ ที่ตรวจจับการดัดแปลงได้ระหว่าง qub ต่าง ๆ บันทึกความโปร่งใสเพิ่มสองชั้นใต้และรอบ ๆ ระดับเดียวนั้น:
| ระดับ | ชื่อ | การรับประกัน | เมื่อใด |
|---|---|---|---|
| T1 | การตอบรับแบบซิงโครนัส R2-first | พื้นความคงทน — ไบต์ที่ผนึกถูกเขียนลงที่จัดเก็บที่คงทนก่อนที่การผนึกจะคืนค่า (< 300 ms p95) |
ทุก qub แบบซิงโครนัส (§16.10) |
| T2 | การรวม qub เข้าบันทึกความโปร่งใสแบบเป็นชุด | ข้อผูกพันที่เพิ่มต่อท้ายเท่านั้น ตรวจจับการดัดแปลงได้ และมีผลทั่วถึง + การจัดลำดับสมบูรณ์ ยึดกับ Arweave | ทุก qub แบบเลื่อน + เป็นชุด (§16.5–16.7) |
| T3 | ความถาวรบน Arweave ต่อ qub | ธุรกรรม Arweave เฉพาะตัวสำหรับ qub | การขายเพิ่มแบบจ่ายเงิน และทางสำรองเมื่อ Arweave ไม่พร้อมใช้งาน (§16.8) |
T2 ทำให้ Arweave ต่อ qub กลายเป็น ทางเลือก (T3) แทนที่จะเป็นเส้นทางความคงทนเดียว ARWEAVE_DAILY_CEILING ถูกปลดจากการเป็นเพดานผลิตภัณฑ์และลดบทบาทเป็นเบรกเกอร์ตัดวงจรบนกระเป๋าเงินจุดยึดเฉพาะเท่านั้น (§16.7); การผนึกของผู้ใช้ไม่เคยถูกปฏิเสธเพราะเกินค่านั้น
ความซื่อตรงด้านความคงทน (ได้ข้อสรุป — §16.15 Q6) ความคงทน ไม่ ถดถอย: การเขียน T1 R2 เป็นแบบซิงโครนัสและเขียนครั้งเดียว ดังนั้น qub ระดับฟรีที่ไม่ได้ซื้อ T3 จึงคงทนอย่างสมบูรณ์ในขณะที่การผนึกคืนค่า สิ่งที่หยาบขึ้นคือ เวลาข้อผูกพันขอบเขตบน ที่พิสูจน์ได้: สำหรับ qub ฟรี มันกลายเป็น เวลาบล็อกของจุดยึด แทนที่จะเป็นเวลาบล็อกของธุรกรรมต่อ qub ที่ปริมาณต่ำ — สถานะช่วงเปิดตัวแรกและช่วงนอกพีคที่เป็นจริง — จังหวะรายวันเต็มเป็นพื้น ทั่วไป ไม่ใช่กรณีขอบที่หายาก การวางกรอบผลิตภัณฑ์จึงเป็นขอบเขตบนที่ ไม่มีคำมั่นด้านหน่วงเวลาเชิงตัวเลข — "ผนึกและคงทนแล้วในขณะนี้; ตราเวลาสาธารณะอิสระจะถูกเพิ่มที่จุดยึดบันทึกถัดไป (โดยทั่วไปรายวัน)" — และ หลักฐานข้อผูกพันระดับชั่วโมงที่แม่นยำเป็นคุณสมบัติ T3 แบบจ่ายเงิน เปิดเผยที่พื้นผิวเปรียบเทียบระดับและในข้อกำหนด (§16.11, §16.15 Q6) ขอบเขตเวลาใด ๆ เป็น SLO ภายในเท่านั้น ไม่เคยเป็น SLA ที่ทำการตลาด
16.2 โครงสร้าง LogLeaf (สองรูปทรงที่ผูกพัน)
รายการบันทึกคือ LogLeaf เข้ารหัสเป็น CBOR มาตรฐานที่เขียนด้วยมือภายใต้โปรไฟล์ §3.1 (ความยาวกำหนดได้ ไม่มี tag ไม่มี float จำนวนเต็มรูปแบบสั้นที่สุด ข้อความ NFC ฟิลด์เลือกได้ถูกละเว้นเมื่อไม่มี กุญแจเรียงตามความยาวไบต์ที่เข้ารหัสจากน้อยไปมากแล้วเรียงตามไบต์) ตัวป้องกันมาตรฐาน parse → re-encode → compare ของ §3.1 ถูกใช้ บนเส้นทางเข้ารหัสก่อนการแฮช (ไม่ใช่เฉพาะบนการถอดรหัส) ดังนั้นการใช้งานสองตัวจึงไม่สามารถขัดแย้งกันเรื่องไบต์ของลีฟผ่านความแตกต่างของความกว้างจำนวนเต็มหรือลำดับกุญแจได้ จำนวนเต็มทั้งหมดเป็น u8 / u64 / i64; ไดเจสต์ทั้งหมดเป็นสตริงไบต์ 32 ไบต์ (bstr[32]) id ธุรกรรม Arweave ที่จัดเก็บเป็นไดเจสต์ SHA-256 ดิบ 32 ไบต์ที่พกพาเป็น bstr[32] ไม่เคย เป็นสตริงข้อความ base64url (ตรงกับ §3.3)
ลีฟมี สองรูปทรงที่เลือกด้วยไบต์ kind เพราะบนเส้นทางอัปโหลดเริ่มต้น Worker มองไม่เห็นไบต์: POST /api/v1/upload รับเฉพาะ qub_id และ unlock_at เป็น คำกล่าวอ้างของไคลเอนต์ที่ไม่น่าเชื่อถือ — body_hash, drand_round, created_at และ drand_chain_version ล้วนถูกผนึกอยู่ภายในตัวห่อหุ้มภายนอก §13 ซึ่ง Worker ไม่เคยถือกุญแจ มีเพียงเส้นทางผนึกฝั่งเซิร์ฟเวอร์ (POST /api/v1/seal) ที่หาค่า body_hash / drand_round จากข้อความธรรมดา ลีฟรูปทรงเดียวที่พก body_hash + drand_round จึงจะผูกพันค่าที่ผู้ดำเนินการไม่เคยตรวจสอบสำหรับ qub จริงส่วนใหญ่ การแยกทำให้ทุกค่าที่ผูกพันซื่อตรง:
| กุญแจ | ไบต์เข้ารหัส | ชนิด | การมีอยู่ | ความหมาย |
|---|---|---|---|---|
seq |
4 | u64 |
จำเป็น | ดัชนีลีฟทั่วโลกฐาน 0; ตำแหน่งที่หลักฐานการรวมผูกพันไว้ |
kind |
5 | u8 |
จำเป็น | 0x01 รับรอง (ผนึกฝั่งเซิร์ฟเวอร์) หรือ 0x02 กล่าวอ้าง (ผนึกฝั่งไคลเอนต์ / อัปโหลดที่มองไม่เห็นไบต์) |
ref |
4 | bstr[32] |
จำเป็น | id อ้างอิงลีฟ รับรอง → qub_id ดิบ กล่าวอ้าง → id ที่ ปิดบัง SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1) |
chash |
6 | bstr[32] |
จำเป็น | ที่อยู่เนื้อหา SHA3-256(stored_bytes) — ผูกเนื้อหาหนึ่งเดียวที่ Worker สามารถคำนวณได้อย่างซื่อตรงเสมอบนทั้งสองเส้นทาง |
unlock_at |
10 | i64 |
จำเป็น | คัดลอก (รับรอง) หรือกล่าวอ้าง (กล่าวอ้าง); ตรวจสอบ > 0 ก่อนเข้าสู่ลีฟ |
received_at |
12 | i64 |
จำเป็น | เวลานาฬิกาผนัง Worker ที่ R2-ack ไม่ใช่หลักฐานเชิงพยาน (ผู้ดำเนินการกล่าวอ้าง; §16.6) มีไว้เพื่ออธิบายตัวเอง ไม่เคยเป็นหลักฐาน ตรวจสอบ > 0 |
body_hash |
10 | bstr[32] |
kind=0x01 เท่านั้น |
ละเว้นบน 0x02 — Worker ไม่มีภายใต้ §13 |
drand_round |
12 | u64 |
kind=0x01 เท่านั้น |
ละเว้นบน 0x02 |
ลีฟ kind=0x02 จงใจไม่ผูกพันทั้ง body_hash และ drand_round: มันรับรอง ข้อผูกพันและการจัดลำดับของไซเฟอร์เท็กซ์ทึบแสงที่ที่อยู่เนื้อหา chash โดยอ้าง qub_id และ unlock_at — ไม่ใช่ข้อความธรรมดาหรือรอบของมัน ขาข้อความธรรมดา/รอบสำหรับ qub ที่กล่าวอ้างมาจากการตรวจสอบ .qub-bundle §11 ที่มีอยู่ ไม่ใช่จากบันทึก (§16.11) drand_chain_version ไม่อยู่ในลีฟ (มันอยู่ภายในตัวห่อหุ้มบนเส้นทางเริ่มต้น); ความละเอียดของเชนอยู่บนจุดยึด (§16.7) ระเบียบวินัยของตัวเข้ารหัส: ปฏิเสธ ref หรือ chash ที่เป็นศูนย์ทั้งหมด และปฏิเสธ unlock_at / received_at ที่ไม่เป็นบวก สะท้อนตัวป้องกันค่าเซนติเนล outcome_at > 0 ใน cbor.rs
16.2.1 การปิดบัง qub ส่วนตัว
บันทึกต้องไม่กลายเป็นออราเคิลแจกแจงที่ตัวห่อหุ้มภายนอก §13 มีไว้เพื่อป้องกัน (§13.1) สำหรับ qub ส่วนตัว (ที่ห่อหุ้ม) ลีฟ asserted ผูกพัน ตัวระบุที่ปิดบัง SHA3-256(qub_id ‖ log_blind_secret) โดยที่ log_blind_secret เป็นความลับที่เซิร์ฟเวอร์ถือไว้ และละเว้น body_hash บุคคลที่สามไม่สามารถผูกลีฟดังกล่าวเข้ากับ qub_id ที่เฉพาะเจาะจงได้; ผู้ถือ qub ซึ่งมี URL ส่งและจึงมี qub_id สามารถคำนวณการปิดบังใหม่เพื่อยืนยันการรวมของตัวเอง qub สาธารณะ (แจกแจงได้อยู่แล้ว มี tag Arweave Visibility: public ตาม §13.8 อยู่แล้ว) ผูกพัน qub_id ดิบ นี่คือจุดเดียวที่ความสามารถในการตรวจสอบแบบยืนเดี่ยวจงใจยอมให้กับค่าคงที่ความเป็นส่วนตัวที่รับน้ำหนัก; การผูกแบบยืนเดี่ยวสำหรับ qub ส่วนตัวคือ chash (§16.9)
การเก็บรักษา log_blind_secret (ได้ข้อสรุป — §16.15 Q4) การปิดบังปกป้อง การไม่สามารถเชื่อมโยงลีฟ ไม่ใช่ความลับของข้อความธรรมดา (ตัวห่อหุ้ม §13 ถือสิ่งนั้นไว้อย่างอิสระ) เมื่อ log_blind_secret ถูกบุกรุก สำหรับ qub_id ใด ๆ ที่ฝ่ายตรงข้ามถือหรือสร้างใหม่ได้อยู่แล้ว (ทุก qub ที่มี bundle/URL ของมัน รวมถึง qub_id สาธารณะหรือเอนโทรปีต่ำใด ๆ) มันคำนวณ ref ของลีฟใหม่ใน การแฮชเดียว และเชื่อมโยงมัน — นี่คือการเชื่อมโยงโดยตรงของประชากรที่รู้จัก ไม่ใช่การลองทุกค่าบนพื้นที่ที่ไม่รู้จัก จัดประเภท log_blind_secret เป็นความลับระดับความสัมพันธ์/Sybil ในชั้นการเก็บรักษาเดียวกับความลับเซิร์ฟเวอร์อื่น ๆ และหมุนไปข้างหน้าเท่านั้น (การหมุนปิดบังลีฟในอนาคตใหม่; มันไม่สามารถยกเลิกการเชื่อมโยงลีฟที่ยึดแล้วย้อนหลังได้)
16.3 การแฮชลีฟและโหนด
การแฮชแบบแยกโดเมนตาม RFC 6962 §2.1 โดยแทนที่ SHA-256 ด้วย 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
ไบต์คำนำหน้าโดเมน 0x02 (เชนรายการ §16.4) และ 0x03 (แฮช STH §16.6) ถูกสงวนและแยกออกจากเหล่านี้ พวกมันเป็นไบต์เดี่ยวจึงไม่สามารถชนกับตัวแยกโดเมน ASCII 10 ไบต์ที่มีอยู่ได้ (QUB_ID_V2 เป็นต้น) ต้นไม้คือต้นไม้ ไม่สมดุลแบบเต็มซ้าย ตาม RFC 6962 (แต่ละการแยกภายในที่กำลังสองสูงสุดที่น้อยกว่าจำนวนลีฟของต้นไม้ย่อยอย่างเคร่งครัด) ซึ่งทำให้หลักฐานการรวมและความสอดคล้องใช้อัลกอริทึมเส้นทางตรวจสอบเดียวกันได้ ข้อกำหนดอ้างอิงพกซูโดโค้ดการหาค่าซ้าย/ขวาอย่างชัดเจน และตรึง เวกเตอร์ทดสอบที่ไม่ใช่กำลังสอง (5 ลีฟ) ดังนั้นกรณีการเลื่อนขอบขวา — ซึ่งเวกเตอร์ 4 ลีฟซ่อนไว้ — จึงถูกใช้งาน
16.4 การเชื่อมโยงแฮช (ภายใน)
LogDO ดูแลเชนรายการภายในเพื่อความสอดคล้องเมื่อแครชเท่านั้น มัน ไม่เคยถูกเผยแพร่และไม่เคยหันสู่ตัวตรวจสอบ:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
อำนาจเพิ่มต่อท้ายเท่านั้นที่เผยแพร่คือ รากเมอร์เคิลสะสม + จุดยึดของมัน (§16.5–16.6) ไม่เคยเป็นลำดับดิบที่ผู้ดำเนินการบังเอิญให้บริการลีฟ: เชนคำนวณใหม่สำหรับลำดับใด ๆ ที่ให้บริการ ดังนั้นมีเพียงรากที่ยึดแล้วเท่านั้นที่ตรึงตำแหน่งมาตรฐาน
16.5 ต้นไม้เมอร์เคิลสะสมและการรวมเป็นชุด
มี ต้นไม้ RFC 6962 ที่เติบโตตลอดเวลาหนึ่งต้น เหนือลีฟทั้งหมดตามลำดับ seq — ไม่ใช่ต้นไม้แยกต่อชุด (การสร้างแบบเชนลีฟพ่วงต่อชุดถูกปฏิเสธ: มันไม่ใช่ความสัมพันธ์คำนำหน้าที่แท้จริง ดังนั้น "หลักฐานความสอดคล้อง" ของมันจึงไม่สมเหตุสมผล) ต้นไม้สะสมให้หลักฐานความสอดคล้อง RFC 9162 ของแท้และทำให้จุดยึดล่าสุดเพียงจุดเดียวสามารถพิสูจน์การรวมสำหรับ qub เก่ากว่าใด ๆ ได้
Durable Object LogDO เป็น ผู้เขียนเดียว (blockConcurrencyWhile สะท้อน QuotaDO / EntitlementDO) — การเพิ่มต่อท้ายบันทึกที่ใช้ร่วมกันเป็น read-modify-write บนสถานะที่ใช้ร่วมกัน จึงต้องผ่าน DO ไม่เคยผ่าน KV มันแคชชายขอบขวาของต้นไม้ (O(log n) แฮช) ดังนั้นการปิดชุดจึงเป็น O(batch) ชุด คือเซตของลีฟที่ยึดด้วยกัน; ตัวกระตุ้นของมันกำหนดค่าได้ ไม่ถูกตรึงโดยโปรโตคอล: การก้าวหน้าของ tree_size อย่างน้อย LOG_BATCH_MAX_LEAVES (ค่าเริ่มต้น 4096) หรืออายุถึงจังหวะการยึด หรือการล้างที่ถูกบังคับเมื่อการผนึก T3 แบบจ่ายเงินลงมา root_i คือ Merkle Tree Hash สะสมเหนือลีฟ 0 .. tree_size_i
16.6 Signed Tree Head ผ่านจุดยึด Arweave
ธุรกรรมจุดยึด Arweave คือ Signed Tree Head และ แทนที่ ลายเซ็นผู้ดำเนินการ สำหรับหัวต้นไม้นั้นเอง: จุดยึดรายวันไม่ต้องการกุญแจ qub เพราะ owner ของธุรกรรม Arweave คือลายเซ็น แนวคิดคูเมืองยังคงอยู่ — สารตั้งต้นที่เปลี่ยนแปลงไม่ได้ ไม่ใช่ความลับที่ qub ถือ คือสิ่งที่รับน้ำหนักสำหรับรากที่ยึดแล้ว
มีกุญแจลงนาม qub ที่อุ่นอยู่ หนึ่งเดียว ในการออกแบบ และมันถูก ตรึง: กุญแจใบเสร็จ ต่อการผนึก (§16.10) กุญแจสาธารณะของมันถูกผูกพันใน LogProfile (แจกจ่ายพร้อมตัวตรวจสอบ) และ ลงนามไขว้โดย anchor_owner ดังนั้นตัวตรวจสอบจึงตรวจสอบใบเสร็จกับ รากที่ตรึงเดียวกัน กับจุดยึด นี่คือการแก้ §16.15 Q2 — กุญแจใบเสร็จที่ ไม่ตรึง ผู้ดำเนินการหมุนได้ จะปฏิเสธได้ (ผู้ดำเนินการสามารถปฏิเสธว่ากุญแจเป็นของพวกเขา) ซึ่งจะทำให้คุณค่าด้านความรับผิดชอบของใบเสร็จต่อฝ่ายตรงข้ามระดับผู้ดำเนินการที่ใบเสร็จมีไว้เพื่อขัดขวางเป็นโมฆะ ดังนั้น: qub ถือ กุญแจลงนามบันทึกที่ ไม่ตรึง ไม่มี; กุญแจใบเสร็จถูกตรึงและลงนามไขว้โดย anchor_owner
SignedTreeHead เป็น CBOR มาตรฐาน (กุญแจตามความยาวเข้ารหัส): size:u64, root:bstr[32], batch:u64, prev:bstr[32] (sth_hash ก่อนหน้า; genesis = 32 ไบต์ศูนย์), log_id:bstr[32], first_seq:u64, anchored_at:i64 แฮชของมันคือ sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead))
รากความเชื่อถือที่ตรึง log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address) ตัวตรวจสอบที่สอดคล้องต้องกำหนด anchor_tx.owner == LogProfile.anchor_owner โดยที่ anchor_owner (และกุญแจสาธารณะกุญแจใบเสร็จ) ถูกอบเข้าใน qub_core เป็น LogProfile — ควบคู่กับค่าคงที่ quicknet ที่อยู่ใน DrandTimelockProvider::quicknet() อยู่แล้ว — และแจกจ่ายพร้อมไบนารีตัวตรวจสอบ ตัวตรวจสอบต้องตรวจสอบ การผูก data → tx_id ของธุรกรรม Arweave ในเครื่อง ด้วย แทนที่จะเชื่อถือการตอบสนอง /raw/ ของเกตเวย์ สิ่งนี้ปิดช่องการให้ข้อมูลขัดแย้งของกระเป๋าเงินทุจริต: "ยึดบน Arweave แล้ว" ไม่มีความหมายจนกว่าตัวตรวจสอบจะตรึง ว่ากระเป๋าเงินใด
การหมุนเป็น ส่วนขยาย การกำกับดูแล §15 ไม่ใช่การนำกลับมาใช้ซ้ำ (ได้ข้อสรุป — §16.15 Q3) พื้นผิวโปรไฟล์ของ §15.2 ปัจจุบันแจกแจงเฉพาะ sig_algs / drand chains / เวอร์ชันตัวห่อหุ้ม / ประเภทเนื้อหา และตัวกระตุ้นของ §15.3 ไม่ระบุค่าเหล่านี้เลย — LogProfile / anchor_owner ยังไม่ อยู่ในพื้นผิวของ §15 การกำกับดูแลการหมุนจึงต้อง สร้าง: §15.3 ถูกขยาย (ด้านล่าง) เพื่อเพิ่มตัวกระตุ้น LogProfile และการหมุนคือการเพิ่ม LogProfile ที่ลงนามแล้วจัดส่งในการอัปเดตตัวตรวจสอบ การหมุนที่ วางแผนไว้ พกลายเซ็นไขว้ออก → เข้า; การหมุนที่ เกิดจากการบุกรุก ทำไม่ได้ (กุญแจที่ออกไม่น่าเชื่อถือ/ไม่พร้อมใช้งานในตอนนั้นพอดี) และตกกลับสู่การเพิ่มที่กำกับโดย §15 โดยการตรวจสอบฟอร์กของจุดยึดก่อนหน้า (ด้านล่าง) จำกัดความเสียหายในช่วงระหว่าง
หน้าต่างการให้ข้อมูลขัดแย้ง (พารามิเตอร์ความเชื่อถือชั้นหนึ่ง) ลีฟต้านการให้ข้อมูลขัดแย้งได้ก็ต่อเมื่อจุดยึดที่ครอบคลุมมันถูก Arweave-ยืนยันแล้ว หน้าต่างคือ received_at → การยืนยันจุดยึด (≤ จังหวะ + การเสร็จสิ้นของ Arweave) ภายในนั้นการรับประกันเดียวคือใบเสร็จการผนึกที่ตรึง (§16.10) และความสมบูรณ์เชิงปฏิบัติการของ qub สิ่งประดิษฐ์ความรับผิดชอบสามอย่างทำให้สิ่งนี้ซื่อตรงแทนที่จะโบกมือ (แบบจำลองพยานคือการแก้ §16.15 Q2):
- ใบเสร็จการผนึกที่ลงนามและตรึง — อนาล็อก SCT ที่คืนในการตอบสนองการอัปโหลด (§16.10) ลงนามโดยกุญแจใบเสร็จที่ตรึงและลงนามไขว้โดย
anchor_ownerลีฟที่ถูกทิ้งก่อนจุดยึดของมันทิ้งใบเสร็จที่ปฏิเสธไม่ได้ให้เหยื่อเผยแพร่ ปิดช่องการละเว้นแบบเงียบ - ระเบียบวิธีมอนิเตอร์ที่เผยแพร่ + การเดินเชน prev — เชน
prevของจุดยึดถูกเดินจากหัว → genesis; ฟอร์ก (จุดยึดสองจุดที่sizeเดียวกันแต่rootต่างกัน หรือprevที่ขาด) เป็นหลักฐานพฤติกรรมไม่ถูกต้องที่เผยแพร่ได้ การตรวจจับการให้ข้อมูลขัดแย้งเป็นคำมั่นเชิงปฏิบัติการที่ระบุไว้ ไม่ใช่สมมติฐานที่เงียบ - หัวที่เผยแพร่เองคู่ — หัวใหม่แต่ละหัว
{sth_hash, tree_size}ถูกโพสต์ไปยัง คลัง GitHub สาธารณะที่เพิ่มต่อท้ายเท่านั้นที่ qub เป็นเจ้าของ เฉพาะ (ขาการเผยแพร่เองที่ตรวจจับการดัดแปลงได้ที่รับน้ำหนัก) โดยมีโพสต์โซเชียลเป็นการยืนยันแบบเต็มที่ดีที่สุดเท่านั้น การโพสต์ที่ล้มเหลวต้องเรียกเตือน (ไม่ล้มเหลวแบบเงียบ)
ขอบเขตความซื่อตรง (ข้อจำกัดที่ผูกพัน) เพราะ qub ควบคุมพื้นผิวการโพสต์ทั้งสอง นี่คือ การเผยแพร่เอง ไม่ใช่การเป็นพยานโดยอิสระ ไม่มีพื้นผิวผลิตภัณฑ์ การตลาด หรือกฎหมายใดอ้างว่าบันทึก "ถูกเป็นพยานโดยอิสระ" ได้; การอ้างที่อนุญาตคือ การให้ข้อมูลขัดแย้งตรวจจับได้และทิ้งใบเสร็จที่ปฏิเสธไม่ได้ พยานบุคคลที่สามอิสระที่แท้จริงถูกเลื่อนไปยังการเพิ่มการกำกับดูแล §15 ในอนาคต
received_at เป็นค่าที่ผู้ดำเนินการกล่าวอ้าง และ ไม่มีการอ้างใดพึ่งพามันได้ — มันไม่เคยถูกแสดงเป็นหลักฐานหรือการยืนยันข้อพิพาทบนพื้นผิวผลิตภัณฑ์ / กฎหมาย / API / การเรนเดอร์หลักฐานใด ๆ เวลาบล็อก T ของจุดยึด Arweave เป็นตราเวลาที่ไม่ต้องเชื่อถือเพียงหนึ่งเดียว (ขอบเขตบนของ "บันทึกเมื่อ") การตรวจสอบความสมเหตุสมผลของมอนิเตอร์บน received_at ใด ๆ ต้องเทียบกับ T ไม่ใช่กับฟิลด์ STH anchored_at ที่ผู้ดำเนินการควบคุม; การตรวจสอบเช่นนั้นเป็นตัวป้องกันต่อบั๊กนาฬิกาของผู้ดำเนินการที่ซื่อสัตย์เท่านั้น ไม่ใช่ การควบคุมความรับผิดชอบต่อผู้ดำเนินการที่มุ่งร้าย (§16.15 Q5)
16.7 รูปแบบและจังหวะธุรกรรมจุดยึด
AnchorBundle คือ body ธุรกรรม Arweave แบบ CBOR มาตรฐาน เขียนผ่าน bundler §16.8: ver:u8, sth:bstr (ไบต์ SignedTreeHead มาตรฐาน), prev_anchor:bstr (id ธุรกรรมจุดยึดก่อนหน้าเป็นไบต์ดิบ; ละเว้นที่ genesis), chain_hash:tstr (drand chain ที่มีผลบังคับ — quicknet) และ สตรีมลีฟ-CBOR ของชุดตามลำดับ seq ดังนั้นจุดยึดจึงครบในตัว: มอนิเตอร์หาค่า root ใหม่จาก body โดยไม่พึ่งพา qub เลย (หากสตรีมลีฟใหญ่ขึ้นที่ปริมาณสูง การแก้ไขในอนาคตอาจผูกพันเฉพาะช่วงลีฟโดยอ้างอิง; บันทึกไว้ ไม่นำมาใช้ใน v1)
tag Arweave จงใจให้แจกแจงได้ — บันทึก มีไว้ เพื่อให้ค้นพบ ต่างจาก 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 tag เป็น คำใบ้ที่ไม่น่าเชื่อถือ; body CBOR เป็นอำนาจเดียว
จังหวะ: รายวันโดยค่าเริ่มต้น ทบทวนตามปริมาณ (ตัวกระตุ้นขนาดทำให้จังหวะที่มีผลสั้นลงอัตโนมัติภายใต้ภาระ); การผนึก T3 แบบจ่ายเงินบังคับจุดยึด ดังนั้นลูกค้าที่จ่ายเงินจึงไม่เคยรอหนึ่งวัน กระเป๋าเงินจุดยึด เป็นแบบเฉพาะและความเร็วต่ำ แยกจากกระเป๋าเงินอัปโหลด — มันต้องเป็น JWK ของตัวเอง (กุญแจที่แตกต่าง ไม่ใช่บทบาทเชิงตรรกะบนกระเป๋าเงินอัปโหลด) ดังนั้นการบุกรุกกระเป๋าเงินอัปโหลดจึงไม่สามารถปลอมจุดยึดได้ — โดยมีงบประมาณธุรกรรมจุดยึดต่อวันแบบแข็ง (ARWEAVE_DAILY_CEILING ที่ถูกลดบทบาท) ท่าทีการเก็บรักษาถูกระบุอย่างตรงไปตรงมา: กุญแจร้อนขอบเขตแคบที่มีเบรกเกอร์ตัดวงจรที่รัดกุมและยอดคงเหลือต่ำ ไม่ใช่ "เย็น" — กระเป๋าเงินที่ลงนามอัตโนมัติรายวันไม่สามารถเย็นได้ และข้อกำหนดไม่แสร้งทำเป็นอย่างอื่น
16.8 ANS-104 Bundler
ตัวเข้ารหัส ANS-104 DataItem ภายในและตัวลงนาม deep-hash ราว 300 บรรทัด Web Crypto เท่านั้น ไม่มี dependency npm (Turbo SDK ทั้งสองตัวสอบตกประตูซัพพลายเชน npm ci --ignore-scripts) เลย์เอาต์ไบต์ DataItem:
signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data
การลงนามคือ deepHash ของ Arweave — ไดเจสต์ SHA-384 แบบเรียกตัวเอง (ข้อกำหนดการสื่อสารของ Arweave, crypto.subtle.digest("SHA-384")) เหนือ ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — จากนั้น RSA-PSS เหนือ deep hash ด้วยกระเป๋าเงิน JWK ผ่าน crypto.subtle; id = base64url(SHA-256(signature)) SHA-384 ที่นี่ถูก กักกันเป็นองค์ประกอบพื้นฐานสำหรับการสื่อสาร Arweave เท่านั้น ไม่เคยเป็นองค์ประกอบพื้นฐานความเชื่อถือของ qub (§15 บันทึกรั้วนี้ไว้; การแฮชความเชื่อถือ qub เป็น SHA3-256 ตลอด)
เส้นทางโค้ดเดียวให้บริการสามผู้บริโภค: ความถาวรต่อ qub T3 แบบจ่ายเงิน ทางสำรองเมื่อ Arweave ไม่พร้อมใช้งาน (เข้าคิว DataItem คืนการตอบรับ R2-first ไม่ว่าอย่างไร — สิ่งนี้ปิดทางตัน 503 ARWEAVE_UNAVAILABLE ปัจจุบัน) และการเขียน AnchorBundle โครงร่างลายเซ็น (ได้ข้อสรุป — §16.15 Q8): v1 ลงนามด้วย RSA-PSS (ชนิดลายเซ็น 1) นำกลไกกระเป๋าเงิน Arweave JWK ที่มีอยู่กลับมาใช้ซ้ำ (ไม่มีการเก็บรักษากุญแจระยะยาวใหม่ ตอบโจทย์แนวคิด "ความลับน้อยลงหนึ่ง"); Ed25519 ถูกเลื่อนไปยังเส้นทางการย้าย PQ ของ §15
deep hash ที่ทำมือเป็นโค้ดที่เสี่ยงสูงสุด มีการครอบคลุมตามธรรมชาติต่ำสุดใน W5 ดังนั้นการกั้นของมันจึง ต่อรองไม่ได้ (§16.15 Q8):
- fixture ข้ามภาษา
tlog_v1.json(Rust + TS รูปแบบwrapper_v1.jsonของ §14.5) ครอบคลุม deep-hash ไบต์ DataItem + id แฮชลีฟ ราก 5 ลีฟ + เส้นทางตรวจสอบ แฮช STH หลักฐานการรวม และหลักฐานความสอดคล้อง — ในทั้งทิศทางลงนามและทิศทางตรวจสอบ (ทิศทางตรวจสอบสำคัญเพราะการตรวจสอบ tx → tx_id ในเครื่องของ §16.6 ดึง deep hash เข้าสู่ตัวตรวจสอบยืนเดี่ยวทุกตัว ไม่ใช่เพียงผู้เขียน) - การไปกลับ interop ครั้งเดียวผ่าน bundler ANS-104 อ้างอิง บริโภคเป็น ข้อมูลทดสอบสถิตเท่านั้น — ไม่เคยเป็น dependency รันไทม์ npm (ท่าที Web-Crypto-เท่านั้น / ไม่มี install-script ยังคงอยู่)
- เส้นทาง deep-hash + RSA-PSS ต้องไปกลับผ่านองค์ประกอบพื้นฐาน
crypto.subtleเดียวกัน กับที่การผลิตใช้ ดังนั้นตัวเข้ารหัสภายในจึงเข้ากันได้ระดับไบต์ - มอนิเตอร์การยอมรับหลัง bundle ที่ดำเนินต่อเนื่อง ยืนยันว่าแต่ละ DataItem จุดยึด / ทางสำรอง บรรลุการยอมรับของ Arweave จริง พร้อมการเตือน + เบรกเกอร์ตัดวงจร — เพราะ deep hash ยังให้บริการคิวทางสำรองเมื่อ Arweave ไม่พร้อมใช้งานด้วย ดังนั้นการถดถอยแบบเงียบจะเติมคิวนั้นด้วยรายการที่เครือข่ายปฏิเสธในช่วงไฟดับที่มันมีไว้เพื่อครอบคลุมพอดี
16.9 หลักฐานการรวมและความสอดคล้อง
ทั้งสองเป็น RFC 9162, SHA3-256, ให้บริการเป็น CBOR มาตรฐาน
InclusionProof — GET /api/v1/qub/:tx_id/proof: ver:u8, leaf:bstr (CBOR ลีฟที่แม่นยำ — ตัวตรวจสอบคำนวณ leaf_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 รายการกุญแจเดียวที่ไม่กำกวม ตรึงด้วยเวกเตอร์ทดสอบ
การตรวจสอบแบบยืนเดี่ยว (ไม่มีเซิร์ฟเวอร์ qub ขยาย §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).
ที่จัดเก็บที่ให้บริการหลักฐานต้องคีย์ด้วยพิกัด (ได้ข้อสรุป — §16.15 Q7 เงื่อนไขเบื้องต้นที่ปิดกั้น) การสร้างหลักฐานลีฟเย็นเป็นกลางต่อความถูกต้อง ก็ต่อเมื่อ วัสดุตรวจสอบ R2 เป็น ที่จัดเก็บโหนดเมอร์เคิลถาวรที่คีย์ด้วยพิกัดต้นไม้สัมบูรณ์ (level, index) — ไม่ใช่เดลตาโหนดต่อ batch ด้วยที่จัดเก็บที่คีย์ด้วยพิกัด เส้นทางตรวจสอบ (leaf i, size N) ใด ๆ คือเซตของ R2 GET โดยตรง O(log N) ครั้งโดย ไม่มีการคำนวณใหม่ข้ามขอบเขตชุด; ด้วยที่จัดเก็บที่คีย์ด้วยชุดมันไม่เป็นเช่นนั้น ซึ่งคือช่องว่างเลย์เอาต์ที่จัดเก็บที่การแก้นี้ปิด body ลีฟก็สามารถระบุที่อยู่ด้วยเนื้อหาโดย seq เช่นกัน เวกเตอร์ทดสอบ W5 ต้องพิสูจน์ ลีฟเย็นยุค genesis เทียบกับรากที่ภายหลังกว่ามากโดยใช้เพียง R2 + Arweave โดยล้างที่จัดเก็บ LogDO ดังนั้นการอ้างความปลอดภัยของการเรียกคืนใน §16.13 จึงมีหลักฐานหนุนแทนที่จะถูกกล่าวอ้าง R2 GET ตามลำดับ O(log N) ครั้งอยู่ เฉพาะ บนปลายทางหลักฐานแบบอะซิงโครนัส — ไม่เคยบนเส้นทางผนึกร้อน (§16.10) หรือ cron ต่อ tick
16.10 การจัดลำดับการตอบรับ R2-First
ลำดับ POST /api/v1/upload กลายเป็น:
- ประตูครึ่งหน้า (auth การตรวจสอบ คีย์ shard ของความเป็นเอกลักษณ์) — ไม่เปลี่ยนแปลง
- แบบซิงโครนัส
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)— พื้นความคงทน; ปิดการแข่งขัน precache ของ W1 ด้วย (เดิมเป็นctx.waitUntilหลังการส่ง Arweave) - แบบซิงโครนัส
await LogDO.append(leaf)— RPC DO ในโคโลหนึ่งครั้ง; ผู้เขียนเดียวกำหนดseqขยายเชนรายการ และอัปเดตชายขอบ (RMW บนสถานะที่ใช้ร่วมกัน → DO ไม่เคย KV) RPCappendทำ เฉพาะ สิ่งนั้น — งานปิดชุดเมอร์เคิลO(batch)รัน นอก RPC นี้บนนาฬิกาปลุก LogDO มิฉะนั้น p95 ของ append พุ่งทุกการผนึกที่LOG_BATCH_MAX_LEAVES - คืนการตอบรับเดี๋ยวนี้ — พร้อมใบเสร็จการผนึก (ลงนามโดยกุญแจใบเสร็จที่ตรึง §16.6) และ
{ tx_id, log_seq, anchor_status: "pending" }การกระจาย Arweave หลายวินาทีถูกนำออกจากเส้นทางวิกฤต ctx.waitUntilหนึ่ง ครั้งเข้าคิวงานที่เลื่อน: การส่ง Arweave ต่อ qub (ตอนนี้ความพยายามที่ดีที่สุด / จ่ายเงิน; เมื่อล้มเหลวจะไปคิวทางสำรองของ bundler แทนที่จะ 503 ผู้ใช้) บวกการเขียนเมตาชั่วคราวที่มีอยู่ การปิดชุดและการยึดรันอย่างอิสระจากนาฬิกาปลุก LogDO และ cron จุดยึดรายวัน ไม่มีctx.waitUntilภายในลูป; คีย์ shard ของความเป็นเอกลักษณ์ที่มีอยู่ถูกรักษาไว้
งบประมาณหน่วงเวลา (ได้ข้อสรุป — §16.15 Q7) เป้าหมาย < 300 ms p95 เป็น ประตูเปิดตัวที่วัดได้ ไม่ใช่สมมติฐาน เส้นทางวิกฤตที่ซื่อตรงคือการอ่าน KV ครึ่งหน้า + R2 PUT หนึ่งครั้ง + Durable Object สองตัวแบบอนุกรม — การหักโควต้าผนึก QuotaDO ที่มีอยู่ และ การ append LogDO ใหม่ — ดังนั้นงบประมาณต้องนับการไปกลับ DO ในโคโลสองครั้ง ไม่ใช่หนึ่งครั้ง จัดส่งนาฬิกาเตือนหน่วงเวลา LogDO ที่สะท้อนของ QuotaDO และถือว่าการถดถอยของ p95 เป็นตัวปิดกั้นการปล่อย
16.11 แบบจำลองความเชื่อถือ — การอ้างที่แม่นยำ ขอบเขตตามชนิดลีฟ
สำหรับ kind=0x01 (รับรอง): "เนื้อหานี้ — body ตรงกับ body_hash ระบุด้วย qub_id — ถูกผูกพันเข้าบันทึกเพิ่มต่อท้ายเท่านั้นของ qub ที่ตำแหน่ง seq และมีอยู่ไม่ช้ากว่าเวลาบล็อก Arweave T; มันอ่านไม่ได้ในเชิงการเข้ารหัสจนถึงรอบ drand R = unlock_round(unlock_at)" นี่คือชุดสาม {การผูกรอบ tlock + การรวมเมอร์เคิล + รากที่ยึดแล้ว} เต็ม
สำหรับ kind=0x02 (กล่าวอ้าง ค่าเริ่มต้น): "ไซเฟอร์เท็กซ์ทึบแสงที่ที่อยู่เนื้อหา chash อ้าง qub_id และ unlock_at ถูกผูกพันเข้าบันทึกเพิ่มต่อท้ายเท่านั้นที่ตำแหน่ง seq และมีอยู่ไม่ช้ากว่าเวลาบล็อก Arweave T" ขารอบและ body มาจากการตรวจสอบ .qub-bundle §11 ที่มีอยู่ (qub_core::unlock) ไม่ใช่ จากบันทึก; สิ่งที่บันทึกเพิ่มเหนือธุรกรรมต่อ qub เปล่า ๆ คือการจัดลำดับที่ตรวจจับการดัดแปลงได้ เวลาข้อผูกพันขอบเขตบนที่ไม่ต้องเชื่อถือ และการต้านการให้ข้อมูลขัดแย้ง
การอ้างทั้งสองยกเว้น ตาม §11: ผู้เขียนโดยไม่มี sig_alg ≥ 0x01 เจตนา และเวลาความละเอียดต่ำกว่าจุดยึด ทั้งสองไม่ให้การอ้างใดพึ่งพา received_at
เพดานการอ้าง (ข้อจำกัดการเปิดตัวที่ผูกพัน — ได้ข้อสรุป §16.15 Q1) สำหรับ qub ฟรี / ค่าเริ่มต้น (kind=0x02) การอ้างขอบเขต kind=0x02 ด้านบนคือ เพดาน ของสิ่งที่พื้นผิวผลิตภัณฑ์ การตลาด ข้อกำหนด หรือการเรนเดอร์หลักฐานใด ๆ อาจยืนยันได้ ไม่มีพื้นผิวใดอาจระบุหรือบ่งบอกว่า บันทึก พิสูจน์เนื้อหาหรือรอบปลดล็อกของ qub เริ่มต้น — บันทึกพิสูจน์ การจัดลำดับ + เวลาข้อผูกพันขอบเขตบนที่ไม่ต้องเชื่อถือของไซเฟอร์เท็กซ์ทึบแสง หลักฐานเนื้อหาและรอบมาจากการตรวจสอบ .qub-bundle §11 ที่มีอยู่เท่านั้น ซึ่งเป็นอิสระจากบันทึก นี่คือตัวปิดกั้นการเปิดตัวที่แข็งบนคอปปี้ ไม่ใช่ความชอบเชิงสไตล์; เป็นการแก้ที่ทำให้เส้นทางเริ่มต้นที่มองไม่เห็นไบต์ซื่อตรง
16.12 การกำหนดเวอร์ชันและการประสานงาน W3
ไม่มี การเพิ่มรูปแบบการสื่อสาร SealedQub และจึง ไม่มี การเพิ่มเวอร์ชันโปรโตคอล (§12.1): บันทึกเป็น sidecar ที่ผูกพันกับฟิลด์และไบต์ที่มีอยู่ ดังนั้นมันจึงไม่เข้าสู่ประวัติเวอร์ชันโปรโตคอลของ §12.2 drand_chain_version ที่เลือกได้ของ W3 ไม่ถูกแตะต้องและยังคงเป็นฟิลด์ SealedQub ที่เลือกได้เพียงฟิลด์เดียว บันทึกแทนที่จะนำเสนอพื้นที่เวอร์ชันอิสระของตัวเอง — LOG_VERSION_1, ANCHOR_FORMAT_1, InclusionProof.ver — สะท้อนความเป็นอิสระของเวอร์ชันตัวห่อหุ้มของ §12.4 (ตัวห่อหุ้มพกไบต์เวอร์ชันที่เป็นอิสระจากเวอร์ชันโปรโตคอล และเวอร์ชันบันทึกตามการแยกเดียวกัน)
การส่งมอบหลักฐานถูกดึงโดยค่าเริ่มต้น โดยมีตัวพ่วงที่เลือกได้ หลักฐานไม่สามารถมีอยู่ที่เวลาผนึก (จุดยึดยังไม่ถูกเขียน) ดังนั้น .qub bundle เวลาผนึกจึงปลอดหลักฐาน ตัวตรวจสอบของ W7 ดึง GET …/proof ครั้งเดียว หรือในโหมดออฟไลน์เต็มสร้างหลักฐานใหม่จาก AnchorBundle สาธารณะผ่านการสอบถาม Arweave บน Log-Id .qub bundle (W7) สงวน สมาชิก inclusion_proof ที่เลือกได้ — ไม่มีที่ผนึก เติมโดยการส่งออกใหม่หลังการยึดสำหรับการเก็บถาวรเย็น — ตามรูปแบบ "เลือกได้ ละเว้นโดยค่าเริ่มต้น เพิ่มเติม" เดียวกันกับ drand_chain_version ของ W3
16.13 การเก็บรักษา
หน้าต่างการเก็บรักษาสำหรับหางเปิดของ LogDO สารตั้งต้นที่ให้บริการหลักฐาน R2 ตัวนับเบรกเกอร์ตัดวงจรจุดยึด และคิวทางสำรองของ bundler ถูกระบุใน docs/DATA-RETENTION.md หลักการ: ที่จัดเก็บต่อรายการร้อนของบันทึก (LogDO) เรียกคืนได้หลังการยึด; วัสดุตรวจสอบของมัน — ที่จัดเก็บโหนดเมอร์เคิลที่คีย์ด้วยพิกัด (level, index) + body ลีฟที่ระบุด้วย seq (§16.9) + จุดยึด Arweave — เป็นแบบถาวร การเรียกคืนลีฟเย็นจาก DO ไม่เคยทำให้หลักฐานที่ออกแล้วเป็นโมฆะ เพราะหลักฐานแก้ไขเทียบกับที่จัดเก็บโหนด R2 ถาวรนั้นและจุดยึด Arweave ไม่ใช่ DO (และเวกเตอร์ทดสอบ DO ที่ถูกล้างของ §16.9 พิสูจน์มัน)
16.14 เวกเตอร์ทดสอบ
W5 จัดส่ง fixture ข้ามภาษา tlog_v1.json (§16.8) บวกเวกเตอร์ที่ทำงานแล้ว: ลีฟ kind=0x01 และ kind=0x02 → leaf_hash; รากสะสม 5 ลีฟ; หลักฐานการรวมหนึ่ง; หลักฐานความสอดคล้องหนึ่ง; AnchorBundle หนึ่ง; และ id DataItem หนึ่ง สิ่งเหล่านี้อยู่ควบคู่กับเวกเตอร์ตัวห่อหุ้มภายนอกของ §14.5 และถูกใช้งานโดยทั้งการใช้งาน Rust (qub-core) และ TypeScript (Worker)
16.15 การตัดสินใจตรวจทาน (W5 — ได้ข้อสรุป)
การตรวจทานภายนอก W5 (การตรวจการออกแบบเชิงปฏิปักษ์ + การอนุมัติของเจ้าของ) เสร็จสมบูรณ์แล้ว การตัดสินใจแต่ละข้อด้านล่างได้ข้อสรุปและสะท้อนในเนื้อหา §16 ด้านบน; ข้อจำกัดการเปิดตัวที่ผูกพัน ถูกกล่าวซ้ำที่ตอนท้าย การใช้งานอาจดำเนินการได้ภายใต้ข้อจำกัดเหล่านั้น
- ความซื่อตรงของลีฟเส้นทางเริ่มต้น (
kind=0x02) — ได้ข้อสรุป จัดส่งการแยกสองชนิดลีฟตามที่ระบุ:kind=0x02ไม่ผูกพันทั้งbody_hashและdrand_roundไม่มีฟิลด์*_body_hashบนเส้นทางที่มองไม่เห็นไบต์ (มันจะเป็นสัญญาณ "ตรวจสอบแล้ว" เท็จที่อ่านง่ายที่สุดสำหรับผู้รวมระบบ และเป็นความสะดวกที่ §11 ให้จาก bundle อยู่แล้ว) อย่า ต้องการการผนึกฝั่งเซิร์ฟเวอร์สำหรับ qub ที่บันทึกรับรอง (นั่นจะบังคับข้อความธรรมดาผ่าน Worker และทำลายคูเมืองการทำลายการเข้ารหัส) การลัดวงจรที่อธิบายตัวเองใด ๆ อยู่ใน.qubbundle / ซองหลักฐานเป็นฟิลด์ที่ตัวตรวจสอบคำนวณใหม่ ไม่เคยเป็นฟิลด์ลีฟ เพดานการอ้างที่เจ้าของยืนยัน: §16.11 - ความรับผิดชอบการให้ข้อมูลขัดแย้ง / การละเว้น — ได้ข้อสรุป กุญแจใบเสร็จการผนึกถูกตรึงใน
LogProfile+ ลงนามไขว้โดยanchor_owner(ปิดความขัดแย้ง "ไม่มีกุญแจลงนาม" ก่อนหน้า; §16.6) แบบจำลองพยานการเปิดตัว: ใบเสร็จที่ตรึง + ระเบียบวิธีมอนิเตอร์ + การเดินเชน prev + หัวที่เผยแพร่เองคู่ (คลัง GitHub สาธารณะที่ qub เป็นเจ้าของ โซเชียลความพยายามที่ดีที่สุด) ทำการตลาดเป็น ตรวจจับได้ + มีใบเสร็จ ไม่เคย เป็นพยานโดยอิสระ พยานบุคคลที่สามที่แท้จริงถูกเลื่อนไปยังการเพิ่มการกำกับดูแล §15 - รากความเชื่อถือเจ้าของจุดยึดที่ตรึง + การหมุน — ได้ข้อสรุป ใช้การตรึง
LogProfile(§16.6); ตัวตรวจสอบตรวจanchor_tx.owner == anchor_ownerและตรวจสอบการผูก data → tx_id ของธุรกรรมในเครื่อง การกำกับดูแลการหมุนเป็น ส่วนขยายที่ต้องสร้าง ของ §15 (เพิ่มตัวกระตุ้น §15.3) ไม่ใช่การนำกลับมาใช้ซ้ำ; การหมุนที่วางแผนไว้ลงนามไขว้ การหมุนที่เกิดจากการบุกรุกตกกลับสู่การเพิ่ม §15 โดยการตรวจสอบฟอร์กจำกัดความเสียหาย - การปิดบังลีฟ qub ส่วนตัว — ได้ข้อสรุป คงการปิดบังสำหรับ qub ส่วนตัว (
ref = SHA3-256(qub_id ‖ log_blind_secret)),qub_idดิบสำหรับ qub สาธารณะ (§16.2.1 อยู่แล้ว),chashเป็นการผูกแบบยืนเดี่ยวlog_blind_secretเป็นความลับระดับความสัมพันธ์/Sybil หมุนไปข้างหน้าเท่านั้น (§16.2.1) received_at— ได้ข้อสรุป คงไว้ในลีฟ ผูกพันแต่ไม่ใช่หลักฐานเชิงพยานอย่างชัดเจน; ไม่เคยแสดงเป็นหลักฐานหรือการยืนยันข้อพิพาทบนพื้นผิวใด การตรวจสอบความสมเหตุสมผลของมอนิเตอร์ใด ๆ เทียบกับเวลาบล็อก ArweaveTไม่ใช่anchored_atที่ผู้ดำเนินการควบคุม (§16.6)- เวลาที่พิสูจน์ได้ระดับฟรี — ได้ข้อสรุป (เจ้าของอนุมัติ) ความคงทนไม่ถดถอย; มีเพียงเวลาข้อผูกพันขอบเขตบนที่พิสูจน์ได้ที่หยาบขึ้นเป็นเวลาบล็อกของจุดยึด คอปปี้ระดับฟรีไม่ใช้ SLA เชิงตัวเลข ("…เพิ่มที่จุดยึดบันทึกถัดไป โดยทั่วไปรายวัน"); หลักฐานระดับชั่วโมงที่แม่นยำเป็นคุณสมบัติ T3 แบบจ่ายเงิน เปิดเผยที่พื้นผิวเปรียบเทียบระดับ + ในข้อกำหนด (§16.1)
- ต้นไม้สะสมบน Workers — ได้ข้อสรุป ต้นไม้ RFC 9162 สะสมเดียว + LogDO ผู้เขียนเดียวที่แคชชายขอบ (มีพื้นที่สบายเทียบกับเพดาน DO ~1k การเขียน/วินาที; เลื่อนการแบ่ง Merkle-of-shard-roots จนใกล้มัน) เงื่อนไขเบื้องต้นที่ปิดกั้น: ที่จัดเก็บโหนด R2 ที่คีย์ด้วยพิกัด
(level, index)+ เวกเตอร์ทดสอบลีฟเย็นแบบ DO ที่ล้าง (§16.9);< 300 msเป็นประตูเปิดตัวที่วัดได้เหนือ DO สองตัวแบบอนุกรม (§16.10) - โครงร่างลายเซ็น ANS-104 + deep-hash — ได้ข้อสรุป RSA-PSS (ชนิดลายเซ็น 1 นำกระเป๋าเงินจุดยึดเฉพาะ JWK กลับมาใช้ซ้ำ); Ed25519 เลื่อนไปยังเส้นทาง PQ ของ §15 deep hash SHA-384 ที่ทำมือถูกกั้นด้วย fixture ข้ามการใช้งานทั้งสองทิศทาง การตรวจ interop ของ bundler อ้างอิงแบบสถิตเท่านั้น การไปกลับ
crypto.subtleที่ใช้ร่วมกัน และมอนิเตอร์การยอมรับ Arweave หลัง bundle (§16.8)
ข้อจำกัดการเปิดตัวที่ผูกพัน (นำเข้าสู่การใช้งาน + การตรวจทานผลิตภัณฑ์/กฎหมาย):
- เพดานการอ้าง (Q1/Q6) ไม่มีพื้นผิวใดอาจพูดว่า บันทึกพิสูจน์ เนื้อหาหรือรอบปลดล็อกของ qub เริ่มต้น; การอ้างที่อนุญาตคือ จัดลำดับ ตรวจจับการดัดแปลงได้ พร้อมเวลาข้อผูกพันขอบเขตบนที่ไม่ต้องเชื่อถือ คอปปี้ตราเวลาระดับฟรีไม่พกหน่วงเวลาเชิงตัวเลข; หลักฐานระดับชั่วโมงที่แม่นยำเป็น T3 แบบจ่ายเงินเท่านั้น
- ความซื่อตรงของพยาน (Q2) ทำการตลาดการให้ข้อมูลขัดแย้งเป็น ตรวจจับได้ + มีใบเสร็จ ไม่เคย เป็นพยานโดยอิสระ
- กุญแจใบเสร็จ + จุดยึด (Q2/Q8) กุญแจใบเสร็จถูกตรึง + ลงนามไขว้; กระเป๋าเงินจุดยึดเป็น JWK ของตัวเองที่แตกต่างจากกระเป๋าเงินอัปโหลด
- ประตู deep-hash (Q8) ไม่มีจุดยึดหรือธุรกรรม T3 ใดจัดส่งจนกว่า fixture ทั้งสองทิศทาง + การตรวจ interop จะผ่าน; มอนิเตอร์การยอมรับเรียกเตือนเมื่อล้มเหลว
- เงื่อนไขเบื้องต้นที่จัดเก็บ (Q7) ที่จัดเก็บโหนดที่คีย์ด้วยพิกัด + เวกเตอร์ลีฟเย็นแบบ DO ที่ล้างเป็นข้อกำหนดเบื้องต้นสำหรับการรับประกัน "การเรียกคืนไม่เคยทำให้หลักฐานเป็นโมฆะ"