مواصفات بروتوكول qub
qub بروتوكول للالتزامات الزمنية التشفيرية: نظام لختم الكلمات حتى تاريخ مستقبلي ثم التحقق لاحقًا مما خُتم بالضبط، وأي جولة drand قيّدت إصداره، وعند توفر معاملة تخزين أو إثبات من سجل الشفافية، حدٍّ أقصى مستقل ذي طابع زمني لموعد الالتزام بالنص المشفر.
تجعل ثلاث بدائيات هذا النظام يعمل. drand منارة عشوائية لامركزية—يُفرض تاريخ الكشف تشفيريًا لا بحسن نية qub. ويحفظ التخزين المتين مع سجل شفافية للإلحاق فقط البايتات المختومة ويُرسي التزامات مجمّعة في تخزين عام دائم؛ ويكتب مسار T3 المدفوع أيضًا معاملة تخزين دائم فردية. وML-DSA-65 توقيع رقمي ما بعد الكم—عند تفعيل النسب، يرتبط qub بزوج مفاتيح لا يغادر سرّه جهاز المؤلف أبدًا.
تُنتج هذه البدائيات مجتمعةً بيانًا مُقفلًا زمنيًا وكاشفًا للعبث، وقابلًا للنسب اختياريًا ولإسناد طابع زمني مستقل—إيصالًا تنمو قيمته بنمو قدرة العالم على تزييف الماضي.
ما تبقى من هذه الوثيقة هو المواصفة المعيارية المطلوبة لتنفيذات قابلة للتشغيل البيني.
مواصفة بروتوكول qub
| الحقل | القيمة |
|---|---|
| إصدار المستند | 1.0.0 (protocol-v1.0.0) |
| بروتوكول السلك | 0x01 |
| الغلاف الخارجي | 0x01 |
| تاريخ السريان | 2026-09-23 |
| الحالة | حالي |
| مراجَع حتى | 2026-09-23 |
هذه الوثيقة هي مواصفة البروتوكول المعيارية لنظام الالتزام الموقوت في qub. تُعرّف هياكل البيانات، وقواعد التسلسل، وصيغ الاشتقاق، وإجراءات التحقق المطلوبة لتنفيذات قابلة للتشغيل البيني.
النطاق: طبقة البروتوكول محايدة لغويًا عن قصد — يكون جسم qub نصًا أو ماركداون أو بايتات ميثاق غير مفسَّرة، والعَرْض المراعي للُّغة هو مسؤولية المُشاهِد (تطبيق qub.social على الويب، إطار <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 | تمثيل ثنائي مختصر للكائنات (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, // 0x00 = private; 0x01 = public
content_type: u8, // 0x01 text; 0x03 pact; 0x04 verdict
plaintext: Vec<u8>, // Raw body bytes (UTF-8 for text)
sender_label: Option<String>, // Display name; V2-signed when authorship is enabled
title: Option<String>, // Plaintext countdown title; bound via title_hash
reply_to: Option<[u8; 32]>,// Parent qub_id; V2-signed when authorship is enabled
outcome_at: Option<i64>, // Optional future judgment time; bound to qub_id
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>, // When reality renders judgment; bound to qub_id
sender_label: Option<String>, // Not in qub_id; V2-signed when authorship is enabled
reply_to: Option<[u8; 32]>,// Parent qub_id; not in qub_id; V2-signed when present
body: Vec<u8>, // UTF-8 text or canonical CBOR pact/verdict body
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؛ وحقول التوقيع وشريك التوقيع غائبة. وقد توجد حقول بيانات وصفية اختيارية أخرى.
تكوينات 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 الأب لـ qubs سلاسل الردود (انظر §9.3 لاستتباعات نطاق التوقيع).
2.3 SealedQub (الصيغة السلكية القانونية)
تُسلسَل باستخدام CBOR قانوني (§3). هذه هي القطعة الأثرية السلكية الداخلية: تُخزَّن هذه البايتات عارية للتسليم العام، بينما يلفّها التسليم الخاص في OuterWrapper قبل التخزين (§13).
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x00 = private/wrapped; 0x01 = public/bare
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // 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
drand_chain_version: Option<u8>, // W3 — chain-migration version. Absent / 0 = quicknet
// (the only chain today). Lets a future chain swap
// be expressed on the wire without a breaking format
// change. NOT part of the §4.1 qub_id preimage, so its
// addition never alters an existing qub's identity.
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 hostile/control code points.
}
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>, // Carried from both wire layers; drives the verdict-watch block
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 (متطلبات الترميز الحتمي الأساسي) |
| ترتيب مفاتيح الخريطة | تُرتَّب حسب طول البايتات المُرمَّزة أولًا (الأقصر قبل الأطول)، ثم معجميًا (بايت ببايت للترميزات بنفس الطول) |
| ترميز الأعداد الصحيحة | أقصر صيغة: 0–23 في البايت الابتدائي؛ 24–255 في بايتين؛ 256–65535 في 3 بايتات؛ إلخ. |
| ترميز الطول | أطوال محدَّدة فقط. لا يُسمح بأي مصفوفات أو خرائط أو سلاسل بايتات أو سلاسل نص بطول غير محدَّد (المعلومات الإضافية = 31 محظورة). |
| الوسوم | لا توجد وسوم CBOR (النوع الرئيسي 6 محظور). |
| الفاصلة العائمة | لا أعداد عائمة (النوع الرئيسي 7 بقيم 0xF9–0xFB محظور). |
| سلاسل النص | مُرمَّزة بـ UTF-8، مُطبَّعة وفق NFC (صيغة تطبيع يونيكود C). |
| سلاسل البايتات | بايتات خام. لا ترميز base64 في طبقة CBOR. |
| المفاتيح المكرَّرة | يُرفض مع خطأ. يَجِب على المحللات ألّا تقبل بصمت مفاتيح خريطة مكرَّرة. |
| المفاتيح غير المعروفة | يُرفض مع خطأ. يَجِب على المحللات ألّا تتسامح مع مفاتيح خريطة خارج مجموعة المفاتيح القانونية للنوع — يَجِب ألّا يُفضي فكُّ ترميز سلسلتي بايتات قانونيتين متمايزتين إلى القيمة نفسها أبدًا (encode(decode(x)) == x)، وفي الحمولات الموقّعة سيكون أي مفتاح إضافي محتوى خفيًا يلتزم به كلا التوقيعين. تطوُّر المخطط يمرّ عبر version، لا عبر مفاتيح إضافية أبدًا. |
| القيم البسيطة | يُسمح فقط بـ true (0xF5) وfalse (0xF4) وnull (0xF6). |
| الحقول الاختيارية | الحقول الاختيارية الغائبة تُحذف من خريطة CBOR كليًا (لا تُرمَّز كـ null). الحقول الاختيارية الموجودة تُدرَج بترتيب المفاتيح المُصنَّف. |
3.2 ترتيبات المفاتيح القانونية المُتحقَّق منها
ترتيبات المفاتيح هذه معيارية. يَجِب على التنفيذات إصدار المفاتيح بهذا الترتيب تمامًا. يَنْبَغي لتأكيدات التصحيح أن تتحقق من الترتيب في بنيات ما قبل الإصدار.
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 (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. الطول المُرمَّز = 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 (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)
"drand_chain_version" (20 encoded bytes) ← only if present (W3; absent = quicknet)
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 (خريطة 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) | النوع الرئيسي 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. يُضاف بايت حشو 0x00 واحد للوصول إلى 10 بايتات للمحاذاة. يَجِب على التنفيذات استخدام هذه البايتات العشر بالضبط: [0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00].
ترميز outcome_at: وسّعت مراجعة تنفيذية سابقة للإصدار الصورة الأولية من 92 إلى 100 بايت لطيّ حقل outcome_at الاختياري ضمن الربط. يُرمَّز غياب outcome_at بثماني بايتات صفرية؛ وترفض مُتحقِّقات البروتوكول outcome_at <= 0 في كل موضع، فلا تستطيع هذه القيمة الحارسة أن تتصادم مع قيمة مشروعة. انظر §3.2 (الصيغة السلكية) والملف الداخلي tasks/verdict-uplift-plan.md للآلية التحكيمية التي تُحفّز هذا الحقل.
ترميز drand_round: وسّعت مراجعة تنفيذية لاحقة سابقة للإصدار الصورة الأولية من 100 إلى 108 بايت لطيّ drand_round (جولة drand المستهدفة، §4.3) ضمن الربط، ورفعت فاصل النطاق إلى QUB_ID_V2. وهذا يربط جولة القفل الزمني ضمن هوية qub: لا تستطيع أي بوابة إعادة ربط النص المُشفَّر بجولة مختلفة (مثلًا جولة ماضية بالفعل) عن الجولة التي يقتضيها unlock_at المعروض. كما يتحقق إجراء فكّ القفل (§8) إضافةً إلى ذلك من أن الجولة المخبوزة في مقطع نص tlock المُشفَّر تطابق unlock_round(unlock_at)، فيكون وقت فكّ القفل المعروض هو الجولة التي تتحكم في فكّ التشفير بشكل قابل للإثبات.
الخصائص:
- تغيير أي حقل تربطه الصورة الأولية—
version، أوcontent_type، أوcreated_at، أوunlock_at، أوoutcome_at، أوdrand_round، أو بايتاتbodyالخام (عبرbody_hash)، أوtitle(عبرtitle_hash)—يُنتجqub_idمختلفًا. - يُحسب qub_id قبل التشفير. يحمل كلٌّ من QubEnvelope و SealedQub نفس qub_id. يتحقق المُشاهِد من تطابقهما بعد فكّ التشفير.
- لا يعتمد
qub_idعلىsender_labelأوreply_toأو بايتات التوقيع أو مفاتيح التوقيع العامة. لكن في بناء توقيع V2 الحالي، يُصادَق علىsender_labelوreply_toمباشرةً بواسطةsender_label_hashوreply_to_or_zero(§9.3) كلما وُجدت توقيعات. - تغيير
titleفي SealedQub (مع تثبيت كل ما عداه) يُغيّرqub_idعبرtitle_hash. لذا لا تستطيع أي بوابة استبدال العنوان النصي المعروض على العدّ التنازلي دون إبطال هوية qub. - تغيير
outcome_atفي SealedQub (مع تثبيت كل ما عداه) يُغيّرqub_idعبر الصورة الأولية. لا تستطيع أي بوابة استبدال تاريخ ظهور الحكم قبل الكشف المعروض على العدّ التنازلي دون إبطال هوية qub. - تغيير
drand_round(مع تثبيت كل ما عداه) يُغيّرqub_idعبر الصورة الأولية. لا تستطيع أي بوابة إعادة ربط نص القفل الزمني المُشفَّر بجولة مختلفة دون إبطال هوية qub؛ وبالاقتران مع فحص جولة المقطع وقت فكّ القفل في §8، يكونunlock_atالمعروض هو الجولة التي تتحكم فعليًا في فكّ التشفير.
4.2 body_hash
body_hash = SHA3-256(body)
حيث body هو حمولة المحتوى الخامة Vec<u8>. لـ qubs النصية، هذا هو جسم 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 وقت التجزئة كي يكون الموجز ثابتًا عبر تتابعات نقاط الترميز المتكافئة بصريًا. القيمة الصفرية بالكامل محجوزة لحالة الغياب؛ والسلسلة الفارغة مرفوضة عند حدود CBOR القانوني بوصفها ترميزًا غير قانوني لـ "غائب" (الترميز القانوني يحذف الحقل كليًا).
4.3 ربط جولة فكّ القفل
drand_round = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
| المُعامل | المصدر | مثال |
|---|---|---|
unlock_at |
ثوان Unix UTC يختارها المستخدم | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
معلومات سلسلة drand (genesis_time) |
1595431050 |
chain_period_seconds |
معلومات سلسلة drand (period) |
30 |
هذا هو ربط tlock المرجعي (CurrentRound في drand). تنشر drand الجولة N عند chain_genesis_time + (N - 1) * chain_period_seconds، لذا تختار الصيغة الجولة الحالية عند unlock_at—الجولة التي يكون توقيعها أول توقيع يستطيع استخدامه مشاهد يصل عند unlock_at.
خاصية المحاذاة (الحالة المهمة عمليًا): عندما يكون (unlock_at - chain_genesis_time) قابلًا للقسمة تمامًا على chain_period_seconds، يُنشر توقيع الجولة المحددة بالضبط عند unlock_at، لا قبله أبدًا. وينطبق ذلك دائمًا على النشر المرجعي: وقت نشأة quicknet (1692803367) قابل للقسمة على فترتها البالغة 3 ثوانٍ، وتثبّت التطبيقات المرجعية أوقات الفتح على دقائق كاملة. وعند unlock_at غير محاذٍ، يُنشر توقيع الجولة المحددة قبل unlock_at بأقل من فترة واحدة—ودقة الالتزام الزمنية هي فترة منارة واحدة.
الربط القديم السابق للإصدار وتسامح جهة فك القفل: كان الربط الأصلي ceil((unlock_at - chain_genesis_time) / chain_period_seconds)، الذي اختار—في الحالة المحاذية أعلاه—الجولة المنشورة قبل unlock_at بفترة كاملة، فجعل النص المشفر قابلًا لفك التشفير مبكرًا بفترة واحدة بالضبط. يختلف الربطان بـ +1 تحديدًا عندما تقبل دلتا القسمة على الفترة، ويتفقان فيما عدا ذلك. ولأن drand_round مطوي في صورة qub_id الأولية الثابتة (§4.1)، لا يمكن إعادة اشتقاق القطع المختومة بالربط القديم؛ لذلك يَجِب على المتحققين عند إجراء الفحص المتقاطع في §8 الخطوة 6a قبول drand_round مخزنة مساوية إما للجولة المشتقة أو للجولة المشتقة ناقص واحد (ويَجِب أن يشترطوا أن تساوي جولة مقطع tlock الجولة المخزنة تمامًا). يوسع التسامح أبكر توقيع مُقيِّد بفترة واحدة على الأكثر. وتطبّق خدمة تجهيز المواثيق التسامح نفسه حين تعيد اشتقاق qub_id لميثاق مجهز (عند التجهيز وعند التوقيع المشترك): إذا لم يُعِد الربط الحالي إنتاج qub_id الملتزم به وكانت دلتا تقبل القسمة على الفترة، فإنها تعيد المحاولة بالجولة ناقص واحد، وتختم الميثاق النهائي إلى الجولة التي يربطها qub_id فعليًا—لا إلى الجولة المعاد حسابها عميانيًا، إذ سيجعل ذلك القطعة غير قابلة للاشتقاق إلى الأبد.
التحقق: يَجِب أن يكون unlock_at في المستقبل وقت الختم. يَجِب ألّا يبعد unlock_at أكثر من 10 سنوات عن created_at (للحدّ من مخاطر الاعتماد بعيد الأفق على drand؛ يَنْبَغي لواجهة المستخدم أن تُحذّر من تواريخ فكّ قفل تتجاوز سنتين).
5. أنواع جديدة للصيغة السلكية
تُوفّر الأنواع الجديدة للصيغة السلكية سلامة وقت الترجمة ضد الخلط بين بايتات 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() أن تتحقق من أن المدخل يبدأ برأس خريطة CBOR صالح. يحدث التحقق البنيوي الكامل وقت التحليل، لا وقت البناء، تجنبًا للتحليل المزدوج.
6. سجل أنواع المحتوى
| القيمة | النوع | أقصى حجم للجسم | ملاحظات |
|---|---|---|---|
0x00 |
محجوز (غير صالح) | — | يَجِب ألّا يُستخدم |
0x01 |
نص عادي (UTF-8، ماركداون مقيّد) | 50 KB مدفوع / 10 KB مجاني | انظر §10 لقواعد العرض. تُفرض القسمة بين المجاني والمدفوع بواسطة خدمة الرفع؛ السقف الصلب لطبقة البروتوكول هو 50 KB. |
0x02 |
محجوز (مستقبلي) | — | مُخصَّص لنوع محتوى مستقبلي؛ غير صالح في v1. يَجِب على المُشاهِدين رفضه وفقًا للقاعدة أدناه. |
0x03 |
ميثاق (اتفاق ثنائي، جسم CBOR) | 100 KB | الجسم هو CBOR قانوني PactTerms (§6.1). توقيع شريك التوقيع وفقًا لـ §9.7. |
0x04 |
حكم (تقييم ذاتي من المنشئ، جسم CBOR) | 8 KB | الجسم هو CBOR قانوني VerdictBody (§6.2). يصدر فقط عبر نيّة verdict على جانب النظام. علاقة الأب موجودة على وسم Arweave Parent-Tx-Id، لا في الجسم. انظر verdict-uplift-plan §3.4. |
يَجِب على المُشاهِدين رفض أنواع المحتوى غير المعروفة برسالة خطأ واضحة مرئية للمستخدم. يَجِب على المُشاهِدين ألّا يحاولوا عرض الأنواع غير المعروفة كنصّ.
6.1 جسم الميثاق (content_type = 0x03)
جسم الميثاق هو ترميز 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 bytes), value: String (≤ 2,000 bytes) } // NFC
PartyIdentifier{ label: String (≤ 100 bytes), contact: Option<String (≤ 320 bytes)> }
ترتيبات مفاتيح CBOR القانونية للخرائط الثلاث جميعها مذكورة في §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. لا تُترجَم محليًا؛ الجسم الموقّع محايد لغويًا. أي تغيير في الصياغة يستلزم إصدارًا جديدًا من المخطط (structured/v2).
تُثبَّت السلاسل الثماني وعملية البحث عنها (acknowledgement_for(role, kind)) ومبررات كل منها بواسطة التنفيذ المرجعي. يَجِب على التنفيذات المطابقة إصدار قيم إقرار متطابقة بالبايتات؛ واختبارات body-hash لـ SHA3-256 الذهبية المُغطّية للتركيبات الأربعة للأدوار تكشف أي انجراف.
ترتيب عرض المُشاهِد. تحتوي سلاسل الإقرار على عبارات مثل "described above"، والتي تفترض أن صفوف الوصف / النطاق تُعرَض قبل الإقرارات. يَجِب على المُشاهِدين عرض مصفوفة terms بترتيب CBOR؛ إعادة الترتيب تكسر دلالات النص.
جهة اتصال الطرف المقابل. عندما يكون contact للطرف B عنوان بريد إلكتروني صالحًا، تُرسل خدمة رفع qub تلقائيًا بريدًا إلكترونيًا لدعوة المراجعة / التوقيع المشترك وقت التحضير وتربط التوقيع المشترك النهائي بالتحقق من ذلك العنوان نفسه (§9.7). المواثيق التي يكون فيها جهة اتصال الطرف B غائبة لا يزال يمكن التوقيع المشترك عليها، لكن فقط عبر قناة خارجية — ترفض الخدمة طلبات التوقيع المشترك التي لا يمكنها إنتاج علامة تحقق-بريد-إلكتروني مطابقة لمدة 15 دقيقة.
6.2 جسم الحكم (content_type = 0x04)
جسم الحكم هو ترميز 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
}
ترتيب مفاتيح 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 (يطابق صفّ السجل أعلاه).
تعداد النتيجة. بايت السلك محايد لجهة النيّة؛ السلال الأربع Right / Partial / Wrong / Unfalsifiable تغطّي فضاء النتائج لكل نيّة حاملة للحكم. أما التسميات الخاصّة بكل نيّة ("توقّعتها" / "وفّيت به" / "أُطلق" / "أكّدتها الأحداث" لـ Right، إلخ) فهي شأن عرض من جانب المُشاهِد يُحَلّ مقابل نيّة qub الأب — يبقى السلك محايدًا لغويًّا ولجهة النيّة. القيم خارج النطاق 1..=4 يَجِب رفضها عند فكّ الترميز.
ربط الأب. لا يحمل qub الحكم مرجع الأب في جسمه. معرّف معاملة Arweave لـ qub الأب يُصدَر كوسم تخزين Parent-Tx-Id وقت الرفع (طبقة وسوم التخزين §7). هذا يُبقي الجسم بيانًا موقّعًا قائمًا بذاته للتقييم الذاتي؛ سلسلة التدقيق ("مُحقّ بشأن ماذا؟") تُثبَت عبر بحث وسم Arweave.
سلامة رابط الدليل (معياري). عندما يكون evidence_url حاضرًا، يَجِب على المُتحقّقين (جانب التأليف، جانب السلك، حافة الـ Worker) فرض ما يلي:
- HTTPS فقط. يَجِب أن تبدأ السلسلة بمتسلسلة البايتات
https://. أي مخطّط آخر —httpأوftpأوjavascriptأوdataأوfileوغيرها — يُرفض. - حدّ الطول. ≤ 2,048 بايت (الحدّ العملي لعنوان المتصفح).
- NFC + فحص النقاط المعادية. نفس قاعدة
titleوreflection— نقاط إلغاء ترتيب الكتابة الثنائي / العرض الصفري / كتلة الوسوم / BOM / C0 / C1 تُرفض. التعريف يطابقcrate::handle::contains_hostile_text_codepointفي Rust وworkers/api/src/utils/unicode.ts::isHostileCodepointفي TS (حافظ على التوافق بينها). - بدون مسافات أو أحرف تحكم ASCII. المسافات / DEL / البايتات تحت
0x20في أيّ موضع من الرابط تُرفض — يُغلق ذلك ناقل حقن\n/\tالذي لا تغطّيه قاعدة bidi. - مقطع مضيف غير فارغ. كل ما بين
https://وأول/أو?أو#يَجِب أن يكون غير فارغ.
لا جلب من جانب الخادم. يَجِب ألّا يتولّى الـ Worker وكالة الرابط أو جلبه أو معاينته. البروتوكول يخزّن سلسلة فقط؛ العرض يحدث على جانب المُشاهِد بـ 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.
f. visibility is 0x00 (private) or 0x01 (public).
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 = floor((unlock_at - chain_genesis_time) / chain_period_seconds) + 1
(§4.3). (Computed here, before qub_id, because drand_round is bound into the
qub_id preimage—§4.1.)
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,
visibility, unlock_at, drand_chain_id, and drand_round.
11. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12. Select the delivery shape from visibility:
a. Private (0x00): generate K = 32 random bytes and N = 12 random bytes
using a CSPRNG. Compute W = wrap_sealed_qub(SealedQubCbor,
qub_id=qub_id, key=K, nonce=N) per §13. Upload payload = W.
b. Public (0x01): upload payload = bare SealedQubCbor; do not generate K.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit the selected upload payload to the qub upload service. For a private
browser seal, the service is byte-blind to the inner SealedQubCbor and never
receives K. The Builder `/api/v1/seal` route is an explicit exception: it
receives plaintext and caller-supplied K in memory, then persists neither.
16. Receive arweave_tx_id from the service. For private delivery, construct
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or the equivalent short-code
path). For public delivery, omit the fragment. Browsers do not transmit URL
fragments to servers, so K from the browser-seal path is not observed by
qub.social or any storage gateway.
طبقة وسوم التخزين (خارج النطاق). تُرفق خدمة رفع qub مجموعة صغيرة عمدًا من وسوم معاملات التخزين إلى جانب حُمولة الرفع المحددة. Content-Type=application/octet-stream مطلوب معياريًا. تُرفق الخدمة المرجعية إضافيًا ثلاثة وسوم اختيارية عندما يختار المُنْشِئ إظهارها: Intent (نية إنشاء مُتحقَّق منها بقائمة السماح—announcement أو thesis أو prediction أو letter أو secret أو commitment أو proof أو verdict الصادر عن النظام)، وAuthor (بصمة المفتاح العام للمُنْشِئ وفقًا لـ §9.3 ست عشرية صغيرة من 64 رمزًا)، وParent-Tx-Id (معرّف معاملة التخزين لـ qub الأب لسلاسل الردود، base64url من 43 رمزًا).
وسم Author اختياري لكل qub: تطبيق المُنْشِئ المرجعي يُرفقه فقط عندما يُفعّل المستخدم صراحةً النسب العام وقت الختم. عند إيقاف التفعيل — وهو الافتراضي — لا يُكتب وسم Author ويكون qub غير منسوب على السلسلة: لا شيء في التخزين الدائم يربط الرفع بمعرّف المُنْشِئ أو بريده الإلكتروني أو qubs أخرى له. عند تفعيل التبديل، تنحلّ بصمة Author إلى @handle الذي اختاره المُنْشِئ عبر سلسلة التَّوْثيق في §9.5. علاقات سلاسل الردود وIntent لا تُعرّف الهوية. في التسليم الخاص يشفّر الغلاف الخارجي (§13) قطعة SealedQub الداخلية القابلة للتمييز، لذلك لا يكفي حصاد الأغلفة المخزّنة والحصول على توقيعات drand العامة لاستعادة الجسم من دون K؛ وتظل وسوم التخزين بيانات وصفية عامة عمدًا.
لا تُرفق الخدمة المرجعية عمدًا وسوم App-Name أو App-Version أو Type: أي مرشّح قائم على قيمة واحدة من هذا القبيل سيُرجع كامل مجموعة qubs لاستعلام GraphQL، وهذا يتعارض مع نطاق سرية الجسم فقط للغلاف.
يَجِب على المُتحقِّق المطابق ألّا يعتمد على أي وسم تخزين للتحقق من طرف ثالث وفقًا لـ §11؛ تجزئة الجسم / qub_id / التوقيع تلتزم بالـ CBOR الداخلي فقط، لا بمجموعة الوسوم.
8. بروتوكول فكّ القفل
تسلسل فكّ القفل الكامل. كل خطوة معيارية.
1. Viewer opens delivery URL. Extract arweave_tx_id from the path and retain
the optional URL fragment. Do not assume a missing fragment is an error:
public/bare delivery intentionally has no K.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch the stored bytes (with multi-gateway fallback).
3a. Resolve the delivery shape structurally:
a. If the bytes parse as OuterWrapper, require a well-formed 32-byte K in
the URL fragment, require wrapper version 0x01, and unwrap per §13.
Any missing/malformed K or AEAD failure is a terminal error.
b. Otherwise require the bytes to parse as bare SealedQubCbor; no K is
required. If neither shape parses, report an integrity error.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01), visibility is known, and the
delivery shape matches it (wrapped = 0x00 private; bare = 0x01 public).
Reject any mismatch or unknown value.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check. Recompute expected_round from
SealedQub.unlock_at per §4.3. Reject unless SealedQub.drand_round ==
expected_round OR SealedQub.drand_round == expected_round - 1 (the
legacy pre-release mapping—see §4.3), AND the round baked into the tlock
ciphertext stanza (read via the age/tlock header, no signature required)
== SealedQub.drand_round exactly. 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.
12a. Verify: QubEnvelope.outcome_at == SealedQub.outcome_at (both absent, or
both present and equal). Fail → integrity error.
12b. Content re-derivation. Recompute qub_id per §4.1 from the decrypted
fields — (QubEnvelope.version, content_type, created_at, unlock_at,
outcome_at, SealedQub.drand_round, QubEnvelope.body_hash,
title_hash(SealedQub.title)) — and verify it equals SealedQub.qub_id.
Fail → integrity error. The pairwise checks in steps 10-12a only prove
the two layers agree with EACH OTHER; a forger who rewrites a bound
field consistently on both surfaces (a pre-reveal title swap, or a
post-round body swap with a recomputed body_hash re-encrypted to the
same round under the same qub_id) passes them all. Only re-deriving
the identity from content closes this.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact), 0x04 (verdict).
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 the appropriate renderer (see §10 for text and §6 for pact/verdict).
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 بايتات | نشط |
0x02 |
Ed25519 | 32 بايتًا | 64 بايتًا | ثابت محجوز؛ غير مدعوم في البروتوكول v1 |
يَجِب على مشاهدي البروتوكول v1 رفض كل قيمة خارج {0x00, 0x01}، بما فيها القيمة المحجوزة 0x02. يمنع الحجز إعادة الاستخدام عرضيًا، ولا يعني التفعيل. ويتطلب تفعيلها التغيير المحكوم الموصوف في §15.
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]). بلا حشو. يفصل الفاصلُ المختلف بين البِنيتين نطاقيًا، لذا لا يمكن أبدًا أن يتحقق توقيع على إحدى الصورتين الأوليتين بوصفه الأخرى.
البايت الزائل: يَجِب أن يكون البايت التالي لـ 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 الأولية |
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 أو عنوان بريد إلكتروني أو معرّف اجتماعي أو شهادة 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–2000 بايت، يُضاعف 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 رفض طلب التوقيع المشترك ما لم توجد علامة تحقق بريد إلكتروني قصيرة العمر تطابق كلًا من معرّف التحضير وتجزئة البريد المُطبَّع لتلك الجهة. تُكتب العلامة بواسطة /api/v1/auth/verify عندما يحمل رمز الرابط السحري staging_id ويطابق العنوان المُتحقَّق منه SHA-256(normalise_email(party_b.contact)) — حيث تُحافظ normalise_email(addr) على حالة الجزء المحلي وتُصغّر فقط جزء النطاق (وفقًا لـ RFC 5321 §2.3.11)، وSHA-256 هنا هي تجزئة NIST FIPS 180-4 (متميزة عن SHA3-256 المستخدمة في اشتقاقات §4) — وتنتهي صلاحيتها بعد 900 ثانية (15 دقيقة) من الإصدار. هذه بوابة تشغيلية لمكافحة انتحال الشخصية، وليست جزءًا من إثبات qub على السلسلة — يحتاج المُتحقِّق من طرف ثالث الذي يُعيد تنفيذ §11 فقط إلى التخزين الدائم و drand، دون أي بحث على جانب الخادم. توجد العلامة على جانب الخادم فقط ولا تكون أبدًا جزءًا من الجسم الموقّع.
أثر الحجم (مؤلف ML-DSA-65 + شريك التوقيع):
| المكوّن | الحجم |
|---|---|
| توقيع المؤلف | 3,309 بايتات |
| مفتاح المؤلف العام | 1,952 بايتًا |
| توقيع شريك التوقيع | 3,309 بايتات |
| مفتاح شريك التوقيع العام | 1,952 بايتًا |
| إجمالي العبء التشفيري | 10,522 بايتًا |
| دلتا تكلفة التخزين | ~$0.05 |
10. عرض الماركداون والتعقيم
هذا القسم حرج أمنيًا. يعرض المُشاهِد qubs النصية (content_type = 0x01) باستخدام مجموعة فرعية مقيَّدة من الماركداون.
10.1 العناصر المسموح بها
- العناوين: من
#إلى####(لا#####ولا######) - التأكيد: عريض (
**)، مائل (*)، مشطوب (~~) - القوائم: مرتَّبة (
1.) وغير مرتَّبة (-،*) - اقتباسات الكتلة (
>) - الكود: نطاقات سطرية (```) وكتل مُسوَّرة (`````)
- المساطر الأفقية (
---) - فواصل الأسطر (مسافتان زائدتان أو سطر فارغ)
- الفقرات
10.2 العناصر المحظورة
| العنصر | المعالجة |
|---|---|
HTML خام (<div>، <script>، إلخ) |
يُجرَّد كليًا. لا يمرّ أي HTML. |
الصور () |
تُجرَّد. تُزال صيغة الصورة من المخرج. |
الروابط ([text](url)) |
يُعرَض الـ URL كنصّ عادي مرئي. لا يُربط تلقائيًا. غير قابل للنقر دون إجراء صريح من المستخدم. |
| مخططات URL الخطرة | javascript:، data:، vbscript:، file: — تُجرَّد. |
| الإطارات الداخلية، التضمينات، الكائنات | تُجرَّد. |
| كيانات HTML | تُفكَّك إلى أحرف عرض فقط إن كانت آمنة. |
10.3 التنفيذ
يَجِب على التنفيذات استخدام محلّل قائمة سماح صارمة، لا قائمة حظر. النهج المُوصى به:
- تحليل الماركداون باستخدام
pulldown-cmark(أو ما يكافئه). - السير على الـ AST وإسقاط أي عقدة ليست في قائمة السماح (§10.1).
- لعُقد الروابط: إصدار الـ URL كنصّ مرئي، لا كعنصر
<a>قابل للنقر. - تحويل الـ AST المُرشَّحة إلى تمثيل وسيط من نوع مُحدَّد (مثل enum
MarkdownNodeبمتغيرات آمنة فقط). HTML الخام غير قابل للتمثيل بنيويًا في هذا الـ IR. - العرض من الـ IR المُحدَّد النوع إلى طبقة العرض المستهدفة (مثل مكوّنات العرض التفاعلية، عُقد DOM). لا تسلسل سلسلة HTML ولا
innerHTMLفي أي نقطة.
نهج قائمة الحظر هشّ لأن امتدادات الماركداون الجديدة أو غرابات المحلل يمكن أن تُدخل عناصر غير مرشَّحة. نهج الـ AST المُحدَّد النوع يجعل XSS مستحيلًا بنيويًا — لا يوجد متغيّر يمكن أن يحمل HTML تعسفيًا.
10.4 حدود الحجم والبنية
- أقصى عمق عنوان مَعروض:
####(H4).#####وأعمق تُعرَض كنصّ عريض. - لا حدّ على عدد الفقرات (حدود حجم الجسم في §6 هي القيد).
- كتل الكود المُسوَّرة: لا تلوين بُنيوي في الـ MVP. تُعرَض كنص أحادي المسافة مُهيَّأ مسبقًا.
11. التحقق من طرف ثالث
يستطيع أي طرف ثالث يحمل البايتات المخزنة (ويحمل K لـ qub خاص/مغلّف) التحقق من القطعة التشفيرية دون تعاون من qub. ويتطلب ادعاء وجود ذي طابع زمني مستقل أيضًا إما إدراجًا متحققًا منه في التخزين الدائم لكل qub، أو إثباتًا متحققًا منه من سجل الشفافية في §16.
1. Obtain the stored bytes. For a private delivery, also obtain K from the
delivery-link fragment.
2. Resolve the delivery shape (§8 step 3a): unwrap OuterWrapper with K, or
accept bare SealedQubCbor. Require shape/visibility agreement.
3. Parse SealedQubCbor → SealedQub; validate protocol version, visibility,
content type, chain identity, and structural bounds.
4. Recompute expected_round from unlock_at (§4.3); require the stored round
(allowing the documented legacy minus-one case) and ciphertext-stanza round
to agree exactly as §8 step 6a specifies.
5. Obtain the drand signature for SealedQub.drand_round and verify its BLS
signature against the pinned chain public key.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify envelope/sealed equality for qub_id, unlock_at, and outcome_at.
10. Recompute qub_id from the decrypted fields, sealed drand_round, and title;
require it to equal the carried qub_id (§4.1).
11. If sig_alg != 0x00, verify the V2 author signature (§9.4). If cosigner
fields are present, verify their pairing, key separation, and signature
(§9.7).
12. For an existence-time claim, independently verify either:
a. the permanent-storage transaction's data-to-id binding, owner, block
inclusion, and block timestamp; or
b. a §16 inclusion proof through its pinned anchor and anchor block time.
13. Report each verified claim separately; do not collapse an absent storage
or anchor proof into a successful timing verdict.
ما يُثبته التحقق:
| مدخل الإثبات | ما يُرسّخه |
|---|---|
| حزمة/قطعة مختومة صالحة + توقيع drand | يطابق المتن المستعاد body_hash؛ وتظل البيانات الوصفية المرتبطة بـ qub_id سليمة؛ ويرتبط النص المشفر بجولة drand المعلنة؛ وقد انقضت تلك الجولة. ولا يثبت هذا متى أُنشئ النص المشفر. |
| توقيع V2 صالح للمؤلف/شريك التوقيع | صادق حامل المفتاح السري المقابل (أو حاملَاهما) على السطح الموقع في §9.3. |
| معاملة تخزين لكل qub متحقق منها مستقلًا | كان النص المشفر المخزن نفسه موجودًا في موعد أقصاه طابع كتلتها الزمني. |
| إثبات صالح من سجل الشفافية المُرسى | الادعاء الخاص بنوع الورقة في §16.11، بما فيه وقت التزام أقصى من كتلة المرساة. |
ما لا يُثبته التحقق:
| غير الإثبات | السبب |
|---|---|
| التأليف | sender_label زخرفي. بدون sig_alg ≥ 0x01، كان يمكن لأي شخص ختم هذا المحتوى. |
| النية | تثبت القطعة البايتات والعلاقات التشفيرية، لا ما قصده المُنشئ ذاتيًا. |
التزام سابق من .qub وحدها |
يستطيع المُنشئ تجميع حزمة صالحة بعد انقضاء الجولة المرتبطة. يثبت توقيع drand المضمّن أن الجولة انقضت، لا أن النص المشفر كان موجودًا قبلها. |
| الوقت الدقيق لضغط زر الختم | طابع كتلة التخزين أو المرساة حد أقصى يمكن التحقق منه مستقلًا، وقد يتأخر عن فعل المستخدم المحلي. أما ادعاءات sealed_at / received_at فليست أدلة. |
يوسّع سجل الشفافية المنفّذ (§16) التحقق عبر qubs بترتيب كاشف للعبث ووقت التزام أقصى عديم الثقة (وقت كتلة المرساة)، محدد بنوع الورقة (§16.11). ولا يضيف النسب أو النية؛ وفي مسار الرفع الافتراضي الأعمى للبايتات لا يثبت بنفسه body_hash أو drand_round، إذ تظل هذه من فحوص القطعة.
12. الإصدارات والتحكم في الإصدار
إصدارات المستند، وبروتوكول السلك الداخلي، والغلاف الخارجي مساحات إصدارات منفصلة. لذلك لا يغيّر توضيح خاص بالمستند البايتات بصمت، ولا يمكن لهجرة سلك مستقبلية أن تتنكر في صورة مراجعة تحريرية.
12.1 إصدار المستند
تستخدم هذه المواصفة إصدارات مستند دلالية (MAJOR.MINOR.PATCH) ووسم Git ثابتًا باسم protocol-v<release>.
- PATCH: تصحيح دقة أو تحرير لا يغيّر البايتات المطابقة أو السلوك المطلوب.
- MINOR: إضافة معيارية متوافقة رجعيًا، أو إدخال جديد في سجل، أو صيغة جانبية جديدة مستقلة الإصدار.
- MAJOR: تغيير معياري غير متوافق، بما فيه تفسير سلكي مطلوب جديد.
حالة الإصدار واحدة من مسودة (لم يصبح معياريًا بعد)، أو حالي (هدف التنفيذ الوحيد الموصى به)، أو مستبدَل (محفوظ للتحقق التاريخي). يعرض المسار غير ذي الإصدار /protocol الإصدار الحالي؛ ويحفظ وسم الإصدار مصدره الدقيق وكل لغة منشورة معه. ويتطلب تغيير الحالة أو رقم الإصدار تحديث هذا الجدول وسجل الإصدار في التغيير المراجَع نفسه.
| إصدار المستند | تاريخ السريان | الحالة | بروتوكول السلك | الغلاف | المصدر |
|---|---|---|---|---|---|
| 1.0.0 | 2026-09-23 | حالي | 0x01 |
0x01 |
protocol-v1.0.0 |
12.2 إصدار البروتوكول
حقل version (u8) في كل من SealedQub وQubEnvelope يُحدّد الإصدار الرئيسي للبروتوكول.
- يَجِب على المُشاهِدين رفض الإصدارات الرئيسية غير المعروفة برسالة خطأ واضحة.
- ضمن إصدار رئيسي معروف، يَجِب على مفكِّكات الترميز رفض مفاتيح الخريطة غير المعروفة (§3.1) — يحدث تطوُّر المخطط بإدخال
versionجديد، لا بإضافة مفاتيح كانت مفكِّكات الترميز القائمة ستتخطاها. (كانت مراجعات سابقة من هذه المواصفة تسمح بالتسامح مع الحقول الاختيارية غير المعروفة؛ وقد سُحب ذلك البند — فقد جعلencode(decode(x))غير متباين وفتح متّجهًا لمحتوى موقّع خفي على حمولات الميثاق.) - أنواع المحتوى (
content_type) ومخططات التوقيع (sig_alg) محكومة بالإصدار: يُمكن إدخال قيم جديدة فقط بالتزامن مع إصدار بروتوكول جديد أو تحديث صريح للسجل.
12.3 سجل إصدارات البروتوكول
| الإصدار | القيمة | الوصف |
|---|---|---|
| v1 | 0x01 |
تسليم خاص/مغلّف وعام/مجرّد؛ أجسام نص (0x01) وميثاق (0x03) وحكم (0x04)؛ توقيع V2 للمؤلف/شريك التوقيع بـ ML-DSA-65؛ قفل tlock على drand quicknet؛ SHA3-256. |
12.4 التوافق الأمامي
مُشاهِد v1 يصادف QubEnvelope بمفاتيح خريطة CBOR غير معروفة (مفاتيح ليست في الترتيب القانوني في §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.5 إصدار الغلاف الخارجي
يحمل OuterWrapper الموصوف في §13 بايت version خاصًا به، مستقلًا عن SealedQub.version وQubEnvelope.version. تتطور مساحتا الإصدارين بشكل منفصل: استبدال متماثل آمن ما بعد الكم مستقبلي يرفع بايت الغلاف دون لمس إصدار البروتوكول الداخلي، وإضافة طبقة بروتوكول مستقبلية (مثل حقل غِلاف جديد) ترفع الإصدار الداخلي دون لمس بايت الغلاف.
OUTER_WRAPPER_VERSION_* |
القيمة | الخوارزمية | الحالة |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM مع nonce بطول 12 بايتًا، وسم مصادقة بطول 16 بايتًا، AAD مرتبط بـ qub_id |
نشط للتسليم الخاص |
| — | 0x02–0xFF |
محجوز | مستقبلي |
يَجِب على المُشاهِدين رفض إصدارات الغلاف غير المعروفة برسالة خطأ واضحة. يُبقي البروتوكول عمدًا مساحة إصدار الغلاف ضيقة حتى يظهر دافع هجرة ملموس (مثل توجيه NIST يفضّل AEAD مختلفًا)؛ ستُخصَّص خانة 0x02 في نفس المراجعة التي تُدخل الخوارزمية.
13. غلاف التشفير الخارجي
13.1 المبرر
تجعل طبقات البروتوكول (QubEnvelope ← tlock ← SealedQub) qub المختوم مُقفلًا زمنيًا: الجسم غير قابل للقراءة حتى unlock_at وبعد نشر توقيع جولة drand. بعد فكّ القفل، مع ذلك، يصبح توقيع الجولة عامًا والشكل القانوني لـ CBOR لـ SealedQub قابلًا للتعرف، لذا يستطيع حاصد فهرس معاملات التخزين الدائم فكّ تشفير مجموعة qubs كاملة بالجملة.
في التسليم الخاص، يُغلق غلاف التشفير الخارجي تلك القناة بإقحام طبقة AEAD متماثلة إضافية بين SealedQubCbor القانوني والبايتات المخزنة. وفي مسار ختم المتصفح، يعيش مفتاح 256-بت K فقط في جزء URL لرابط التسليم وعلى أجهزة المستخدمين؛ ولا تنقل المتصفحات أجزاء URL إلى الخوادم، لذا يكون qub.social وكل بوابة تخزين وكل CDN أمام أي منهما عميًا رصديًا عن K. لذا يكون التمثيل المخزن لـ qub خاص نصًا مشفرًا معتمًا لا يمكن استرداد نصه الواضح دون الـ URL الذي اختار المُنشئ مشاركته. ويحذف التسليم العام هذه الطبقة عمدًا (§13.8).
التأثير الصافي:
- مقاومة التعداد للتسليم الخاص. يظل
OuterWrapperبنية CBOR قابلة للتمييز—فهو ليس حرفيًا غير قابل للتمييز من البايتات العشوائية—لكن حقل النص المشفّر فيه يخفي شكلSealedQubالداخلي القابل للتمييز. لا تنتهي استراتيجية الحاصد الموثقة «استعلام GraphQL عن رفعات qub عارية الشكل، وفكّ تشفيرها بالجملة بتوقيعات drand العامة» بنص عادي من دون K. - وضعية خصوصية تمزيق التشفير للتدفّق الخاص الافتراضي في المتصفح. لا تستطيع qub.social فكّ تشفير هذه القطع الأثرية المخزّنة من بياناتها الافتراضية على جانب الخادم. للاسترداد الصريح والتسليم العام والختم الموثوق على جانب الخادم حدود ثقة مختلفة ومعلنة.
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
├─ public (visibility=0x01) ───────────────▶ stored directly (§13.8)
└─ private (visibility=0x00)
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12, this section)
OuterWrapper CBOR bytes ← stored private payload
الختم وفكّ القفل في طبقة البروتوكول (§7، §8) لم يتغيّرا تحت حدود الغلاف؛ يلتصق الغلاف عند موقع استدعاء seal() وينفصل عند موقع استدعاء unlock().
13.3 هيكل بيانات OuterWrapper
struct OuterWrapper {
version: u8, // 0x01, see §12.5
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.0. - يَجِب أن يساوي
qub_idحقلqub_idفي SealedQub المُستعاد بعد فكّ الغلاف. يحلّل كلٌ منwrap_sealed_qubوunwrap_sealed_qubالمرجعيين CBOR الداخلي ويفرض هذه المساواة مباشرة؛ ويجعل ربط AAD، على نحو منفصل، العبث بـqub_idالخارجي بعد التغليف يفشل المصادقة. - يَجِب أن يكون
nonceبطول 96 بتًا (12 بايتًا)، مُولَّدًا حديثًا بواسطة CSPRNG لكل عملية تغليف. إعادة استخدام nonce تحت نفس المفتاح تسمح بهجمات إعادة استخدام nonce في AEAD التي تستعيد النص العادي؛ يَجِب على المُنتجين معاملة أزواج (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 هو إذن رأس الخريطة بطول محدَّد لخريطة من 4 إدخالات (0xA4).
13.4 ربط AAD بـ qub_id
يربط الغلاف qub_id بوصفه بيانات AEAD المصادَق عليها إضافيًا. هذا هو الدفاع البنيوي الحامل للحمل ضد ثلاث فئات من الهجمات:
| الهجوم | الدفاع |
|---|---|
نقل نص مشفّر تحت حقل qub_id مختلف في الغلاف |
عدم تطابق AAD ← تفشل مصادقة AEAD |
| خلط جزء URL لـ 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
I := canonical_cbor_decode(S) as SealedQub
require I.qub_id == Q // reject mismatched caller AAD
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.5
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
S := canonical_cbor_decode(P) as SealedQub
require S.qub_id == O.qub_id // explicit inner/outer cross-check
return P // P is the validated SealedQubCbor
انهيار وضع الإخفاق. K خاطئ، nonce خاطئ، عدم تطابق AAD، ونص مشفّر معبوث به تُنتج جميعًا نفس الخطأ DECRYPT_FAILED. هذه خاصية AEAD مقصودة: التمييز بين وضع الإخفاق سيُنشئ قناة جانبية يستطيع مهاجم بعيد سبرها بإرسال أغلفة مشوَّهة وحساب زمن الاستجابة. يَجِب على التنفيذات المرجعية طيّ كل إخفاقات AEAD إلى شكل خطأ واحد.
13.6 مادة المفتاح والتوزيع
مفتاح التغليف K هو قيمة عشوائية موحَّدة بطول 256 بتًا تُولَّد لكل qub بواسطة CSPRNG. تستمدّه التنفيذات المرجعية من:
- منشئ WASM:
getrandom(WebCrypto تحت الواجهة الخلفيةwasm_js). - مستدعي API الختم على جانب الخادم: الـ CSPRNG المحلّي الخاص به؛ يُقدّم المُستدعي
Kويحتفظ به كـwrapper_key_b64url. يستخدم الـ Worker المفتاحKفي الذاكرة للغلاف لكن يَجِب ألّا يستمرّ به. يتيح هذا لإعادة محاولة مُتماثِلة الأثر (idempotent) استعادةَ استجابة محجوبة باستخدام القدرة التي احتفظ بها المُستدعي بدلًا من الاعتماد على سرٍّ مُولَّد على الخادم يُستخدَم لمرّة واحدة.
التوزيع: يَجِب ترميز K كـ base64 آمن للـ URL (RFC 4648 §5، بلا حشو) وإلحاقه برابط التَّسْليم كمكوّن الجزء:
delivery_url = <origin>/c/<arweave_tx_id>#<base64url(K)>
لا يُنقل الجزء أبدًا إلى أي خادم بواسطة متصفح مطابق. قنوات الاستعادة (فهرس السجل على جانب الخادم، الإرسال التلقائي الاختياري بالبريد الإلكتروني) التي تستمر بحفظ كامل رابط التَّسْليم — بما في ذلك الجزء — وراء جهاز المستخدم هي مقايضة صريحة ضد الوضعية الافتراضية لتمزيق التشفير ويَجِب أن تكون مشروطة بموافقة المستخدم الصريحة.
فقدان الجزء. إذا فقد المستخدم جزء URL وليست لديه قناة استعادة، يكون qub غير قابل للقراءة. هذه هي المقايضة الحاملة للحمل في التصميم ويَجِب الإفصاح عنها للمستخدم وقت الختم. يُعزّز الـ MVP الإفصاح وقت الختم بنصّ صريح "احفظ هذا الرابط" وقناة استعادة ببريد إلكتروني مُتحقَّق منه للمستخدمين الذين يختارون التفعيل.
13.7 خارج نطاق هذا القسم
- توقيع التأليف (§9) لم يتغير: تُحسب التوقيعات داخل
QubEnvelopeالداخلي وتُستعاد بعد فكّ الغلاف ← فكّ تشفير tlock ← تحليل CBOR. - تشفير المفتاح العام للمستلم (حقل
recipient_pubkeyالمحجوز) ميزة مستقبلية مختلفة عن وضع الغلاف الخاص الحالي المحكوم بقدرة الرابط. - يُصدر تدفّق التوقيع المشترك على المواثيق الحالي من جانب الخادم
SealedQubCborعامًا/مجرّدًا بظهور0x01؛ ولا يستطيع تحقيق نموذج سرية K الخاص بالمتصفح لأن الختم النهائي يحدث بعد توقيع مشترك بوساطة الخادم. ويجوز لمنتج ميثاق خاص مستقبلي استخدام الغلاف نفسه، وهو أعمى بايتيًا عن نوع المحتوى الداخلي.
13.8 qubs العامة (حذف الغلاف)
الغلاف الخارجي اختياري في طبقة التَّسْليم. يجوز للمُنْشِئ ختم qub بوصفه عامًا، وفي هذه الحالة يدخل SealedQubCbor القانوني مسار التخزين مباشرةً، دون طبقة OuterWrapper ودون مفتاح K:
SealedQubCbor bytes ──(public)──▶ stored as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ stored
qub العام مُقفل زمنيًا لكنه غير محكوم بالرابط: يبقى غير قابل للقراءة حتى تنشر جولة drand الخاصة به (طبقة tlock لم تتغير)، لكن بعد فكّ القفل يستطيع أي شخص يملك معرّف معاملة التخزين فكّ تشفيره — لا يلزم جزء URL، إذ لا وجود لـ K. هذه هي المقايضة المقصودة للأسطح التي يَجِب أن يقودها الخادم: رسائل إشعار الكشف بالبريد الإلكتروني، وروابط oEmbed/التضمين التلقائي الخالية من الأجزاء، وتحسين محركات البحث الأغنى بعد الكشف — كلها تحتاج إلى رابط يعمل دون سرّ لا يحوزه الخادم أبدًا (§13.6). ولا يزال بوسع qub خاص استخدام الصيغة الصريحة <qub-embed src="full_delivery_url"> عندما يوفّر الناشر قدرته الكاملة الحاملة للجزء.
عواقب يَجِب على المُنتِج أخذها في الحسبان:
- لا حصانة تعداد. تتخلّى qubs العامة عن خاصية حصانة التعداد في §13.1 بحكم البناء. تَختِم خدمة الرفع المرجعية وسم تخزين دائم
Visibility: publicعليها (وعليها وحدها) لتكون قابلة للاكتشاف عمدًا؛ لا تحمل qubs الخاصة أي وسم كهذا وتحتفظ بعدم تمييزها البايتي. - عنوان النص العادي مكشوف وقت الختم. حقل
titleفي §3.2 هو نص عادي داخلSealedQubCbor. تحت الغلاف يبقى مخفيًا حتى يُقدّم المشاهدK؛ دون الغلاف يكون قابلًا للقراءة عالميًا على التخزين الدائم منذ لحظة الرفع، قبل فكّ القفل. يَجِب على تطبيقات المُنْشِئ المطابقة الإفصاح عن ذلك وقت الختم. - الكشف بنيوي ويُفحص تقاطعيًا. يميّز المشاهد/التضمين المطابق بين شكلي التخزين بالتحليل: البايتات التي تُحلَّل بوصفها
OuterWrapperتسلك مسار فكّ الغلاف بـK؛ والبايتات التي تُحلَّل بوصفهاSealedQubCborمجردًا تُقبل مباشرةً. ويَجِب أن تتفق القيمة الداخلية المستعادة (0x00للمغلّف/الخاص، و0x01للمجرّد/العام). ولا يربط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 = 4695446 (= floor((1736294400 - 1595431050) / 30) + 1, §4.3 mapping, 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—current protocol v1):
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)
0x000000000047A596 || // drand_round as u64 big-endian (4695446)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 4a84e3dfaec32954949c30073f8e6506
fd3204c1bb97f9162b81c7587afe412e
يَجِب على التنفيذات إنتاج قيم body_hash وqub_id متطابقة لهذا المدخل. يَنْبَغي أن يكون متّجه الاختبار هذا أول اختبار وحدة يُكتب. حُسبت القيم القانونية أعلاه بواسطة التنفيذ المرجعي ويَجِب أن تتطابق بتًا ببتٍ. استخدمت تخطيطات نموذجية تاريخية سابقة للإطلاق (لم تعتمد qubs حية على أول اثنين) 92 بايتًا قبل outcome_at (3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0) و100 بايت بعد إضافة outcome_at_or_zero (b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed). ثم أضاف التخطيط الحالي ذو 108 بايتات drand_round وفاصل النطاق QUB_ID_V2. واستخدم متجه مبكر ذو 108 بايتات ربط الجولة القديم ceil (drand_round = 4695445) فأنتج 3a9fcb31b750d985c262fada6d4f777fd6a28be831d941d85c131f5a4bbaf8a4—وهو qub_id صالح لذلك المدخل، بينما يتبع المثال أعلاه ربط الجولة الحالي في §4.3.
14.2 ربط جولة فكّ القفل
Input:
unlock_at = 1735689600
chain_genesis_time = 1595431050
chain_period_seconds = 30
Calculation:
(1735689600 - 1595431050) / 30 = 4675285.0
floor(4675285.0) + 1 = 4675286
drand_round = 4675286
تُنشر الجولة 4675286 عند 1595431050 + (4675286 - 1) * 30 = 1735689600—بالضبط عند unlock_at، لا قبله أبدًا. (أعطى ربط ceil القديم السابق للإصدار الجولة 4675285، المنشورة عند 1735689570—مبكرًا بـ 30 ثانية؛ ويقبل المتحققون تلك الجولة القديمة وفق §4.3.)
14.3 جولة CBOR قانونية ذهابًا وإيابًا
يَجِب على التنفيذات التحقق من أن serialize(parse(serialize(qub))) == serialize(qub) لكل المدخلات الصالحة. هذا اختبار خصائص، لا متّجه واحد.
14.4 PactTerms CBOR (content_type 0x03)
Input:
pact_version = 1
title = "Scooter deposit"
terms = [
{ key: "Item", value: "Honda Metropolitan scooter" },
{ key: "Price", value: "$100" },
{ key: "Deposit", value: "$10" }
]
party_a = { label: "Alice" }
party_b = { label: "Bob", contact: "bob@example.com" }
notes = absent
Canonical CBOR key order (PactTerms):
"notes"(6) < "terms"(6) < "title"(6) < "party_a"(8) < "party_b"(8) < "pact_version"(13)
Canonical CBOR key order (PactTerm):
"key"(4) < "value"(6)
Canonical CBOR key order (PartyIdentifier):
"label"(6) < "contact"(8)
تُحسب بايتات CBOR القانونية وbody_hash لـ SHA3-256 بواسطة التنفيذ المرجعي. يَجِب على التنفيذات إنتاج CBOR متطابق بايتيًا لهذا المدخل.
كما يَجِب على التنفيذات التحقق من أن serialize(parse(serialize(pact))) == serialize(pact) لكل مدخلات PactTerms الصالحة (اختبار خصائص).
14.5 متّجهات الغلاف الخارجي عبر اللغات
يحتوي الغلاف الخارجي (§13) على تثبيت قانوني منفصل في crates/qub-core/tests/vectors/wrapper_v1.json. تثبت كل حالة رباعية (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).
يثبّت التثبيت حاليًا ثلاث حالات غلاف منخفضة المستوى. تختبر هذه الحالات ترميز OuterWrapper الحتمي وقابلية تشغيل AEAD بين التطبيقات بمعزل عن ثابت شكل التسليم في §13.8؛ وعلى وجه الخصوص، لا يجعل الاسم التاريخي basic-text-public ولا قيمة visibility = 0x01 الداخلية البايتات المغلَّفة تسليمًا عامًا مطابقًا. يجب على المُنتِج مع ذلك تخزين البايتات الداخلية العامة عارية، وألا يغلّف إلا البايتات الخاصة (0x00).
| الحالة | التغطية |
|---|---|
basic-text-public |
اسم تاريخي لمُثبّت منخفض المستوى. أصغر شكل واقعي لـ SealedQub، بلا حقول اختيارية؛ يختبر بايتات الغلاف فقط وليس تسليمًا مخزّنًا مطابقًا لـ §13.8. |
with-recipient-pubkey |
SealedQub مع تعيين recipient_pubkey (مسار مستقبلي محجوز). يختبر مجموعة مختلفة من مفاتيح CBOR الداخلية؛ وينتج محتوى المُثبّت المختلف بذاته qub_id مختلفًا (ولا يدخل recipient_pubkey نفسه في الصورة السابقة في §4.1). |
longer-body |
جسم بحجم ~4 KiB — يُمارس بادئات طول CBOR متعددة البايتات داخل كل من الغِلاف الداخلي والنص المشفّر الخارجي. |
يَجِب على التنفيذات إنتاج expected_wrapper_hex متطابق بايتيًا للمدخلات المُسجَّلة. تتطلب إعادة توليد التثبيت 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). تحجز قاعدة الشيفرة0x02لـ Ed25519، لكن البروتوكول v1 لا يفعّله؛ يَجِب على مُتحقِّق v1 رفض كلsig_algخارج{0x00, 0x01}. - القفل الزمني: drand quicknet فقط — تجزئة السلسلة والمفتاح العام ووقت التكوين والفترة هي معاملات شبكة ثابتة يحملها
DrandTimelockProvider::quicknet()المرجعي (crates/qub-core/src/tlock.rs) وconfig/drand-endpoints.json. - الغلاف الخارجي: AES-256-GCM v1 فقط (§13).
يُرسّخ المُتحقِّقون حاليًا أطوال المفتاح والتوقيع لكل بدائية نشطة. وبايتا sig_alg وإصدار الغلاف محدِّدان صريحان، لكن v1 لا يجري تفاوضًا ضمن النطاق ولا يقبل إلا القيم النشطة أعلاه.
15.2 الشكل المنشود
عندما تدخل خوارزمية ثانية إلى البروتوكول، سيُهيَّأ المُتحقِّق لـ CryptoProfile مُسمّى (مثل ExqubV1) يسرد المجموعة الدقيقة للقيم المسموح بها لكل عنصر أولي — sig_algs، سلاسل drand، إصدارات الغلاف، أنواع المحتوى. يُثبَّت الملف وقت التحقق، ولا يُتفاوَض عليه ضمن النطاق. أي قيمة خارج الملف النشط تُرفض.
يضمن هذا أن إضافة ML-DSA-87 أو تفعيل Ed25519 لا يمكن أن يُضعفا تكوينات المُتحقِّقين القائمة بأثر رجعي: مُتحقِّق v1 يبقى مُتحقِّق v1 حتى بعد نشر ملف v2.
15.3 شروط التفعيل
ارفع §15 إلى حالة معيارية عندما يُقترح أي مما يلي:
- بايت
sig_algثانٍ (تفعيل Ed25519، ML-DSA-87، أو أي إدخال جديد في سجل §9). - سلسلة drand ثانية في الاستخدام الإنتاجي.
- إصدار غلاف خارجي ثانٍ.
- تدوير جذر ثقة سجل الشفافية—عنوان
LogProfile.anchor_ownerأو المفتاح العام المثبّت لمفتاح الإيصال (§16.6). ينضمLogProfileإلى سطح الملف في §15.2 كبدائية محكومة: التدوير رفع موقّع لـLogProfileيُشحَن في تحديث للمتحقق (توقّع التدويرات المخططة الصادر ← الوارد؛ ولا تستطيع التدويرات المدفوعة باختراق فعل ذلك، فتعتمد على هذا الرفع مع فحص تفرع المرساة السابقة لتقييد الضرر المؤقت). وتتطور مساحات إصدارات سجل الشفافية (LOG_VERSION، وANCHOR_FORMAT) كأشقاء مستقلين، تمامًا كما يستقل إصدار الغلاف في §12.5 عن إصدار البروتوكول.
حتى ذلك الحين، §15 هو حافظ مكان يُثبّت شكل الهجرة كي تنزل PRs المستقبلية على هدف معروف بدلًا من إعادة التقاضي على سطح التفاوض من الصفر.
16. سجل الشفافية ومستويات المتانة (تم تنفيذه — اكتملت المراجعة)
الحالة. هذا القسم تم تنفيذه (W5/UP-B1، المراحل 1–8)، مع تحديد نطاق المنتج ونطاق جذر الثقة هنا. تنسيقات الأسلاك، التجزئة، ومسارات التحقق نشطة: أنواع Merkle + CBOR الكانونية الأساسية (
qub-core)، مرآة TypeScript + حزمة ANS-104 (workers/api/src/crypto/)،LogDOكاتب واحد + مخزن عقدة R2 بمفتاح الإحداثيات، محاولة إضافة/uploadlog-appt، كرونات المذكور اليومي + تجميع البنجر، نقاط النهايةGET /api/v1/qub/:tx_id/proof(الإدمن) وGET /api/v1/log/consistency(RFC 9162)، إثبات التضمين المطبوع المحمول في حزمة.qub(§17.5)، وجهاز التحقق الأصلي من المرساة ANS-104 (tools/qub-verify)، وخطاف الرأسين المنشورين ذاتيا (§16.6)./uploadالناجح دائما ما يكون مدوما في R2 لكنه يغطي السجل فقط عندما يتم تكوينLOG_DOوينجح الإضافة الداخلية؛ عندها فقط يحمل ردهlog_seqreceiptوanchor_status. إذا كانRECEIPT_SKغائبا أو غير صحيح، فإنsig_b64urlذلك الإيصال يصبح فارغا ولا يوفر أي رفض للنفي. مسارات نشر/sealواتفاقية الحالية تجدول معاملات Arweave الفردية لكنها لا تضيف ورقة سجل. لا يوجد كود حاليا يقوم بتسوية لاحقة مقترحة لتعليق/uploadبعد فشل الإضافة. اكتملت مراجعة W5 الخارجية: §16.15 تسجل قرارات التصميم وقيود الإطلاق، لكن هذه القيود لا توسع تغطية المنتج المذكورة للتو. ثلاثة عناصر للثقة/النشر تبقى مغلقة: (أ) محفظة المرساة المخصصة (ANCHOR_JWK؛LogProfile.anchor_ownerلا تزال[0xAB; 32]المؤقتة); (ب) مفتاح توقيع الإيصال ورمز المفتاح العام المطابق (RECEIPT_SKاختياري وLogProfile.receipt_pubkeyفارغ حاليا); و(ج) مستودع GitHub المنشور ذاتيا + الرمز (§16.6). حتى يتم توفير دبابيس المثبت/الملف الشخصي، يقوم المتحقق المستقل بالإبلاغ عن حالة الإثبات بصدق بدلا من الادعاء بالتحقق المثبت والمثبت بالكامل. التصميم مضاف تماما ولا يوجد تغيير في صيغةSealedQub/QubEnvelopeالأسلاك.
16.1 المنطق ومستويات المتانة
تفصل مسارات النشر الحالية التأكيد عن تأكيد Arweave: فهي تستتق وتوقع معاملة فردية، وتستمر في حالة الإرسال الدقيقة والحالة الدقيقة في R2، ثم تنشر بشكل غير متزامن. يضيف سجل الشفافية طبقة ترتيب مستقلة لمجموعة طلبات /upload العامة التي ينجح LogDO إضافتها:
| المستوى | الاسم | الضمان | متى |
|---|---|---|---|
| T1 | R2-إقرار متزامن أول | حد التحمل — يتم كتابة البايتات المختومة وحالة النشر الدقيقة إلى التخزين الدائم قبل إرجاع النجاح. | تم التطبيق عبر مسارات النشر الحالية. |
| T2 | إدراج سجل الشفافية بشكل مجمّع | التزام للكتابة فقط يظهر أي تلاعب + ترتيب كامل بمجرد إدراجه والتثبيت. | المنتج الحالي: الإضافات الناجحة من LogDO من /upload؛ الاستجابة تحمل زوج الإيصال. ليس عالميًا. |
| T3 | ديمومة كل qub على Arweave | معاملة Arweave فردية للـ qub. | حاليًا مُعدة لكل نشر مقبول ويتم نشرها بشكل غير متزامن؛ المعاملة الموقعة الدقيقة تبقى في صندوق الصادر القابل للتصريف حتى يتم التسليم. |
تصف المستويات خصائص الأدلة والديمومة المميزة، وليس الخطة التجارية الحالية. لا يزال الكود الحالي يبرمج معاملة Arweave فردية لكل نشر مقبول؛ لا يعرض T3 فقط كخيار مدفوع. تظل حدود الحصة لمفتاح API/الحساب منضبطة بواسطة التطبيق بشكل منفصل.
أمان الديمومة. كتابة T1 متزامنة، لذا فإن الاستجابة الناجحة تؤسس الديمومة على مستوى التطبيق دون انتظار بوابة Arweave. لم تحدد هي نفسها طابعًا زمنيًا مستقلًا. توفر المعاملة الفردية المؤكدة حدود الوقت العليا للكتلة. بالنسبة لاستجابة تحمل زوج الإيصال الكامل لـ T2، يمكن للمرساة المؤكدة التالية أن توفر دليل السجل الموصوف أدناه. إذا لم يكن الزوج موجودًا، فلا يمكن لأي سطح أن يوحي بأن هذا qub موجود بالفعل في سجل الشفافية. لا يوجد اتفاق مستوى الخدمة الرقمي على بروتوكول المؤشر والنشر.
16.2 هيكل LogLeaf (شكلان مثبتان)
سجل الدخول هو LogLeaf، مشفر بصيغة CBOR القانونية المكتوبة يدويًا وفقًا لملف §3.1 (طول محدد، بدون وسوم، بدون أرقام عشرية، أعداد صحيحة بأقصر صيغة، نصوص بتكويد NFC، حقول اختيارية تُحذف عند غيابها، المفاتيح مرتبة حسب طول البايت المشفر تصاعديًا ثم حسب البايت). يتم تطبيق الحماية القانونية parse → re-encode → compare وفق §3.1 على مسار التشفير قبل التجزئة (وليس فقط عند فك التشفير)، لذلك لا يمكن لتطبيقين مختلفين أن يختلفا في بايتات الأوراق بسبب فرق عرض الأعداد أو ترتيب المفاتيح. جميع الأعداد صحيحة u8 / u64 / i64؛ جميع الملخصات (digests) هي سلاسل بايت 32-بايت (bstr[32]). معرف معاملة Arweave المخزن هو ملخص SHA-256 خام مكون من 32 بايت، يُحمل بصيغة bstr[32]، وليس أبدًا سلسلة نصية base64url (يتطابق مع §3.3).
تأخذ الورقة شكلين يتم اختيارهما بواسطة بايت kind، لأن مسار التحميل العام لا يميز نوع البايت: POST /api/v1/upload يتعامل بشكل متعمد مع كلتا صيغتي الحمولة المقبولة على أنهما غامضتان ويتلقى qub_id وunlock_at فقط كـ ادعاءات عميل غير موثوقة. في المسار الخاص الافتراضي، body_hash وdrand_round وcreated_at وdrand_chain_version تكون مخفية أيضًا داخل الغلاف الخارجي §13، الذي لا يحتفظ العامل بمفتاحه. كما يعرف نظام النوع شكلًا مثبتًا للمنتج الذي يستنبط body_hash / drand_round بنفسه. مسار /seal الحالي يحتوي على تلك القيم ولكنه لا يستدعي LogDO، لذلك الإنتاج حاليًا يصدر فقط أوراق مثبتة (0x02) من الإضافات العامة الناجحة. الانقسام يحافظ على صدق كل قيمة ملتزمة دون الادعاء بأن المنتج المثبت متصل بشكل مباشر:
| المفتاح | العنصر | نوع | الحضور | يعني |
|---|---|---|---|---|
seq |
4 | u64 |
يتطلب | مؤشر ورقة عالمي قائم على 0؛ الموقع الذي يلتزم به إثبات الشمول. |
kind |
5 | u8 |
تتطلب | 0x01 الشهادة (معرفة، غير صادرة حاليا) أو 0x02 التأكيد (ختم العميل / رفع أعمى البايتات). |
ref |
4 | bstr[32] |
المرجع الورقي. موثق → qub_id الخام. تم تأكيده → المعرف الأعمى SHA3-256(qub_id ‖ log_blind_secret) (§16.2.1). |
|
chash |
6 | bstr[32] |
المطلوبة | SHA3-256(stored_bytes) المحتوى — وهو الرابط الوحيد للمحتوى الذي يمكن للعامل دائما حسابه بصدق، على كلا المسارين. |
unlock_at |
10 | i64 |
يتطلب | نسخ (توثيق) أو تأكيد (تأكيد)؛ التحقق من صحة > 0 قبل دخوله الورقة. |
received_at |
12 | i64 |
يتطلب | ساعة الحائط للعامل عند R2-ack. غير دليلية (مؤكد من المشغل؛ §16.6). حاضر لوصف الذات، لا يوجد إثبات أبدا. تم التحقق > 0. |
body_hash |
10 | bstr[32] |
kind=0x01 |
حذفها فقط في 0x02 — حيث يفتقر العامل إليها بموجب المادة 13. |
drand_round |
12 | u64 |
kind=0x01 |
حذف فقط في 0x02. |
ورقة kind=0x02 لا تلتزم عمدا ب body_hash ولا drand_round: فهي تشهد الالتزام وترتيب النص المشفر المعتم عند chash عنوان المحتوى، مدعية qub_id و unlock_at — وليس نصه الأصلي أو الدائري. تأتي أرجل النص العادي/الدائري للكيووب المؤكد من التحقق الموجود في حزمة .qub §11، وليس من السجل (§16.11). drand_chain_version ليست في الورقة (هي داخل الغلاف في المسار الافتراضي)؛ تعيش دقة السلسلة على المرساة (§16.7). انضباط المشفر: رفض ref أو chash صفري بالكامل، ورفض unlock_at / received_at غير الإيجابية، مما يعكس حارس الحارس outcome_at > 0 في cbor.rs.
16.2.1 تعمية قوب خاصة
يجب ألا يصبح السجل هو Oracle العد الذي تهدف الغلاف الخارجي §13 لمنعه (§13.1). بالنسبة لقوب خاص (مغلف) فإن ورقة asserted تُلزم المُعرف المعمى SHA3-256(qub_id ‖ log_blind_secret)، حيث أن log_blind_secret هو سر محتفظ به من قبل الخادم، وتتجاهل body_hash. لا يمكن لطرف ثالث ربط مثل هذه الورقة بـ qub_id محدد؛ حامل القوب، الذي لديه عنوان التوصيل وبالتالي qub_id، يمكنه إعادة حساب التعمية لتأكيد إدراجه الخاص. القوب العام (القابل للعد بالفعل، ويحمل بالفعل وسم Visibility: public الخاص بـ Arweave وفق §13.8) يلزم الـ qub_id الخام. هذا هو المكان الوحيد الذي تتنازل فيه إمكانية التحقق المستقل عن ثباتية الخصوصية الحاملة للحمل؛ الارتباط المستقل للقوب الخاصة هو chash (§16.9).
log_blind_secret الحفظ (تم الحل — §16.15 Q4). التعمية تحمي عدم قابلية ربط الورقة، وليس سرية النص الصريح (فالـ §13 الغلاف يحتفظ بذلك بشكل مستقل). عند حدوث اختراق log_blind_secret، بالنسبة لأي qub_id يمتلكه الخصم بالفعل أو يمكنه إعادة بنائه (كل قوب يمتلك حزمه/عنوانه، بالإضافة إلى أي 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 غير متوازنة يسارًا بالكامل (كل انقسام داخلي عند أكبر قوة للعدد اثنين أصغر بصرامة من عدد أوراق الشجرة الفرعية)، مما يسمح بمشاركة أدوات إثبات الإدراج والاتساق خوارزمية مسار التدقيق واحدة. يحمل المعيار المرجعي رمزًا مزيفًا صريحًا للاشتقاق الأيسر/الأيمن ويثبت متجه اختبار غير قوة 2 (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 حقيقية وتتيح لمُرسٍ حديث واحد إثبات الإدراج لأي وحدة قديمة.
كائن LogDO الدائم هو الكاتب الوحيد (blockConcurrencyWhile، يعكس QuotaDO / EntitlementDO) — الإضافة إلى سجل مشترك هي قراءة-تعديل-كتابة على الحالة المشتركة وبالتالي يجب أن تتم عبر DO وليس KV. يقوم بتخزين حدود حافة الشجرة اليمنى (O(log n) تجزئات) بحيث يصبح إغلاق دفعة O(batch). الدفعة هي مجموعة الأوراق المثبتة معًا؛ مشغلاتها المطبقة هي تقدم tree_size بمقدار لا يقل عن LOG_BATCH_MAX_LEAVES (الإعداد الافتراضي 4096)، أو وصول العمر إلى تتابع المرسى، أو إغلاق صريح إداري/مجدول. root_i هو تجزئة شجرة ميركل التراكمية على الأوراق 0 .. tree_size_i.
16.6 رأس الشجرة الموقع عبر مرساة Arweave
معاملة مرساة Arweave هي رأس الشجرة الموقع وتستبدل توقيع المشغل لرأس الشجرة نفسه: المرساة اليومية لا تحتاج مفتاح وحدة لأن معاملة Arweave owner هي التوقيع. تنطبق فرضية الخندق — الركيزة غير القابلة للتغيير، وليست سر مملوك لوحدة، هي الحاملة للجذر المثبت.
تصميم السجل يتطلب مفتاح استلام مضاف ناجح دافئ واحد (§16.10)، مثبت في LogProfile وموقع متقاطع بواسطة anchor_owner. التنفيذ الحالي لم يكمل توفير جذر الثقة هذا: RECEIPT_SK اختياري، والمفتاح الغائب/غير الصالح يؤدي إلى sig_b64url: ""، والـ LogProfile.receipt_pubkey المجمعة فارغة. يمكن للمستند المرفق أن يصف الورقة المضافة لكنه ليس استلامًا موقعًا لا يمكن إنكاره. ينطبق الادعاء التصميمي الأقوى فقط بعد أن يقوم الإصدار المحقق بتثبيت مفتاح عام مطابق ويوقع مالك المرساة عليه بشكل متقاطع. استجابة النشر بدون مجموعة الاستلام الكاملة لا تصدر أي ادعاء بقبول السجل؛ بينما استجابة بتوقيع فارغ تصدر ادعاءً بموقع الإضافة ولكن لا تصدر ادعاءً بالتحقق من التوقيع.
SignedTreeHead هو CBOR القانوني (المفاتيح حسب الطول المشفر): size:u64، root:bstr[32]، batch:u64، prev:bstr[32] (السابق sth_hash؛ الأصل = 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 — إلى جانب ثوابت الشبكة السريعة الموجودة بالفعل في DrandTimelockProvider::quicknet() — وتوزيعه مع الملف الثنائي للمتحقق (COMPRIVER). يجب على المتحقق أيضا التحقق من → tx_id ربط البيانات في Arweave tx محليا** بدلا من الوثوق ببوابة /raw/ الرد. هذا يغلق فجوة التشويش في المحفظة المارقة: "المثبت على Arweave" لا معنى له حتى يقوم المتحقق بتثبيت أي محفظة.
التدوير هو حوكمة §15 امتداد، وليس إعادة استخدام (تم حل — §16.15 Q3). سطح ملف §15.2 حاليا يعدد فقط sig_algs / سلاسل drand / إصدارات التغليف / أنواع المحتوى، ومحفزات §15.3 لا تذكر أيا من هذه — LogProfile / anchor_owner ليس بعد في سطح §15. لذا يجب بناء: §15.3 ممتد (أدناه) لإضافة LogProfile الزناد، والدوران هو انتفاخ LogProfile موقع تم شحنه في تحديث للتحقق. التدوير المخطط* يحمل توقيعا متقاطع → واردا صادرا؛ لا يمكن لتدوير **المفتاح الخارج غير موثوق/غير متوفر بالضبط حينها) ويعود إلى الحافة المحكوم عليها §15، مع فحص شوكة المرساة السابقة (أدناه) لتحديد الضرر في الوقت الانتقالي.
نافذة التداخل (معلمة الثقة من الدرجة الأولى). تكون الورقة مقاومة للتشويش فقط عندما يكون مرساة تغطيتها Arweave-مؤكد. النافذة هي received_at → anchor confirmation (الوتيرة + نهائية Arweave، بدون ضمان تأخير البروتوكول). قبل توفير جذر الثقة، يوفر التنفيذ الحالي سلامة تشغيل QUB بالإضافة إلى أي بيانات وصفية غير موقعة موجودة؛ لا يوفر ضمان عدم الرفض المخطط له. ثلاث عيوب مساءلة تحدد التصميم المكتمل (نموذج الشاهد هو §16.15 دقة Q2):
- إيصال ختم (يعتمد على التجهيز) — يتم إرجاع نظير SCT عندما ينجح ملحق سجل التحميل (§16.10). يصبح غير قابل للرفض فقط عندما يكون
sig_b64urlغير فارغ و يتم تثبيت علاقة المفتاح العام/المالك المتطابقة في المدقق. لا يمكن لرمز ملف الإنتاج الفارغ حاليا دعم هذا الحكم. هذا التحكم لا ينطبق على مجموعة إيصالات محذوفة أو إيصال غير موقع. - منهجية المراقبة المنشورة + سير السلسلة السابقة — سلسلة
prevالمرساة هي رأس مشي→نشأة؛ التفرع (مرساتين فيsizeواحد معrootمختلف، أوprevمكسورة) هو دليل قابل للنشر على سوء السلوك. اكتشاف التشويش هو التزام تشغيلي معلن، وليس افتراضا صامتا. - رؤوس منشورة ذاتيا — كل رأس جديد
{sth_hash, tree_size}ينشر في مستودع GitHub عام مخصص مملوك للكوب (الجانب الذاتي المنتشر الظاهر بالعبث الواضح)، مع منشور اجتماعي كتأكيد لأفضل جهد فقط. صفحة النشر الفاشلة يجب أن تكون صامتة (ليست صامتة). تم تنفيذها (المرحلة 8) كخطافpublishHeadعلى كرون الرابط (workers/api/src/utils/heads-publish.ts):PUTلواجهة برمجة المحتوى بدونshaتكون ملحقة فقط (422تعني أن الرأس منشور بالفعل، وليس استبدال); الاشتراك / الانتشار علىPUBLISH_HEAD_GITHUB_{TOKEN,OWNER,REPO}وغير فعال حتى يتم توفير المستودع. صفحات فشل صعبة في GitHub عبر قناةhealth_alertوترفع مقياس فشل دائم (m:tlog_publish_head_fail); مرساة Arweave نفسها لا تتراجع عن فشل النشر. "عدم فشل الصامت" مضمونة من خلال هذا المقياس الدائم — الذي يجب على المشغلين تنبيه لوحة القيادة عليه — حتى لو لم يتم تسليم صفحة البريد الإلكتروني بأفضل جهد. هناك قيدان صادقان يتبعان من "الرابط عند التقدم" (المكرر ينشر فقط عندما يزداد الحجم): فشل مؤقت في GitHub يترك فجوة في تسلسل الرؤوس المنشورة لهذا الحجم — محدودة، وليست صامتة (تظهر صفحات)، ولأن كل رأس يلتزم بشجرة فائقة، فإن إثبات الاتساق §16.9 يجسر الفجوة؛ والأهم من ذلك، أن إثبات الاتساق هذا يتم حسابه من شجرة الاعتماد المرتبطة ب Arweave، وليس من سطح GitHub*، لذا فإن فجوة GitHub لا تضعف أبدا قابلية التحقق. ملء التعويض الذي يملأ فجوات الرؤوس المنشورة هو تعزيز مؤجل.*
قيد الأمانة (قيد ملزم). لأن QUB يتحكم في سطحي النشر المخططين لها، فهذا منشور ذاتيا، وليس شاهدا مستقلا. لا يجوز لأي منتج أو تسويق أو سطح قانوني أن يدعي أن السجل "مشاهد بشكل مستقل". بعد توفير الإيصال/الملف الشخصي/بوابات الرأس، الادعاء المسموح به هو أن التشويش قابل للكشف وأن الملحق الموقع بنجاح يترك إيصالا غير قابل للنفي. قبل ذلك، هذا الادعاء غير متاح. يتم تأجيل شاهد مستقل حقيقي من طرف ثالث إلى زيادة حوكمة مستقبلية بموجب §15.
received_at يتم تأكيده من قبل المشغل ولا يمكن لأي مطالبة أن يعتمد عليه — لا يظهر أبدا كدليل أو كتأكيد نزاع على أي منتج / قانوني / واجهة برمجة تطبيقات / عرض إثبات. T زمن كتلة التثبيت في Arweave هو الطابع الزمني الوحيد الذي لا يعتمد عليه الثقة (وهو حد أعلى ل "تم تسجيله بواسطة"). أي فحص سلامة مراقبة على received_at يجب أن يقارن مع T، وليس مع حقل STH anchored_at الذي يتحكم به المشغل؛ مثل هذا الفحص هو حماية فقط ضد خطأ الساعة الخاص بالمشغل النزيه، ليس تحكم محاسبة ضد مشغل خبيث (§16.15 Q5).
16.7 صيغة وإيقاع معاملات المحور
AnchorBundle هو جسم معاملات Arweave الرسمي ل CBOR، مكتوب عبر المجمع §16.8: ver:u8، sth:bstr (بايت SignedTreeHead القانوني)، prev_anchor:bstr (بايتات التعريف الخام لتعريف المرساة السابقة؛ تم حذفها عند التكوينيس)، chain_hash:tstr (سلسلة drand في الغالب — الشبكة السريعة)، وتدفق الأوراق وCBOR الخاص بالدفعة بترتيب seq بحيث يكون المرساة مكتفيا ذاتيا: يعيد المشتعل اشتقاق root من الجسم بدون اعتماد على الكوب. (إذا أصبح تدفق الأوراق كبيرا عند حجم عالي، قد يلتزم التعديل المستقبلي بنطاق أوراق فقط بالمرجع؛ وقد تم تدوينه، ولم يعتمد في الإصدار 1.)
وسوم Arweave قابلة للتعداد عمدا — السجل مقصود أن يتم العثور عليه، على عكس الكيوبس الخاصة: App-Name: qub-tlog، Anchor-Format: 1، Log-Id: <hex>، Batch: <n>، Tree-Size: <n>، Root: <hex>، Prev-Anchor: <tx>، Content-Type: application/cbor. الوسوم هي تلميحات غير موثوقة؛ هيئة CBOR هي السلطة الوحيدة.
Cadence: يوميا بشكل افتراضي، يعاد النظر فيه مع الحجم (تفعيل الحجم يقلل تلقائيا من الإيقاع الفعال تحت الحمل). المنتج الحالي لا يطبق خطاف القوة المدفوع بالختم. محفظة المرساة مخصصة ومنخفضة السرعة، منفصلة عن محفظة الرفع — يجب أن تكون JWK خاصة بها (مفتاح مميز، وليس دورا منطقيا في محفظة الرفع) لذا لا يمكن لاختراق محفظة الرفع تشكيل مراسي — مع ميزانية معاملات مرساة صارمة يوميا. وضع الحفظ موضح بوضوح: مفتاح اختصار ضيق النطاق مع قاطع دائرة ضيق وتوازن منخفض، وليس "باردا" — محفظة توقع تلقائيا يوميا لا يمكن أن تكون باردة، والمواصفات لا تدعي خلاف ذلك.
16.8 ANS-104 Bundler
مشفر ANS-104 DataItem داخلي وتوقيع تجزئة عميقة، حوالي 300 خط، تشفير ويب فقط، صفر تبعيات 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
التوقيع هو Arweave deepHash — ملخص متكرر SHA-384 (متطلب الأسلاك في Arweave، crypto.subtle.digest("SHA-384")) عبر ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — ثم RSA-PSS عبر التجزئة العميقة مع المحفظة JWK عبر crypto.subtle؛ id = base64url(SHA-256(signature)). SHA-384 هنا محزن كبدائي فقط عبر أسلاك Arweave، وليس بدائي لثقة الكيوب (§15 يسجل السياج؛ وتجزئة صندوق الكوب هو SHA3-256 طوال الوقت).
يخدم مسار كود ANS-104 آلية الاحتياط/التصريف المؤجلة ويكتب AnchorBundle DataItems. ينشئ مسار النشر العادي أولا معاملة Arweave موقعة بدقة ويحافظ على JSON الخاص به في صندوق خروج متين؛ النشر المباشر هو تحسين للزمن، ويعيد مسار التصريف محاولة نفس المعاملة قبل تطبيق احتياطي الحزمة الخاص به. مخطط التوقيع (تم الحل — §16.15 Q8): علامات v1 مع RSA-PSS (نوع التوقيع 1) مع إعادة استخدام آلية JWK الحالية لمحفظة Arweave (صفر حفظ مفتاح جديد طويل العمر، مع تقديم أطروحة "سر واحد أقل"); تم تأجيل Ed25519 إلى مسار ترحيل PQ §15.
الرمز العميق اليدوي هو أعلى رمز مخاطرة وأقل تغطية طبيعية في W5، لذا فإن حدوده غير قابلة للتفاوض (§16.15 Q8):
- يغطي عنصر التثبيت متعدد اللغات
tlog_v1.json(Rust + TS، النمط §14.5wrapper_v1.json) التجزئة العميقة، بايتات DataItem + المعرف، تجزئات الأوراق، جذر مكون من 5 أوراق + مسار التدقيق، تجزئة STH، دليل الشمول، ودليل التناسق — في اتجاهي التوقيع والتحقق (اتجاه التحقق مهم لأن فحص tx → tx_id المحلي في §16.6 يسحب التجزئة العميقة إلى كل متحقق مستقل، وليس الكاتب فقط). - جولة تفاعل مرة واحدة من خلال جامع مرجعي ANS-104، يتم استخدامها كـ بيانات اختبار ثابتة فقط — وليست كمكتبة npm وقت التشغيل أبدًا (الموقف الخاص بـ Web-Crypto فقط / بدون سكريبتات تثبيت ثابت).
- يجب أن تمر مسار التجزئة العميقة + RSA-PSS عبر نفس الأساسيات
crypto.subtleالمستخدمة في الإنتاج، بحيث يكون الترميز الداخلي متوافقًا على مستوى البايت. - يضمن مراقب قبول ما بعد الحزمة الجاري أن كل DataItem للمرساة / النسخ الاحتياطي يحقق قبول Arweave فعليًا، مع إنذار + قاطع دائرة — لأن التجزئة العميقة تخدم أيضًا قائمة انتظار النسخ الاحتياطي عند عدم توفر 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) هو مجموعة من O(log N) R2 مباشرة GET مع بدون إعادة حساب عبر حدود الدفعات; أما مع المخزن المدفوع بالدفعات فلا يكون كذلك، وهو فجوة ترتيب التخزين التي يغلقها هذا الدقة. أجسام الأوراق قابلة أيضا لعناوين المحتوى بواسطة seq. يجب على ناقل اختبار W5 إثبات وجود ورقة باردة من حقبة النشأة ضد جذر لاحق بكثير باستخدام R2 + Arweave فقط مع مسح تخزين LogDO، لذا فإن ادعاء السلامة في الاستصلاح في §16.13 مدعوم بدلا من تأكيده. تنتمي الناقلات التسلسلية O(log N) ل R2 GET إلى فقط على نقطة النهاية اللامتزامنة — لا على مسار الختم الساخن (§16.10) أو كرون لكل نكتة.
16.10 R2-ترتيب الهجوم الأول
تسلسل POST /api/v1/upload المنفذ هو:
- بوابات النصف الأمامي (التصديق، التحقق، مفتاح شظية الهواء) — دون تغيير.
- إنشاء ووضع علامة وتوقيع على معاملة Arweave الفردية بالضبط. يتم اشتقاق
tx_idمحليا، رغم أن إنشاء المعاملة قد يجلب بيانات المكافأة/المرساة من البوابة. فشل التحضير لا يزال يفشل في الطلب قبل التأكيد. - اكتب العنصر المختار بشكل متزامن عند
qub-cache/<tx_id>وحافظ على سجلات عملية الإنشاء/صندوق الخروج المستقرة. هذه هي مستوى المتانة وإعادة المحاولة؛ الفشل قبل عودة التسوية 503. - عند تكوين
LOG_DO، محاولة متزامنةLogDO.append(leaf). يقوم الكاتب الوحيد بتعيينseq، ويمدد سلسلة الإدخال، ويحدث الحدود. يقوم RPC الملحق بذلك فقط؛ يتم إغلاق الدفعة خارج المسار على الإنذار. فشل النقل/التطبيق في الإلحاق هو حاليا fail-soft: يمكن للاستجابة النجاح بدونlog_seqأوreceiptأوanchor_status. على الرغم من تعليق التنفيذ، لا يتم توصيل السجل التلقائي لاحقا اليوم. - إعادة التأكيد. تضمين
{ log_seq, anchor_status: "pending", receipt }فقط عندما يعيد الملحق المجموع الناجح بالكامل.receipt.sig_b64urlفارغ عندما يكون موقع الإيصال غير متاح؛ يجب على العملاء ألا يعتبروا تلك القيمة موقعة أو غير قابلة للنفض. غياب التوبل يعني النشر الدائم فقط، وليس قبول سجل الشفافية. - استخدم مهمة مؤجلة واحدة لنشر المعاملة الموقعة بالضبط. النجاح يزيل صندوق الخروج؛ يترك الفشل الصندوق الصادر للمصرف المحدود ولا يجب تغيير
tx_idالمعترف به مسبقا. البيانات الوصفية المؤقتة وغيرها من الكارات الجانبية التي تتطلب أفضل جهد تؤجل أيضا.
حد الكمون. يشمل مسار الطلب عمل السلطة/الحصة في النصف الأمامي، وإعداد/توقيع المعاملة، وكتابات R2 الدائمة، و(عند الإعداد) محاولة LogDO. يظهر < 300 ms في مراجعة التصميم كهدف عملي، وليس كضمان بروتوكولي؛ قد يقوم خطوة إعداد المعاملة الحالية بتنفيذ طلب بيانات الجهة الوسيطة. إن تنبيهات الكمون وبوابات الإطلاق هي ضوابط تشغيلية، وليست دليلاً متاحاً للمدقق.
16.11 نموذج الثقة — المطالبة الدقيقة، محدودة بنوع الورقة
بالنسبة إلى kind=0x01 (موثق): "تم الالتزام بهذا المحتوى — الجسم مطابق لـ body_hash، محدد بواسطة qub_id — في سجل qub الذي يُضاف فقط عند الموضع seq وكان موجودًا في أقصى حد عند وقت كتلة Arweave T؛ وكان غير قابل للقراءة بطريقة تشفيرية حتى جولة drand R = unlock_round(unlock_at)." هذا هو الثلاثي الكامل {tlock round binding + Merkle inclusion + anchored root}.
بالنسبة إلى kind=0x02 (مؤكد، الافتراضي): "تم الالتزام بشيفرة مغلقة بمحتوى chash، تدعي qub_id و unlock_at، في سجل الإضافة فقط عند الموضع seq وكان موجودًا في أقصى حد عند وقت كتلة Arweave T." يتم توفير أجزاء الجولة والجسم بواسطة التحقق الحالي لحزمة §11 .qub (qub_core::unlock)، وليس بواسطة السجل؛ وما يضيفه السجل فوق المعاملة الفردية لكل qub هو الترتيب المظهر للتلاعب، والالتزام الأعلى بدون ثقة، ومقاومة التناقض.
كلا المطالبتين تستبعدان، بحسب §11: التأليف بدون sig_alg ≥ 0x01، النية، وتوقيت دقة ما دون المرساة. لا يسمح أي منهما لأي مطالبة بالاعتماد على received_at.
سقف المطالبة (قيد الإطلاق الملزم — محلول §16.15 سؤال 1). بالنسبة إلى ورقة مؤكدة (kind=0x02)، المطالبة المحدودة أعلاه هي السقف لما يمكن لأي منتج أو تسويق أو شروط أو واجهة عرض الأدلة التأكيدية أن يدعيه. لا يجوز لأي واجهة أن تقول أو توحي بأن السجل يثبت المحتوى أو جولة إلغاء القفل لتحميل أعمي للبايت — السجل يثبت الترتيب + الالتزام الأعلى بدون ثقة لشيفرة مغلقة. إثبات المحتوى والجولة يأتي حصراً من التحقق الحالي لحزمة §11 .qub، المستقل عن السجل. المنشور الذي لم ينجح في الإضافة/الإيصال ليس له أي مطالبة سجلية على الإطلاق.
16.12 النسخ والتنسيق مع W3
لا يوجد نتوء سلكي SealedQub وبالتالي لا يوجد انتفاخ في إصدار البروتوكول (§12.2): السجل هو رمز جانبي يلتزم بحقول وبايت موجودة، لذا لا يدخل تاريخ إصدار البروتوكول §12.3. drand_chain_version الاختياري ل W3 لم يلمس ويبقى الحقل الاختياري الوحيد SealedQub. بدلا من ذلك، يقدم السجل فضاءات إصدارات مستقلة خاصة به — LOG_VERSION_1، ANCHOR_FORMAT_1، InclusionProof.ver — تعكس استقلالية إصدار §12.5 (يحمل الغلاف بايت إصدار مستقل عن نسخة البروتوكول، وتتبع نسخ السجل نفس الفصل).
يتم جلب الدليل افتراضيا، مع إمكانية الركوب الاختياري. لا يمكن أن يوجد إثبات في وقت الختم (لم تكتب المرساة بعد)، لذا تبقى حزمة .qub وقت الختم خالية من الإثبات. يجلب التحقق في W7 GET …/proof مرة واحدة، أو في وضع عدم الاتصال الكامل يعيد بناء الإثبات من AnchorBundle العامة عبر استعلام Arweave على Log-Id. حزمة .qub (W7) تحتفظ بعضو inclusion_proof اختياري** — غائب عند الختم، ويتم تعيينه إعادة تصدير بعد التثبيت للأرشفة الباردة — وتتبع نفس نمط "اختياري، محذوف افتراضيا، إضافي" كما في drand_chain_version W3.
16.13 الاحتفاظ
نوافذ الاحتفاظ للذيل المفتوح ل LogDO، وركيزة R2 التي تقدم الإثبات، وعدادات قاطع الدوائر المرساة، وقائمة الانتظار الاحتياطي للحزم محددة في docs/DATA-RETENTION.md. المبدأ: التخزين الساخن لكل دخول (LogDO) للسجل قابل للاسترداد بعد التثبيت؛ مادة التدقيق الخاصة به — مخزن عقدة ميركل (level, index) بمفتاح الإحداثي + أجسام الأوراق المعنونة seq (§16.9) + مراسي Arweave — دائمة. استعادة ورقة باردة من DO لا تبطل البرهان الصادر، لأن البرهان يحل ضد مخزن عقدة R2 الدائم ومرساة Arweave، وليس ضد DO (ومتجه اختبار DO §16.9 يثبت ذلك).
16.14 متجهات الاختبار
يشحن W5 tlog_v1.json التثبيت عبر اللغات (§16.8) بالإضافة إلى المتجهات المحلولة: → leaf_hash kind=0x01 وورقة kind=0x02؛ جذر الخمس أوراق؛ إثبات تضمين واحد؛ إثبات اتساق واحد؛ AnchorBundle واحد؛ ومعرف DataItem واحد. هذه الوحدات تعيش جنبا إلى جنب مع متجهات الغلاف الخارجي §14.5 وتمارس بواسطة كل من تنفيذ Rust (qub-core) وTypeScript (Worker).
16.15 قرارات مراجعة (W5 — تم حله)
اكتملت مراجعة W5 الخارجية (تصريح تصميم خصمي + توقيع المالك). يتم تسوية كل قرار أدناه وينعكس في نص §16 أعلاه؛ يتم إعادة صياغة قيود الإطلاق الملزمة في النهاية. يمكن المضي قدما في التنفيذ بخلالها.
صدق الورقة في المسار الافتراضي (
kind=0x02) — تم الحل. أرسل تقسيم النوع ذو الورقات كما هو محدد:kind=0x02لا يلتزم بbody_hashولاdrand_round. لا يوجد حقل*_body_hashعلى مسار الأعمى للبايت (سيكون أكثر إشارة "مؤكدة" خاطئة وواضحا للمتكاملين وهو راحة توفرها المادة 11 بالفعل من الحزمة). لا يتطلب لا ختم الخادم للكواربات المعتمدة من السجل (الذي سيدفع النص العادي عبر العامل ويدمر خندق تمزيق التشفير). أي دائرة قصيرة تصف نفسها تنتمي إلى حزمة.qub/ ظرف الإثبات كحقل معاد حسابه من قبل المحقق، وليس حقل ورقة. سقف المطالبة المؤكد من المالك: §16.11.التشويش / المساءلة عن الإغفال — تم حل التصميم، التوفير غير مكتمل. يتطلب التصميم تثبيت مفتاح الختم والاستلام في
LogProfileوتوقيعه من قبلanchor_owner، بالإضافة إلى منهجية المراقبة، والمشي السابق، ورؤوس مزدوجة منشورة ذاتيا. الملف الشخصي المجمع وخطافات النشر لا تزال مجرد بدائل/اختيارية كما هو موضح في §16.6، لذا فإن الادعاء الأقوى قابل للاكتشاف + المستلم لا يكون ساريا حتى تغلق تلك البوابات. يجب ألا يتم تسويقه أبدا كشاهد مستقل. يتم تأجيل الشاهد الحقيقي من طرف ثالث إلى زيادة الحوكمة بموجب §15.جذر الثقة المثبت بين المالك والمرساة + الدوران — تم الحل. اعتماد دبوس
LogProfile(§16.6); يقوم المتحقق بفحصanchor_tx.owner == anchor_ownerويتحقق من ربط بيانات الإرسال → tx_id محليا. حوكمة التدوير هي §15 امتداد للبناء (تمت إضافة محفز §15.3)، وليست إعادة استخدام؛ الدورانات المخطط لها تعود إلى نقطة الربط §15 مع فحص الشوكة لتلف الحدود.تعمية ورقة الكويب الخاص — تم الحل. استمر في تعميم الكوب الخاص (
ref = SHA3-256(qub_id ‖ log_blind_secret))،qub_idخام للكيوبس العامة (بالفعل §16.2.1)،chashكرابط مستقل.log_blind_secretهو ارتباط سري/درجة سيبيل، يدور للأمام فقط (§16.2.1).received_at— تم الحل. الاحتفاظ بها في الورقة، مرتبة ولكن غير دليل صريح؛ لم تظهر أبدا كدليل أو تأكيد للنزاع على أي سطح. أي فحص سلامة مراقبة يقارن بTزمن كتلة Arweave، وليس معanchored_atالتي يتحكم بها المشغل (§16.6).توقيت إثبات متدرج — دقة التصميم، وليس التوجيه الحالي. يعين التصميم الراجع توقيت كتلة المرساة للطبقة المجمعة وإثبات الساعة الدقيقة إلى T3 المدفوع، دون وجود رقم قياسي للسابق. المسارات الحالية لم تحدد هذا التمييز التجاري: فهي تحدد معاملة فردية لكل منشور مقبول، وتبقى تغطية السجل مشروطة كما هو مذكور في §16.1/§16.10. يجب أن يصف نسخة المنتج التنفيذ، وليس هذا التقسيم المستقبلي للطبقة.
الشجرة التراكمية على العمال — تم الحل. شجرة RFC 9162 التراكمية الواحدة + سجل LogDO للكاتب الواحد مع تخزين الجبهة المؤقت (مساحة مريحة مقابل سقف ~1k عمليات الكتابة/ثانية في DO؛ تأجيل تقسيم Merkle لجذور الشظايا حتى الاقتراب منه). يتم تنفيذ مخزن عقدة
(level, index)R2 المفهرس بالمفتاح التنسيقي + متجه اختبار الورقة الباردة DO الممسوحة (§16.9). يظل< 300 msهدفاً للتصميم/التشغيل، وليس وعداً بالبروتوكول (§16.10).مخطط التوقيع ANS-104 + التجزئة العميقة — تم الحل. RSA-PSS (نوع التوقيع 1، باستخدام JWK المخصصة لمحفظة المرساة); تم تأجيل Ed25519 إلى مسار §15 PQ. التجزئة العميقة SHA-384 المصنوعة يدوياً مشروطة بفحص التوافق بين الاتجاهين، وفحص التشغيل المرجعي لتجميع الثوابت فقط، وجولة العودة المشتركة لـ
crypto.subtle، ورصد قبول Arweave بعد التجميع (§16.8).
قيود الإطلاق الملزمة (يتم نقلها إلى التنفيذ + مراجعة المنتج/القانونية):
- سقف المطالبات (Q1/Q6). لا يمكن لأي سطح أن يقول السجل يثبت محتوى ورقة مزعومة أو جولة فتح؛ المطالبة المسموح بها لورقة
kind=0x02المثبتة بنجاح هي مرتبة، مع دليل التلاعب، مع وقت التزام أعلى بدون ثقة. الرد بدون زوج إيصال لا يحتوي على أي مطالبة سجل. لا يحمل أي نسخة من الطابع الزمني ضمان تأخير رقمي. - صدق الشاهد (Q2). التناقض في السوق يكون قابلاً للاكتشاف + موثقًا بالإيصال، وليس مشاهدًا بشكل مستقل أبدًا.
- مفاتيح الإيصال والمرساة (Q2/Q8). قبل إرسال مطالبات عدم الإنكار/التحقق المثبت، قم بتوفير وتجميع تثبيت المفتاح العام للإيصال، ووقعه بشكل متقاطع مع مالك المرساة المخصص، واحتفظ بمحفظة المرساة كـ JWK خاص بها ومختلف عن محفظة الرفع.
- بوابة التجزئة العميقة (Q8). لا يتم شحن أي مرساة أو معاملة T3 حتى يتم اجتياز فحص التجهيز + التوافق في الاتجاهين؛ المراقب القبول يعطي إشعار عند الفشل.
- شرط التخزين المسبق (Q7). يعتبر مخزن العقدة المفاتيح المنسقة + متجه الأوراق الباردة DO الممسوح هما متطلبات أساسية لضمان "لا يبطل الاسترداد أي دليل".
17. حزمة التحقق المحمولة (.qub)
الحالة. هذا القسم منفَّذ (W7 / UP-C2): تنتج
qub_core::exportالحزمة وتحللها، وtools/qub-verifyأداة CLI عامة مستقلة تتحقق منها دون اتصال. تشير §11 و§16.9 بالفعل إلى «حزمة.qub» بوصفها الوحدة التي يستهلكها المتحقق المستقل؛ ويحدد هذا القسم بايتاتها ومسار التحقق. وهي إضافية بحتة—تجمع الحزمة مدخلات §11 القائمة ولا تغيّر أي صيغة سلكية على السلسلة.
17.1 الغرض
تقرر §11 أن أي طرف ثالث يستطيع التحقق من القطعة التشفيرية لـ qub دون تعاون من qub. وتجعل حزمة .qub ذلك التحقق محمولًا وغير متصل: فهي تجمع CBOR المختوم وتوقيع جولة drand الذي يفتح قفله في قطعة واحدة مستقلة، بحيث يستطيع المتلقي التحقق من نزاهة المحتوى وربط الجولة وأي توقيعات نسب من دون أي اتصال شبكي (لا جلب من التخزين، ولا طلب حي إلى drand، ولا API لـ qub). ولا تثبت الحزمة وحدها متى أُنشئ نصها المشفر؛ إذ توفر معاملة تخزين متحققًا منها مستقلًا أو إثباتًا مُرسًى في السجل ادعاء وقت الوجود المنفصل (§11، §17.5).
17.2 صيغة الحزمة
QubBundle هو CBOR قانوني مكتوب يدويًا وفق ملف §3.1 (أطوال محددة، بلا وسوم، بلا أعداد عائمة، أعداد صحيحة بأقصر صورة، نص NFC، حذف الحقول الاختيارية عند غيابها، وترتيب المفاتيح تصاعديًا بطول البايت المرمز ثم بايتيًا). وتُرتَّب المفاتيح الثلاثة ذات 15 حرفًا d < i < s. ملف .qub الخام هو هذه البايتات بالضبط؛ وللنقل عبر URL أو النسخ واللصق تكون البايتات نفسها base64url بلا حشو.
| المفتاح | الطول | نوع CBOR | الحضور | المعنى |
|---|---|---|---|---|
version |
8 | u8 |
مطلوب | إصدار صيغة الحزمة (0x01). |
sealed_at |
10 | i64 |
اختياري | وقت الختم الذي يدعيه المُنشئ (ثواني Unix)؛ وصفي ذاتيًا وغير استدلالي. |
drand_round |
12 | u64 |
مطلوب | الجولة التي يُقفل إليها qub. إسقاط من qub المختوم المضمّن. |
arweave_tx_id |
14 | tstr |
مطلوب | معرّف المعاملة الذي خُزنت تحته البايتات المختومة (مؤشر منشأ). |
drand_chain_id |
15 | tstr |
مطلوب | سلسلة drand (hex). إسقاط من qub المختوم المضمّن. |
drand_signature |
16 | bstr |
مطلوب | توقيع منارة drand لـ drand_round—القيمة التي تفتح النص المشفر. |
inclusion_proof |
16 | bstr |
اختياري | إثبات إدراج Merkle من سجل الشفافية في §16. |
sealed_qub_cbor |
16 | bstr |
مطلوب | بايتات SealedQubCbor الداخلية (بعد فك غلاف §13)، أي مدخل التحقق في §11. |
drand_round وdrand_chain_id إسقاطان للملاءمة من sealed_qub_cbor، محمولان كي تقرأهما الأدوات من دون تحليل CBOR الداخلي. ويُشتقان عند البناء ثم يُعاد فحصهما عند فك الترميز مقابل qub المختوم المحلل؛ وتُرفض حزمة يختلف حقلها العلوي عن حمولتها. يماثل انضباط المرمز بقية الصيغة السلكية: ارفض drand_signature أو arweave_tx_id فارغًا، وضع حدًا لكل حقل متغير الطول.
17.3 ما يثبته توقيع drand المضمّن
تحمل الحزمة توقيع drand بدل مطالبة المتحقق بجلبه. ولا ينجح فك تشفير القفل الزمني (tlock فوق سلسلة drand، §8) إلا بتوقيع المنارة الحقيقي للجولة المرتبطة—قيمة لا تنشرها السلسلة إلا عند انقضاء تلك الجولة، وتكون توقيع BLS صالحًا تحت المفتاح العام للسلسلة. ويفشل التوقيع المزور أو الخاطئ في التحقق بـ BLS أو في فك تشفير IBE/AEAD. لذلك تثبت الحزمة التي تنجح في فك التشفير أن النص المشفر مرتبط بالجولة R، وأن الجولة R قد انقضت. يثبّت المتحقق السلسلة (DrandTimelockProvider::quicknet()) ويطبق فحص ربط الجولة في §11، فلا يمكن للحزمة ادعاء جولة لا يرتبط بها نصها المشفر.
هذا إثبات لشرط الإصدار، لا طابعًا زمنيًا للإنشاء. فبعد انقضاء الجولة R، يستطيع أي شخص إنشاء نص مشفر جديد للجولة R وتغليف توقيعها الذي صار عامًا. لذلك يَجِب ألّا توصف الحزمة وحدها بأنها تثبت أن النص المشفر أو المحتوى كان موجودًا قبل R، أو قبل unlock_at، أو قبل أي حدث.
17.4 مسار التحقق دون اتصال
يشغّل qub-verify <file.qub> إجراء §11 القياسي بالكامل من الحزمة، ويقود qub_core::unlock::unlock بواسطة DrandTimelockProvider مثبّت:
1. Parse the .qub bytes → QubBundle (canonical-CBOR guard; bound every field;
re-check drand_round / drand_chain_id against the embedded sealed qub).
2. BLS-verify bundle.drand_signature for the pinned chain and round, then
tlock_decrypt(sealed.tlock_ciphertext, bundle.drand_signature) → QubEnvelope.
3. Verify SHA3-256(body) == body_hash (§11 step 8).
4. Verify QubEnvelope.qub_id == SealedQub.qub_id (§11 step 9).
5. Verify QubEnvelope.unlock_at == SealedQub.unlock_at (§11 step 10).
6. Verify ciphertext round == unlock_round(unlock_at) and the chain binding.
7. If sig_alg != 0x00: verify author_signature (and any cosigner; §9.4).
8. Report integrity, round-elapsed/round-binding, authorship, and cosigner
verdicts separately, plus the recovered body. Do not report a commitment
timestamp unless step 9 succeeds.
9. Optional existence-time leg: verify an included §16 proof through its pinned
anchor, or independently verify the referenced storage transaction. Report
its block time as an upper bound on ciphertext existence.
تخرج أداة CLI بالقيمة 0 (تم التحقق)، أو 1 (فشل التحقق—ما زال مقفلًا، أو عدم تطابق تجزئة المتن، أو كسر في ربط الجولة/السلسلة، أو توقيع يفشل التحقق)، أو 2 (الاستخدام / حزمة مشوهة). ويحمل تقرير --json الأحكام نفسها للأتمتة. ولأن الحزمة مستقلة، فإن صندوق المتحقق (qub-core) وأداة CLI (qub-verify) هما البرنامجان الوحيدان اللذان يحتاج إليهما طرف ثالث؛ وكلاهما عام ويعيد استخدام مسار التحقق القائم للبروتوكول—بلا تشفير خاص مبتكر.
17.5 العلاقة بسجل الشفافية
inclusion_proof خانة اختيارية لإثبات إدراج Merkle في §16. يكتمل تحقق الحزمة وحدها (§17.4) من النزاهة، وربط الجولة / انقضاء الجولة، والنسب الاختياري، لكنه لا يحمل عمدًا ادعاء وجود ذي طابع زمني مستقل. ويضيف inclusion_proof المعبّأ والمتحقق بالكامل عبر المرساة الالتزام الخاص بنوع الورقة والوقت الأقصى من §16.11، دون تغيير إصدار صيغة الحزمة. ويعني غياب الإثبات فقط «لا يوجد إثبات مضمّن»—لا «غير صالح» ولا بالضرورة «غير مُرسى».
في التنفيذ المرجعي أصبحت الخانة الآن محددة النوع: تعيد qub_core::export::QubBundle::inclusion_proof_typed() قيمة Option<InclusionProof> تحمل بنية §16.9 كاملة (الورقة، ومسار التدقيق، والجذر المُرسى، وAnchorRef) عبر حقل CBOR المعتم نفسه—بلا رفع لإصدار صيغة الحزمة. وتستهلكها أداة qub-verify المستقلة عبر مسار --anchor الخاص بها، وحتى تُجهز محفظة المرساة (§16، الحالة)، تُبلغ عن إثبات ذي مالك عنصر نائب على أنه إدراج فقط بدل متحقق منه عبر المرساة بالكامل.