مواصفات بروتوكول qub
qub هو بروتوكول للالتزامات الزمنية التشفيرية: نظام لختم الكلمات حتى تاريخ مستقبلي وإثبات ما قيل بالضبط ومتى عند حلول ذلك التاريخ.
تجعل ثلاث عناصر أولية هذا النظام يعمل. drand هو منارة عشوائية لامركزية — تاريخ الكشف قابل للإنفاذ بقوانين الفيزياء، لا بحسن نية أي طرف. التخزين العام الدائم هو متجر عام غير قابل للعبث — لا يمكن لأي طرف تعديل qub أو حذفه بعد ختمه. ML-DSA-65 هو توقيع رقمي ما بعد الكم — يرتبط كل qub بزوج مفاتيح لا يغادر سرّه جهاز المؤلف أبدًا.
تُنتج هذه العناصر مجتمعةً بيانًا مُقفلًا زمنيًا، ومُقاومًا للعبث، وقابلًا للنسب — إيصال تنمو قيمته بنمو قدرة العالم على تزييف الماضي.
ما تبقى من هذه الوثيقة هو المواصفة المعيارية المطلوبة لتنفيذات قابلة للتشغيل البيني.
مواصفة بروتوكول qub
| الحقل | القيمة |
|---|---|
| الإصدار | 1.0 (إصدار البروتوكول 0x01، إصدار الغلاف الخارجي 0x01) |
| التاريخ | 2026-05-01 |
| الحالة | مسودة |
| مراجَع حتى | 2026-05-01 |
هذه الوثيقة هي مواصفة البروتوكول المعيارية لنظام الالتزام الموقوت في 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, // 0x01 = public (only value in MVP)
content_type: u8, // 0x01 = text (only value in MVP)
plaintext: Vec<u8>, // UTF-8 qub body
sender_label: Option<String>, // Decorative display name; not authenticated
status: DraftStatus, // Composing | Sealed | Uploaded | Failed
}
2.2 QubEnvelope (الحُمولة المفكوكة)
تُسلسَل باستخدام CBOR قانوني (§3). مُشفَّرة داخل SealedQub. هذا هو الهيكل الذي يُثبت سلامة المحتوى بعد فكّ التشفير.
QubEnvelope {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Derived (see §4.1)
content_type: u8, // Content type registry (see §6)
created_at: i64, // Unix seconds UTC
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — when reality renders judgment (verdict-uplift-plan §3.1)
sender_label: Option<String>, // Decorative; not authenticated in MVP
reply_to: Option<[u8; 32]>,// Parent qub_id for reply chains; not in qub_id preimage; not signed (see §9.3)
body: Vec<u8>, // Content payload (UTF-8 for text, CBOR for pact)
body_hash: [u8; 32], // SHA3-256(body) (see §4.2)
sig_alg: u8, // Signature algorithm (see §9.2)
author_signature: Option<Vec<u8>>, // Set when sig_alg != 0x00
author_pubkey: Option<Vec<u8>>, // Set when sig_alg != 0x00
cosigner_pubkey: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
cosigner_signature: Option<Vec<u8>>, // Set for cosigned pact bilateral agreements
}
الأساس (qub نصّي غير موقّع): version = 0x01، content_type = 0x01، sig_alg = 0x00، جميع حقول Option غائبة.
تكوينات v1 الأخرى: content_type = 0x03 (جسم ميثاق، انظر §6.1)؛ sig_alg = 0x01 (ML-DSA-65) مع وجود author_signature وauthor_pubkey (انظر §9.3)؛ وجود cosigner_pubkey وcosigner_signature معًا للمواثيق ذات التوقيع المُشتَرَك (انظر §9.7)؛ ضبط reply_to على qub_id للـ qub الأب لـ qubs سلاسل الردود (انظر §9.3 لاستتباعات نطاق التوقيع).
2.3 SealedQub (الصيغة السلكية القانونية)
تُسلسَل باستخدام CBOR قانوني (§3). تُكتب إلى التخزين الدائم. هذه هي القطعة الأثرية على السلسلة.
SealedQub {
version: u8, // Protocol major version (0x01 for v1)
qub_id: [u8; 32], // Same as QubEnvelope.qub_id
visibility: u8, // 0x01 = public; v1 viewers reject other values
unlock_at: i64, // Unix seconds UTC
outcome_at: Option<i64>, // V1.1 — surfaced on the verdict-watch CTA
// before reveal; mirrors QubEnvelope.outcome_at;
// bound to qub_id via the §4.1 preimage.
drand_chain_id: String, // drand chain hash (hex string)
drand_round: u64, // Target drand round number
tlock_ciphertext: Vec<u8>, // tlock-encrypted QubEnvelope CBOR bytes
recipient_pubkey: Option<[u8; 32]>,// Reserved field; accepted by canonical CBOR
// but not interpreted by the v1 reference viewer
title: Option<String>, // Plaintext title surfaced on the viewer
// countdown before reveal. Bound to qub_id
// via title_hash (§4.1). 1..=100 NFC code
// points, no control characters.
}
2.4 RevealedQub (حالة تطبيق المُشاهِد)
غير متسلسلة إلى CBOR. محلية لتطبيق المُشاهِد. تُبنى بعد فكّ التشفير والتحقق بنجاح.
RevealedQub {
qub_id: [u8; 32],
arweave_tx_id: String,
visibility: u8,
content_type: u8,
created_at: i64,
unlock_at: i64,
outcome_at: Option<i64>, // V1.1 — مُرحَّل من QubEnvelope.outcome_at / SealedQub.outcome_at؛ يُشغّل كتلة ترقّب الحكم في صفحة الكشف (verdict-uplift-plan §5.1)
drand_chain_id: String,
drand_round: u64,
sender_label: Option<String>,
title: Option<String>, // Carried forward from SealedQub.title
reply_to: Option<[u8; 32]>,
body: Vec<u8>,
body_hash: [u8; 32],
body_hash_verified: bool,
author_signature: Option<Vec<u8>>,
author_pubkey: Option<Vec<u8>>,
signature_verified: Option<bool>,
cosigner_pubkey: Option<Vec<u8>>,
cosigner_signature: Option<Vec<u8>>,
cosigner_verified: Option<bool>,
}
3. ملف CBOR القانوني
يَجِب أن يلتزم كل تسلسل لـ SealedQub وQubEnvelope بهذا الملف. أيّ تنفيذيْن يعالجان البنية المنطقية نفسها يَجِب أن يُنتجا بايتات متطابقة.
3.1 قواعد الترميز
| القاعدة | المواصفة |
|---|---|
| المعيار | RFC 8949 §4.2.1 (متطلبات الترميز الحتمي الأساسي) |
| ترتيب مفاتيح الخريطة | تُرتَّب حسب طول البايتات المُرمَّزة أولًا (الأقصر قبل الأطول)، ثم معجميًا (بايت ببايت للترميزات بنفس الطول) |
| ترميز الأعداد الصحيحة | أقصر صيغة: 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 (V1.1 verdict mechanic)
"content_type" (13 encoded bytes)
"sender_label" (13 encoded bytes) ← only if present
"author_pubkey" (14 encoded bytes) ← only if present
"cosigner_pubkey" (16 encoded bytes) ← only if present (pact cosign)
"author_signature" (17 encoded bytes) ← only if present
"cosigner_signature" (19 encoded bytes) ← only if present (pact cosign)
اشتقاق ترتيب مفاتيح QubEnvelope: كل مفتاح هو سلسلة نصية CBOR. الطول المُرمَّز = 1 بايت رأس + طول السلسلة (للسلاسل الأقصر من 24 بايتًا). فرز حسب الطول المُرمَّز الكلي أولًا، ثم معجميًا للمفاتيح ذات الطول نفسه.
SealedQub (الإصدار 0x01، عام، بلا مُستقبِل):
"title" (6 encoded bytes) ← only if present
"qub_id" (7 encoded bytes)
"version" (8 encoded bytes)
"unlock_at" (10 encoded bytes)
"outcome_at" (11 encoded bytes) ← only if present (V1.1 verdict mechanic)
"visibility" (11 encoded bytes)
"drand_round" (12 encoded bytes)
"drand_chain_id" (15 encoded bytes)
"recipient_pubkey" (17 encoded bytes) ← only if present
"tlock_ciphertext" (17 encoded bytes)
PactTerms (جسم الميثاق، content_type 0x03):
"notes" (6 encoded bytes) ← only if present
"terms" (6 encoded bytes)
"title" (6 encoded bytes)
"party_a" (8 encoded bytes)
"party_b" (8 encoded bytes)
"pact_version" (13 encoded bytes)
PactTerm (صفّ في مصفوفة terms):
"key" (4 encoded bytes)
"value" (6 encoded bytes)
PartyIdentifier (خريطة 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: وسّع V1.1 الصورة الأولية من 92 إلى 100 بايت لطيّ حقل outcome_at الاختياري ضمن الربط. يُرمَّز غياب outcome_at بثماني بايتات صفرية؛ وترفض مُتحقِّقات البروتوكول outcome_at <= 0 في كل موضع، فلا تستطيع هذه القيمة الحارسة أن تتصادم مع قيمة مشروعة. انظر §3.2 (الصيغة السلكية) والملف الداخلي tasks/verdict-uplift-plan.md للآلية التحكيمية التي تُحفّز هذا الحقل.
ترميز drand_round: وسّع V1.2 الصورة الأولية من 100 إلى 108 بايت لطيّ drand_round (جولة drand المستهدفة، §4.3) ضمن الربط، ورفع فاصل النطاق إلى QUB_ID_V2. وهذا يربط جولة القفل الزمني ضمن هوية qub: لا تستطيع أي بوابة إعادة ربط النص المُشفَّر بجولة مختلفة (مثلًا جولة ماضية بالفعل) عن الجولة التي يقتضيها unlock_at المعروض. كما يتحقق إجراء فكّ القفل (§8) إضافةً إلى ذلك من أن الجولة المخبوزة في مقطع نص tlock المُشفَّر تطابق unlock_round(unlock_at)، فيكون وقت فكّ القفل المعروض هو الجولة التي تتحكم في فكّ التشفير بشكل قابل للإثبات.
الخصائص:
- تغيير أي حقل في QubEnvelope (الجسم، الطوابع الزمنية، نوع المحتوى، الإصدار) يُنتج qub_id مختلفًا.
- يُحسب qub_id قبل التشفير. يحمل كلٌّ من QubEnvelope و SealedQub نفس qub_id. يتحقق المُشاهِد من تطابقهما بعد فكّ التشفير.
- لا يعتمد qub_id على
sender_labelأوauthor_signatureأوauthor_pubkey. وهذا يعني أن المحتوى نفسه المختوم في الوقت نفسه يُنتج qub_id نفسه بغضّ النظر عن الموقّع. - تغيير
titleفي SealedQub (مع تثبيت كل ما عداه) يُغيّرqub_idعبرtitle_hash. لذا لا تستطيع أي بوابة استبدال العنوان النصي المعروض على العدّ التنازلي دون إبطال هوية qub. - تغيير
outcome_atفي SealedQub (مع تثبيت كل ما عداه) يُغيّرqub_idعبر الصورة الأولية. لا تستطيع أي بوابة استبدال تاريخ ظهور الحكم قبل الكشف المعروض على العدّ التنازلي دون إبطال هوية 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 = ceil((unlock_at - chain_genesis_time) / chain_period_seconds)
| المُعامل | المصدر | مثال |
|---|---|---|
unlock_at |
ثوان Unix UTC يختارها المستخدم | 1735689600 (2025-01-01 00:00:00 UTC) |
chain_genesis_time |
معلومات سلسلة drand (genesis_time) |
1595431050 |
chain_period_seconds |
معلومات سلسلة drand (period) |
30 |
تختار عملية ceil() أول جولة drand يكون وقت كشفها ≥ unlock_at. هذا يضمن ألّا يصبح qub قابلًا لفكّ التشفير قبل وقت فكّ القفل المُختار.
حالة الحد: إذا كان (unlock_at - chain_genesis_time) قابلًا للقسمة تمامًا على chain_period_seconds، فإن النتيجة هي تلك الجولة بالضبط — يُفكّ قفل qub بدقة عند وقت كشف تلك الجولة.
التحقق: يَجِب أن يكون 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), value: String (≤ 2,000) } // NFC on both sides
PartyIdentifier{ label: String (≤ 100), contact: Option<String (≤ 320)> }
ترتيبات مفاتيح 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.
3. Compute body_hash = SHA3-256(body).
4. Set created_at = current Unix seconds UTC.
5. Select drand chain. Load chain_genesis_time and chain_period_seconds, and
compute drand_round = ceil((unlock_at - chain_genesis_time) / chain_period_seconds).
(Computed here, before qub_id, because drand_round is bound into the qub_id
preimage — §4.1, V1.2.)
6. Compute qub_id (see §4.1), folding in drand_round from step 5.
7. Construct QubEnvelope with all fields.
8. Serialise QubEnvelope using canonical CBOR → bytes B.
Assert: serialised output matches canonical profile (§3).
9. Compute C = tlock_encrypt(B, drand_round, drand_chain_public_key).
10. Construct SealedQub with tlock_ciphertext = C, and matching qub_id, version,
unlock_at, drand_chain_id, drand_round.
12. Serialise SealedQub using canonical CBOR → SealedQubCbor.
12a. Generate K = 32 random bytes (CSPRNG) and N = 12 random bytes (CSPRNG).
Compute W = wrap_sealed_qub(SealedQubCbor, qub_id=qub_id, key=K, nonce=N)
per §13. The bytes uploaded to permanent storage are the OuterWrapper CBOR W,
never the bare SealedQubCbor. K leaves the device only as the URL
fragment in step 16.
13. Display seal-time disclosure. User confirms.
14. Validate upload eligibility via the qub upload service (bot-detection, entitlement, rate limits).
15. Submit W (the OuterWrapper bytes) to the qub upload service; the service
signs and uploads to permanent storage. The service is byte-blind to the inner
SealedQubCbor and never receives K.
16. Receive arweave_tx_id from the service. Construct delivery URL as
`<origin>/c/<arweave_tx_id>#<base64url(K)>` (or `<origin>/s/<short_code>#<base64url(K)>`
when a short code is allocated). Browsers do not transmit URL fragments
to servers, so K is never observed by qub.social or any storage gateway.
طبقة وسوم التخزين (خارج النطاق). تُرفق خدمة رفع qub مجموعة صغيرة عمدًا من وسوم معاملات التخزين إلى جانب الحُمولة المُغلَّفة. Content-Type=application/octet-stream مطلوب معياريًا. تُرفق الخدمة المرجعية إضافيًا ثلاثة وسوم اختيارية عندما يختار المُنْشِئ إظهارها: Intent (نية إنشاء مُتحقَّق منها بقائمة مسموح بها — مثلًا quote أو reply أو commitment)، وAuthor (بصمة المفتاح العام للمُنْشِئ وفقًا لـ §9.3 ست عشرية صغيرة من 64 رمزًا)، وParent-Tx-Id (معرّف معاملة التخزين لـ qub الأب لسلاسل الردود، base64url من 43 رمزًا).
وسم Author اختياري لكل qub: تطبيق المُنْشِئ المرجعي يُرفقه فقط عندما يُفعّل المستخدم صراحةً النسب العام وقت الختم. عند إيقاف التفعيل — وهو الافتراضي — لا يُكتب وسم Author ويكون qub غير منسوب على السلسلة: لا شيء في التخزين الدائم يربط الرفع بمعرّف المُنْشِئ أو بريده الإلكتروني أو qubs أخرى له. عند تفعيل التبديل، تنحلّ بصمة Author إلى @handle الذي اختاره المُنْشِئ عبر سلسلة التَّوْثيق في §9.5. علاقات سلاسل الردود وIntent لا تُعرّف الهوية. الغلاف الخارجي (§13) يحمي الجسم الداخلي من ارتباط النصوص المشفرة — مما يمنع الحاصد من التعرف على رفعات بصيغة qub وفكّ تشفيرها بالجملة بعد نشر جولتها على drand.
لا تُرفق الخدمة المرجعية عمدًا وسوم App-Name أو App-Version أو Type: أي مرشّح قائم على قيمة واحدة من هذا القبيل سيُرجع كامل مجموعة qubs لاستعلام GraphQL، وهذا يتعارض مع نطاق سرية الجسم فقط للغلاف.
يَجِب على المُتحقِّق المطابق ألّا يعتمد على أي وسم تخزين للتحقق من طرف ثالث وفقًا لـ §11؛ تجزئة الجسم / qub_id / التوقيع تلتزم بالـ CBOR الداخلي فقط، لا بمجموعة الوسوم.
8. بروتوكول فكّ القفل
تسلسل فكّ القفل الكامل. كل خطوة معيارية.
1. Viewer opens delivery URL. Extract arweave_tx_id from path AND
K = base64url_decode(fragment) from the URL fragment. If the fragment
is absent or malformed → display "this URL is missing its decryption
key" and stop; the viewer MUST NOT contact the storage gateway
without K, since fetching wrapped bytes the viewer cannot decrypt
serves no purpose and only leaks the access attempt.
2. Check denylist. If tx_id is denylisted → display block message. Stop.
3. Fetch OuterWrapper bytes from permanent storage (with multi-gateway fallback).
3a. Unwrap: parse the bytes as OuterWrapper (§13), verify the wrapper
`version` byte is `0x01`, and compute SealedQubCbor =
unwrap_sealed_qub(OuterWrapper, key=K). Any AEAD authentication
failure (wrong K, tampered ciphertext, swapped qub_id-as-AAD,
swapped nonce) → display "this URL's decryption key does not match
the stored qub" and stop. Authentication failures are
indistinguishable to the viewer per §13.5.
4. Parse SealedQubCbor → SealedQub.
5. Validate: SealedQub.version is known (0x01). Reject unknown versions.
6. If current time < SealedQub.unlock_at → display countdown. Poll or wait.
6a. Round-binding check (V1.2). Recompute expected_round =
ceil((SealedQub.unlock_at - chain_genesis_time) / chain_period_seconds).
Reject unless SealedQub.drand_round == expected_round AND the round baked
into the tlock ciphertext stanza (read via the age/tlock header, no signature
required) == expected_round. The stanza round is the one that actually gates
decryption; without this check a malicious creator could bind the ciphertext
to an already-past round while displaying a future countdown, so anyone
reading the stored bytes could decrypt before unlock_at. Implementations with
no chain identity (test mocks) skip this check.
7. Once current time ≥ SealedQub.unlock_at:
a. Fetch drand round signature for SealedQub.drand_round from drand network.
b. Compute B = tlock_decrypt(SealedQub.tlock_ciphertext, round_signature).
8. Parse B → QubEnvelope.
9. Validate QubEnvelope.version is known.
10. Verify: SHA3-256(QubEnvelope.body) == QubEnvelope.body_hash.
Fail → integrity error.
11. Verify: QubEnvelope.qub_id == SealedQub.qub_id.
Fail → integrity error.
12. Verify: QubEnvelope.unlock_at == SealedQub.unlock_at.
Fail → integrity error.
13. Verify: QubEnvelope.content_type is known and renderable.
Known values: 0x01 (text), 0x03 (pact). Unknown → display error.
14. If QubEnvelope.sig_alg != 0x00 → verify author signature (see §9.4).
15. If cosigner_pubkey or cosigner_signature present → verify cosigner (see §9.7).
16. Render content using appropriate renderer (see §10 for text, §6 for pact).
17. Construct RevealedQub for display.
9. توقيع التأليف
9.1 المبرر
تُخزَّن qubs في التخزين الدائم. يَجِب أن تظل توقيعات التأليف غير قابلة للتزوير إلى أجل غير مسمى، ولذلك تستخدم v1.0 مخطط ML-DSA-65 (FIPS 204) ما بعد الكم بدلًا من مخطط كلاسيكي قد يتآكل أمانه خلال عمر qub الدائم.
9.2 سجل الخوارزميات
sig_alg |
المخطط | حجم المفتاح | حجم التوقيع |
|---|---|---|---|
0x00 |
لا توقيع (غير موقّع) | — | — |
0x01 |
ML-DSA-65 (FIPS 204) | 1,952 بايتًا | 3,309 بايتات |
يَجِب على المُشاهِدين رفض قيم sig_alg غير المعروفة.
9.3 بناء الصورة الأولية الموقَّعة
وُجدت نسختان من الصورة الأولية. يَجِب أن تستخدم كل التوقيعات V2، ويَجِب على المُتحقِّقين قبول V2 فقط. كانت الصورة الأولية القديمة V1 (الموثَّقة أدناه للرجوع التاريخي) مقبولة كخيار احتياطي للتحقق فقط أثناء الانتقال إلى V2؛ وقد أُلغي ذلك الخيار الاحتياطي، ويُرفض الآن أي توقيع يعتمد على V1 وحده.
V2 (الحالية — تُنتِجها كل عمليات توقيع المؤلف الجديدة، وكلا التوقيعين في مسار تحضير الميثاق / التوقيع المشترك):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V2" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 || // u8 (1 byte): MUST be 0x00 in v1.x
sender_label_hash || // [u8; 32]: SHA3-256(NFC(sender_label)),
// or 32 zero bytes when absent
reply_to_or_zero // [u8; 32]: parent qub_id, or 32 zero
// bytes when absent
)
// Total preimage: 155 bytes → 32-byte hash
signature = Sign(author_secret_key, sig_input)
يتبع sender_label_hash اصطلاح قيمة-الغياب الحارسة نفسه الذي يتبعه title_hash (§4.2.1): 32 بايتًا صفريًا ليست ناتجًا صالحًا لـ SHA3-256، لذا لا يمكن أبدًا أن يتصادم «الغائب» مع تسمية حاضرة. كل الحقول ثابتة العرض، لذا تكون الصورة الأولية غير ملتبسة دون بادئات طول.
V1 (قديمة — مُلغاة؛ لم تعد تُنتَج ولم تعد مقبولة عند التحقق):
sig_input = SHA3-256(
"QUB_AUTHOR_SIG_V1" || // domain separator (17 bytes)
version || // u8 (1 byte)
qub_id || // [u8; 32] (32 bytes)
body_hash || // [u8; 32] (32 bytes)
unlock_at || // i64 big-endian (8 bytes)
0x00 // u8 (1 byte): MUST be 0x00 in v1.0
)
// Total preimage: 91 bytes → 32-byte hash
أغفلت الصورة الأولية V1 حقلي sender_label وreply_to. كانت مقبولة كخيار احتياطي للتحقق فقط أثناء الانتقال إلى V2؛ وقد أُلغي ذلك الخيار الاحتياطي منذئذٍ — يَجِب على المُتحقِّقين قبول الصورة الأولية V2 فقط. يُحتفَظ بالتعريف هنا للرجوع التاريخي ولشرح فاصل النطاق أدناه. يَجِب معاملة أي توقيع يتحقق فقط مقابل V1 على أنه فشل في التحقق.
فواصل النطاق: "QUB_AUTHOR_SIG_V1" / "QUB_AUTHOR_SIG_V2" كل منهما 17 بايتًا ASCII ([0x51, 0x55, 0x42, 0x5F, 0x41, 0x55, 0x54, 0x48, 0x4F, 0x52, 0x5F, 0x53, 0x49, 0x47, 0x5F, 0x56, 0x31/0x32]). بلا حشو. يفصل الفاصلُ المختلف بين البِنيتين نطاقيًا، لذا لا يمكن أبدًا أن يتحقق توقيع على إحدى الصورتين الأوليتين بوصفه الأخرى.
البايت الزائل: يَجِب أن يكون البايت التالي لـ unlock_at 0x00. يُعرّض التنفيذ المرجعي هذا كثابت ORG_ID_PRESENT_INDIVIDUAL = 0x00 في crates/qub-core/src/signing.rs؛ على المُشاهِدين الذين يعيدون بناء sig_input للتحقق إصدار البايت نفسه.
نطاق التوقيع — ما يُغطَّى وما لا يُغطَّى. يلتزم sig_input في V2 مباشرةً بـ version وqub_id وbody_hash وunlock_at وsender_label وreply_to (إضافة إلى فاصل النطاق الثابت وبايت org_id_present). وqub_id نفسه مُشتق من version وcontent_type وcreated_at وunlock_at وoutcome_at وdrand_round وbody_hash عبر الصورة الأولية في §4.1، لذا أي تغيير في تلك الحقول يُنتج qub_id مختلفًا ويُبطل التوقيع تَعَدّيًا. السطح الموثَّق هو إذن:
| الحقل | موثَّق بالتوقيع | كيف |
|---|---|---|
version |
✓ | مدخل مباشر إلى sig_input |
qub_id |
✓ | مدخل مباشر |
body_hash |
✓ | مدخل مباشر |
unlock_at |
✓ | مدخل مباشر |
sender_label |
✓ | مدخل مباشر عبر sender_label_hash (صورة V2 الأولية — الصيغة الوحيدة المقبولة) |
reply_to |
✓ | مدخل مباشر عبر reply_to_or_zero (صورة V2 الأولية — الصيغة الوحيدة المقبولة) |
content_type |
✓ | تَعَدّيًا، عبر صورة qub_id الأولية |
created_at |
✓ | تَعَدّيًا، عبر صورة qub_id الأولية |
outcome_at |
✓ | تَعَدّيًا، عبر صورة qub_id الأولية |
drand_round |
✓ | تَعَدّيًا، عبر صورة qub_id الأولية (V1.2) |
body |
✓ | تَعَدّيًا، عبر body_hash = SHA3-256(body) |
author_pubkey |
— (ضمني) | المفتاح الذي تحقَّق من التوقيع هو المؤلف، بحكم التعريف |
cosigner_pubkey / cosigner_signature |
— | مُوقَّع باستقلال على sig_input نفسه (انظر §9.7) |
drand_chain_id, tlock_ciphertext, visibility |
— | حقول SealedQub الخارجية، ليست داخل الغِلاف — مُغطّاة بثوابتها البنيوية الخاصة (اتساق الجولة / السلسلة) لكن ليس بتوقيع المؤلف. (أصبح drand_round الآن مربوطًا انتقاليًا عبر الصورة الأولية لـ qub_id — انظر أعلاه.) |
لماذا V2 هي الصورة الأولية الوحيدة المقبولة.
- في ظل صورة V1 الأولية المُلغاة، كان بإمكان طرف لديه صلاحية كتابة على البايتات المخزَّنة استبدال
sender_label("Alice" ← "Mallory") أو إعادة إسناد أبreply_to— وإعادة التشفير بعد الجولة — دون إبطال توقيع المؤلف، لأن أيًّا من الحقلين لم يكن في الصورة الأولية الموقَّعة. تُغطّي V2 كليهما، لذا أي تغيير في أحد الحقلين يقلب التحقق إلى «فشل». ولأن المُتحقِّقين يقبلون الآن V2 فقط، فإن هذا الاستبدال مُغلق لكل توقيع: أي توقيع لا يلتزم بأي من الحقلين (أي يتحقق فقط مقابل V1) يُرفض تمامًا بدلًا من التنازل إليه. - يظل
author_pubkeyداخل الغِلاف هو مرساة الهوية الحقيقية — يَجِب على المُشاهِدين اشتقاق هوية العرض منauthor_pubkey(عبر طبقة التَّوْثيق في §9.5) بدلًا من الوثوق بـsender_label.
التنفيذات التي تعرض sender_label أو reply_to على المستخدمين النهائيين يَجِب أن تُظهر الهوية الموثَّقة (بصمة المفتاح العام، التَّوْثيق) كإشارة الهوية الأساسية، لا التسمية.
9.4 إجراء التحقق
1. Read sig_alg from QubEnvelope.
2. If sig_alg == 0x00 → unsigned. No verification. Display "unsigned qub."
3. If sig_alg is unknown → reject. Display "unrecognised signature scheme."
4. Extract author_signature and author_pubkey. If either is absent → integrity error.
5. Reconstruct sig_input using fields from QubEnvelope (V2 formula, §9.3).
6. Verify(author_pubkey, sig_input, author_signature). The V2 preimage is the
only accepted form — the legacy V1 fallback is retired (§9.3), so a
signature that does not verify against V2 fails, full stop.
7. If verification succeeds → display "signed by [key fingerprint]."
8. If verification fails → display "signature verification failed."
التحقق من التوقيع هو أغلى عملية (لا سيما ML-DSA-65). يَنْبَغي تنفيذه بعد اجتياز كل الفحوصات الأرخص (التجزئة، qub_id، unlock_at).
9.5 توثيقات الهوية
توثيقات الهوية — ربط author_pubkey بادعاءات هوية يتعرّف عليها البشر مثل معرّف qub أو عنوان بريد إلكتروني أو معرّف اجتماعي أو شهادة 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. التحقق من طرف ثالث
يستطيع أي طرف ثالث التحقق من qub عام دون تعاون من qub. إجراء التحقق:
1. Obtain arweave_tx_id (from delivery URL or direct knowledge).
2. Fetch SealedQubCbor from any storage gateway.
3. Confirm storage block inclusion (block height, block timestamp).
4. Parse SealedQubCbor → SealedQub.
5. Fetch drand round signature for SealedQub.drand_round.
6. tlock_decrypt(tlock_ciphertext, round_signature) → QubEnvelope CBOR bytes.
7. Parse → QubEnvelope.
8. Verify SHA3-256(body) == body_hash.
9. Verify QubEnvelope.qub_id == SealedQub.qub_id.
10. Verify QubEnvelope.unlock_at == SealedQub.unlock_at.
11. If sig_alg != 0x00: verify author_signature (see §9.4).
12. All checks pass → qub is verified.
ما يُثبته التحقق:
| الإثبات | ما يُرسّخه |
|---|---|
| الالتزام | كان النص المشفّر موجودًا بحلول طابع كتلة التخزين الزمني. |
| السلامة | جسم النص العادي يطابق التجزئة المُلتزَم بها ولم يُعدَّل. |
| التوقيت | كان المحتوى غير قابل للقراءة حتى جولة drand، التي تقابل وقت فكّ القفل المُختار (شرط افتراضات أمان tlock و drand). |
ما لا يُثبته التحقق:
| غير الإثبات | السبب |
|---|---|
| التأليف | sender_label زخرفي. بدون sig_alg ≥ 0x01، كان يمكن لأي شخص ختم هذا المحتوى. |
| النية | يُثبت qub المحتوى والتوقيت، لا ما قصده المُنْشِئ ذاتيًا. |
| التوقيت قبل الحدث | قد يتأخر إدراج كتلة التخزين عن الرفع الفعلي بدقائق. طابع التزام الوقت هو وقت الكتلة، لا لحظة ضغط المستخدم على "ختم". |
12. الإصدارات
12.1 إصدار البروتوكول
حقل version (u8) في كل من SealedQub وQubEnvelope يُحدّد الإصدار الرئيسي للبروتوكول.
- يَجِب على المُشاهِدين رفض الإصدارات الرئيسية غير المعروفة برسالة خطأ واضحة.
- ضمن إصدار رئيسي معروف، يَجِب على مفكِّكات الترميز رفض مفاتيح الخريطة غير المعروفة (§3.1) — يحدث تطوُّر المخطط بإدخال
versionجديد، لا بإضافة مفاتيح كانت مفكِّكات الترميز القائمة ستتخطاها. (كانت مراجعات سابقة من هذه المواصفة تسمح بالتسامح مع الحقول الاختيارية غير المعروفة؛ وقد سُحب ذلك البند — فقد جعلencode(decode(x))غير متباين وفتح متّجهًا لمحتوى موقّع خفي على حمولات الميثاق.) - أنواع المحتوى (
content_type) ومخططات التوقيع (sig_alg) محكومة بالإصدار: يُمكن إدخال قيم جديدة فقط بالتزامن مع إصدار بروتوكول جديد أو تحديث صريح للسجل.
12.2 سجل الإصدارات
| الإصدار | القيمة | الوصف |
|---|---|---|
| v1 | 0x01 |
qubs نصية عامة (content_type 0x01)، اتفاقيات ميثاق ثنائية (0x03، مخطط structured/v1، ML-DSA-65 مؤلف + شريك توقيع)، tlock، SHA3-256 |
12.3 التوافق الأمامي
مُشاهِد 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.4 إصدار الغلاف الخارجي
يحمل OuterWrapper الموصوف في §13 بايت version خاصًا به، مستقلًا عن SealedQub.version وQubEnvelope.version. تتطور مساحتا الإصدارين بشكل منفصل: استبدال متماثل آمن ما بعد الكم مستقبلي يرفع بايت الغلاف دون لمس إصدار البروتوكول الداخلي، وإضافة طبقة بروتوكول مستقبلية (مثل حقل غِلاف جديد) ترفع الإصدار الداخلي دون لمس بايت الغلاف.
OUTER_WRAPPER_VERSION_* |
القيمة | الخوارزمية | الحالة |
|---|---|---|---|
OUTER_WRAPPER_VERSION_1 |
0x01 |
AES-256-GCM مع nonce بطول 12 بايتًا، وسم مصادقة بطول 16 بايتًا، AAD مرتبط بـ qub_id |
افتراضي v1 |
| — | 0x02–0xFF |
محجوز | مستقبلي |
يَجِب على المُشاهِدين رفض إصدارات الغلاف غير المعروفة برسالة خطأ واضحة. يُبقي البروتوكول عمدًا مساحة إصدار الغلاف ضيقة حتى يظهر دافع هجرة ملموس (مثل توجيه NIST يفضّل AEAD مختلفًا)؛ ستُخصَّص خانة 0x02 في نفس المراجعة التي تُدخل الخوارزمية.
13. غلاف التشفير الخارجي
13.1 المبرر
تجعل طبقات البروتوكول (QubEnvelope ← tlock ← SealedQub) qub المختوم مُقفلًا زمنيًا: الجسم غير قابل للقراءة حتى unlock_at وبعد نشر توقيع جولة drand. بعد فكّ القفل، مع ذلك، يصبح توقيع الجولة عامًا والشكل القانوني لـ CBOR لـ SealedQub قابلًا للتعرف، لذا يستطيع حاصد فهرس معاملات التخزين الدائم فكّ تشفير مجموعة qubs كاملة بالجملة.
يُغلق غلاف التشفير الخارجي تلك القناة بإقحام طبقة AEAD متماثلة إضافية بين SealedQubCbor القانوني والبايتات المكتوبة إلى التخزين الدائم. مفتاح 256-بت K يعيش فقط في جزء URL لرابط التَّسْليم وعلى أجهزة المستخدمين؛ لا تنقل المتصفحات أجزاء URL إلى الخوادم، لذا يكون qub.social وكل بوابة تخزين وكل CDN أمام أي منهما عميًا رصديًا عن K. لذا فإن كل qub في التخزين الدائم هو نص مشفّر غامض لا يمكن استرداد نصه العادي دون الـ URL الذي اختار المُنْشِئ مشاركته.
التأثير الصافي:
- حصانة التعداد افتراضيًا. البايتات المُغلَّفة في التخزين الدائم غير قابلة للتمييز بايتيًا عن نص مشفّر تعسفي. استراتيجية الحاصد "استعلام GraphQL عن رفعات بصيغة qub، فكّ تشفير بالجملة بتوقيعات drand العامة" لا تنتهي بنص عادي.
- وضعية خصوصية تمزيق التشفير. قانون qub.social حرفيًا لا يستطيع فكّ تشفير مجموعته الخاصة. تصل المذكرات إلى النص المشفّر، لا النص العادي.
- سلّم سرية من مستويين. الافتراضي = وصول مُتحكَّم به بالرابط (هذا القسم). تُكدِّس qubs الخاصة المشفّرة لمُستقبِل (ميزة محجوزة من المرحلة الثانية، لم تُحدَّد بعد) فوق ذلك كمستوى ثانٍ.
13.2 الطبقات
plaintext body ← QubEnvelope.body (§2.2)
↓ canonical CBOR (§3)
envelope CBOR
↓ tlock encrypt to drand round (§7 step 10)
tlock_ciphertext (inside SealedQub) (§2.3)
↓ canonical CBOR (§3)
SealedQubCbor bytes ← inner wire artifact
↓ AES-256-GCM(K, nonce, AAD=qub_id) (§7 step 12a, this section)
OuterWrapper CBOR bytes ← uploaded to permanent storage (§7 step 15)
الختم وفكّ القفل في طبقة البروتوكول (§7، §8) لم يتغيّرا تحت حدود الغلاف؛ يلتصق الغلاف عند موقع استدعاء seal() وينفصل عند موقع استدعاء unlock().
13.3 هيكل بيانات OuterWrapper
struct OuterWrapper {
version: u8, // 0x01, see §12.4
qub_id: [u8; 32], // copied from inner SealedQub; AEAD AAD
nonce: [u8; 12], // 96-bit AEAD nonce
ciphertext: Vec<u8>, // AES-256-GCM(K, nonce, SealedQubCbor, AAD=qub_id) || 16-byte tag
}
ثوابت الحقول.
- يَجِب أن يساوي
versionالقيمة0x01لبايتات غلاف v1.0. - يَجِب أن يساوي
qub_idحقلqub_idفي SealedQub المُستعاد بعد فكّ الغلاف. لا تَفرض خطوة فكّ الغلاف هذا مباشرة (إذ يجعل ربط AAD في AEAD العبث على مستوى البايت مستحيلًا)، لكن طبقة فكّ القفل تتحقق من العلاقة تَعَدّيًا: إذا غلَّف مُنْشِئSealedQubCborلا يطابقqub_idالداخلي فيهqub_idللغلاف، تفشل §8 الخطوة 11. - يَجِب أن يكون
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
C := AES_256_GCM_encrypt(key=K, nonce=N, msg=S, aad=Q)
// C includes the 16-byte authentication tag at the end
return canonical_cbor_encode(OuterWrapper{
version: 0x01,
qub_id: Q,
nonce: N,
ciphertext: C,
})
unwrap_sealed_qub(OuterWrapper bytes W, key K):
require K.len() == 32
O := canonical_cbor_decode(W) as OuterWrapper
require O.version == 0x01 // §12.4
P := AES_256_GCM_decrypt(
key=K, nonce=O.nonce, ciphertext=O.ciphertext, aad=O.qub_id
)
// any AEAD failure → DECRYPT_FAILED, indistinguishable to caller
return P // P is the inner SealedQubCbor
انهيار وضع الإخفاق. K خاطئ، nonce خاطئ، عدم تطابق AAD، ونص مشفّر معبوث به تُنتج جميعًا نفس الخطأ DECRYPT_FAILED. هذه خاصية AEAD مقصودة: التمييز بين وضع الإخفاق سيُنشئ قناة جانبية يستطيع مهاجم بعيد سبرها بإرسال أغلفة مشوَّهة وحساب زمن الاستجابة. يَجِب على التنفيذات المرجعية طيّ كل إخفاقات AEAD إلى شكل خطأ واحد.
13.6 مادة المفتاح والتوزيع
مفتاح التغليف K هو قيمة عشوائية موحَّدة بطول 256 بتًا تُولَّد لكل qub بواسطة CSPRNG. تستمدّه التنفيذات المرجعية من:
- منشئ WASM:
getrandom(WebCrypto تحت الواجهة الخلفيةwasm_js). - مستدعي API الختم على جانب الخادم: الـ CSPRNG المحلّي الخاص به؛ يُقدّم المُستدعي
Kويحتفظ به كـwrapper_key_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. - qubs الخاصة المشفّرة لمُستقبِل (ميزة محجوزة من المرحلة الثانية، لم تُحدَّد بعد) تتركب فوق هذا الغلاف بوصفه مستوى سرية ثانٍ؛ يمكن أن يكون كلا المستويين نشطين في الوقت نفسه.
- المواثيق (§6، content_type
0x03) تُغلَّف تمامًا مثل qubs النصية؛ الغلاف عمي بايتيًا عن نوع المحتوى الداخلي.
13.8 qubs العامة (حذف الغلاف)
الغلاف الخارجي اختياري في طبقة التَّسْليم. يجوز للمُنْشِئ ختم qub بوصفه عامًا، وفي هذه الحالة يُكتب SealedQubCbor القانوني إلى التخزين الدائم مباشرةً، دون طبقة OuterWrapper ودون مفتاح K:
SealedQubCbor bytes ──(public)──▶ uploaded to permanent storage as-is
SealedQubCbor bytes ──(private)─▶ AES-256-GCM(K, …) ▶ OuterWrapper ▶ uploaded
qub العام مُقفل زمنيًا لكنه غير محكوم بالرابط: يبقى غير قابل للقراءة حتى تنشر جولة drand الخاصة به (طبقة tlock لم تتغير)، لكن بعد فكّ القفل يستطيع أي شخص يملك arweave_tx_id فكّ تشفيره — لا يلزم جزء URL، إذ لا وجود لـ K. هذه هي المقايضة المقصودة للأسطح التي يَجِب أن يقودها الخادم: رسائل إشعار الكشف بالبريد الإلكتروني، والتضمينات من أطراف ثالثة، وتحسين محركات البحث الأغنى بعد الكشف — كلها تحتاج إلى رابط يعمل دون سرّ لا يحوزه الخادم أبدًا (§13.6).
عواقب يَجِب على المُنتِج أخذها في الحسبان:
- لا حصانة تعداد. تتخلّى qubs العامة عن خاصية حصانة التعداد في §13.1 بحكم البناء. تَختِم خدمة الرفع المرجعية وسم تخزين دائم
Visibility: publicعليها (وعليها وحدها) لتكون قابلة للاكتشاف عمدًا؛ لا تحمل qubs الخاصة أي وسم كهذا وتحتفظ بعدم تمييزها البايتي. - عنوان النص العادي مكشوف وقت الختم. حقل
titleفي §3.2 هو نص عادي داخلSealedQubCbor. تحت الغلاف يبقى مخفيًا حتى يُقدّم المشاهدK؛ دون الغلاف يكون قابلًا للقراءة عالميًا على التخزين الدائم منذ لحظة الرفع، قبل فكّ القفل. يَجِب على تطبيقات المُنْشِئ المطابقة الإفصاح عن ذلك وقت الختم. - الكشف بنيوي. يميّز المشاهد/التضمين المطابق بين الشكلين بالتحليل: البايتات التي تُحلَّل بوصفها
OuterWrapperتسلك مسار فكّ الغلاف بـK؛ والبايتات التي تُحلَّل بوصفهاSealedQubCborمجردًا تُقبل مباشرةً. لا يلزم أي راية على السلك، وqub_idلا يربط الظهور — المحتوى نفسه متطابق بايتيًا في طبقةSealedQubسواء خُتم عامًا أو خاصًا.
الخاص (المُغلَّف) يبقى الافتراضي؛ والعام خيار مُنْشِئ صريح لكل qub.
14. متّجهات الاختبار
14.1 اشتقاق qub_id
Input:
version = 0x01
content_type = 0x01
created_at = 1735689600 (2025-01-01 00:00:00 UTC)
unlock_at = 1736294400 (2025-01-08 00:00:00 UTC)
outcome_at = absent
drand_round = 4695445 (= (1736294400 - 1595431050) / 30, drand mainnet params §14.2)
body = "Hello, future." (UTF-8, 14 bytes)
title = absent
Intermediate:
body_hash = SHA3-256("Hello, future.")
= 76ab8b3f843c6ed4f2d0fd75b9f457b4
ad49dd4450f9c22723ae430e3af3211d
title_hash = [0u8; 32] (title absent — §4.2.1 sentinel)
Domain separator (10 bytes):
[0x51, 0x55, 0x42, 0x5F, 0x49, 0x44, 0x5F, 0x56, 0x32, 0x00]
Preimage (108 bytes — V1.2):
domain_separator || // 10 bytes
0x01 || // version
0x01 || // content_type
0x0000000067748580 || // created_at as i64 big-endian (1735689600)
0x00000000677DC000 || // unlock_at as i64 big-endian (1736294400)
0x0000000000000000 || // outcome_at_or_zero (outcome_at absent)
0x000000000047A595 || // drand_round as u64 big-endian (4695445)
body_hash || // 32 bytes
title_hash // 32 bytes (all-zeros sentinel; title absent)
Expected output:
qub_id = SHA3-256(preimage)
= 3a9fcb31b750d985c262fada6d4f777f
d6a28be831d941d85c131f5a4bbaf8a4
يَجِب على التنفيذات إنتاج قيم body_hash وqub_id متطابقة لهذا المدخل. يَنْبَغي أن يكون متّجه الاختبار هذا أول اختبار وحدة يُكتب. حُسبت القيم القانونية أعلاه بواسطة التنفيذ المرجعي ويَجِب أن تتطابق بتًا ببتٍ. ترتيبات الصورة الأولية التاريخية (ما قبل الإطلاق — لا توجد qubs نشطة كانت تعتمد عليها): كان qub_id لـ V1.0 (بصورة أولية من 92 بايتًا) هو 3d9fc2390eab043d38a1669ed3b71be76f9eefe872b9569ab1aaa027b88392b0؛ وكان qub_id لـ V1.1 (بصورة أولية من 100 بايت، بعد طيّ outcome_at_or_zero) هو b0d032898ad629795150fdcb3f84e518f59ed05b7a2a82bc24ebdb87f52144ed. يطوي V1.2 drand_round ضمنها ويرفع فاصل النطاق إلى QUB_ID_V2.
14.2 ربط جولة فكّ القفل
Input:
unlock_at = 1735689600
chain_genesis_time = 1595431050
chain_period_seconds = 30
Calculation:
(1735689600 - 1595431050) / 30 = 4675285.0
ceil(4675285.0) = 4675285
drand_round = 4675285
14.3 جولة CBOR قانونية ذهابًا وإيابًا
يَجِب على التنفيذات التحقق من أن serialize(parse(serialize(qub))) == serialize(qub) لكل المدخلات الصالحة. هذا اختبار خصائص، لا متّجه واحد.
14.4 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).
يثبّت التثبيت حاليًا ثلاث حالات:
| الحالة | التغطية |
|---|---|
basic-text-public |
أصغر شكل واقعي لـ SealedQub؛ بلا حقول اختيارية. يُؤسّس شكل الغلاف القانوني لـ qub نمطي بإصدار v1.0. |
with-recipient-pubkey |
SealedQub مع تعيين recipient_pubkey (مسار المرحلة 2). مجموعة مفاتيح CBOR داخلية مختلفة وqub_id مختلف. |
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). لا يُعرّف سجل §9.2 أي قيم أخرى؛ يَجِب على مُتحقِّق v1 رفض كلsig_algخارج{0x00, 0x01}. يُتوقَّع إدخال Ed25519 مستقبلي (§15.3) لكنه غير مُخصَّص في v1. - القفل الزمني: drand quicknet فقط — تجزئة السلسلة والمفتاح العام ووقت التكوين والفترة هي معاملات شبكة ثابتة يحملها
DrandTimelockProvider::quicknet()المرجعي (crates/qub-core/src/tlock.rs) وconfig/drand-endpoints.json. - الغلاف الخارجي: AES-256-GCM v1 فقط (§13).
يُرسّخ المُتحقِّقون حاليًا أطوال المفتاح والتوقيع لكل عنصر أولي. لا يُعرَّض أي سطح مرونة بواسطة الصيغة السلكية.
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 ثانية في الاستخدام الإنتاجي.
- إصدار غلاف خارجي ثانٍ.
حتى ذلك الحين، §15 هو حافظ مكان يُثبّت شكل الهجرة كي تنزل PRs المستقبلية على هدف معروف بدلًا من إعادة التقاضي على سطح التفاوض من الصفر.
16. سجل الشفافية وطبقات المتانة (تصميم — اكتملت المراجعة)
الحالة. هذا القسم مواصفة تصميم. الصيغ السلكية والتجزئة ونموذج الثقة أدناه معيارية بالنسبة للتنفيذ، لكن لم يُشحَن بعد أي كود لسجل الشفافية. اكتملت مراجعة W5 الخارجية: تسجّل §16.15 القرارات المحسومة وقيود الإطلاق المُلزِمة التي نتجت عنها. يَجوز أن يمضي التنفيذ تحت تلك القيود. تبقى §16 استشرافية بالمعنى نفسه لـ §15 — فهي تثبّت الهدف كي ينزل التنفيذ على تصميم مستقر بدلًا من إعادة اشتقاق نموذج الثقة في مراجعة الكود. وهي إضافية بحتة — يحتفظ كل qub قائم بمعاملة Arweave الفردية الخاصة به، ولا تغيير على الصيغة السلكية لـ
SealedQub/QubEnvelope.
16.1 المبرر وطبقات المتانة
اليوم تستند متانة qub والتزامه الزمني كلاهما إلى معاملة Arweave واحدة لكل qub (§11). يقرن هذا زمن انتظار الختم بنهائية Arweave، ويجعل الرفع لكل qub سقف تكلفة منتج (ARWEAVE_DAILY_CEILING)، ولا يوفّر ترتيبًا كاشفًا للعبث عبر qubs. يضيف سجل الشفافية طبقتين تحت تلك الطبقة الواحدة وحولها:
| الطبقة | الاسم | الضمان | متى |
|---|---|---|---|
| T1 | إقرار متزامن R2-أولًا | أرضية المتانة — تُكتب البايتات المختومة إلى تخزين دائم قبل أن يعود الختم (< 300 ms p95). |
كل qub، بشكل متزامن (§16.10). |
| T2 | إدراج مُجمَّع في سجل الشفافية | التزام شامل إلحاقي فقط كاشف للعبث + ترتيب كلي، مُرسَّى إلى Arweave. | كل qub، مؤجَّل + مُجمَّع (§16.5–16.7). |
| T3 | دوام Arweave لكل qub | معاملة Arweave فردية لـ qub. | بيع مدفوع إضافي، واحتياطي عند عدم توفر Arweave (§16.8). |
تجعل T2 من Arweave لكل qub خيارًا (T3) بدلًا من أن يكون مسار المتانة الوحيد. يُتقاعَد ARWEAVE_DAILY_CEILING كسقف منتج ويُخفَّض إلى قاطع دائرة على محفظة الترسيخ المخصّصة فقط (§16.7)؛ لا تُرفض أختام المستخدمين أبدًا لتجاوزها.
صدق المتانة (محسوم — §16.15 Q6). المتانة لا تتراجع: كتابة R2 لـ T1 متزامنة وتُكتب مرة واحدة، لذا فإن qub المجاني الذي لم يشترِ T3 متين بالكامل لحظة عودة الختم. ما يخشن هو وقت الالتزام الأقصى القابل للإثبات: لـ qub مجاني يصبح وقت كتلة الترسيخ بدلًا من وقت كتلة معاملة لكل qub. عند حجم منخفض — وهو الحالة الواقعية للإطلاق المبكر وخارج أوقات الذروة — تكون الوتيرة اليومية الكاملة هي الأرضية المعتادة، لا حالة حدية نادرة. لذا فإن تأطير المنتج هو حد أعلى بلا زمن انتظار رقمي ملتزَم به — "مختوم ومتين الآن؛ يُضاف طابع زمني عام مستقل عند الترسيخ التالي للسجل (عادة يوميًا)" — وإثبات الالتزام بالساعة الدقيقة هو خاصية مدفوعة لـ T3، يُفصح عنها في سطح مقارنة الطبقات وفي الشروط (§16.11، §16.15 Q6). أي حد زمني هو SLO داخلي فقط، لا SLA مُسوَّق أبدًا.
16.2 هيكل LogLeaf (شكلان ملتزَم بهما)
إدخال السجل هو LogLeaf، مُرمَّز كـ CBOR قانوني مكتوب يدويًا تحت ملف §3.1 (طول محدَّد، بلا وسوم، بلا أعداد عشرية، أعداد صحيحة بأقصر صيغة، نص NFC، حقول اختيارية تُحذف عند الغياب، مفاتيح مرتبة تصاعديًا حسب طول البايتات المُرمَّزة ثم بايتيًا). يُطبَّق حارس §3.1 القانوني parse → re-encode → compare على مسار الترميز قبل التجزئة (لا على فكّ الترميز فقط)، كي لا يختلف تنفيذان على بايتات الورقة عبر فرق في عرض عدد صحيح أو ترتيب مفتاح. كل الأعداد الصحيحة هي u8 / u64 / i64؛ كل المُلخّصات سلاسل بايتات بطول 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، الذي لا يملك العامل مفتاحه أبدًا. مسار الختم على الخادم فقط (POST /api/v1/seal) يشتق body_hash / drand_round من النص العادي. لذا فإن شكل ورقة واحدًا يحمل body_hash + drand_round سيلتزم بقيم لم يتحقق منها العامل لأغلبية qubs الحقيقية. يُبقي الانقسام كل قيمة ملتزَم بها صادقة:
| المفتاح | طول الترميز | النوع | الحضور | المعنى |
|---|---|---|---|---|
seq |
4 | u64 |
مطلوب | فهرس الورقة العام صفري الأساس؛ الموضع الذي يلتزم به إثبات الإدراج. |
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. غير استدلالي (مؤكَّد من العامل؛ §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 مؤكَّد من التحقق القائم من حزمة .qub في §11، لا من السجل (§16.11). drand_chain_version ليس في الورقة (هو داخل الغلاف على المسار الافتراضي)؛ تعيش دقّة السلسلة على المرساة (§16.7). انضباط المُرمِّز: ارفض ref أو chash المكوّن كله من أصفار، وارفض unlock_at / received_at غير الموجب، مرآةً لحارس outcome_at > 0 في cbor.rs.
16.2.1 إعماء qub الخاص
يَجِب ألّا يصبح السجل أداة التعداد الكاشفة التي يوجد الغلاف الخارجي لـ §13 لمنعها (§13.1). لـ qub خاص (مُغلَّف) تلتزم ورقة asserted بالمعرّف المُعمّى SHA3-256(qub_id ‖ log_blind_secret)، حيث log_blind_secret سرّ يحتفظ به الخادم، وتحذف body_hash. لا يستطيع طرف ثالث ربط ورقة كهذه بـ qub_id بعينه؛ ويستطيع حامل qub، الذي يملك رابط التَّسْليم ومن ثَمّ qub_id، إعادة حساب الإعماء لتأكيد إدراجه الخاص. أما qub العام (قابل للتعداد أصلًا، ويحمل أصلًا وسم Visibility: public في Arweave وفق §13.8) فيلتزم بـ qub_id خام. هذا الموضع الوحيد الذي تتنازل فيه قابلية التحقق المستقلة عمدًا لصالح ثابت خصوصية حامل للحمل؛ الرابط المستقل لـ qubs الخاصة هو chash (§16.9).
حفظ log_blind_secret (محسوم — §16.15 Q4). يحمي الإعماء عدم قابلية ربط الورقة، لا سرية النص العادي (يحمل غلاف §13 تلك مستقلًا). عند تسريب log_blind_secret، فلأي qub_id يملكه الخصم أصلًا أو يستطيع إعادة بنائه (كل qub لديه حزمته/رابطه، إضافةً إلى أي 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) محجوزان ومنفصلان عن هذين. وهما بايت واحد فلا يمكن أن يتصادما مع فواصل المجال الموجودة بطول 10 بايتات ASCII (QUB_ID_V2، إلخ). الشجرة هي شجرة RFC 6962 غير المتوازنة المملوءة يسارًا (كل انقسام داخلي عند أكبر قوة للعدد اثنين أصغر بدقّة من عدد أوراق الشجرة الفرعية)، ما يتيح لإثباتات الإدراج والاتساق أن تتشارك خوارزمية مسار تدقيق واحدة. تحمل المواصفة المرجعية شفرة استنباط صريحة لليسار/اليمين، وتثبّت متّجه اختبار غير قوّة للعدد اثنين (5 أوراق) كي تُختبَر حالة ترقية الحافة اليمنى — التي يخفيها متّجه 4 أوراق.
16.4 تسلسل التجزئة (داخلي)
يحتفظ LogDO بسلسلة إدخالات داخلية لاتساق الأعطال فقط. وهي لا تُنشر أبدًا ولا تُواجِه المُتحقِّق أبدًا:
entry_chain[seq] = SHA3-256(0x02 || entry_chain[seq-1] || leaf_hash[seq])
entry_chain[-1] = SHA3-256("QUB_TLOG_GENESIS_V1")
سلطة الإلحاقي فقط المنشورة هي جذر Merkle التراكمي + مرساته (§16.5–16.6)، لا الترتيب الخام الذي يصادف العامل تقديم الأوراق به: تُعاد حوسبة السلسلة لأي ترتيب مُقدَّم، لذا يثبّت الجذر المُرسَّى وحده الموضع القانوني.
16.5 شجرة Merkle التراكمية والتجميع
هناك شجرة RFC 6962 واحدة دائمة النمو على كل الأوراق بترتيب seq — لا أشجار معزولة لكل دفعة. (رُفض بناء مُسلسَل بورقة حمل لكل دفعة: فهو ليس علاقة بادئة حقيقية، لذا فإن "إثباتات اتساقه" غير سليمة.) تعطي الشجرة التراكمية إثباتات اتساق RFC 9162 حقيقية وتتيح لمرساة حديثة واحدة أن تثبت الإدراج لأي qub أقدم.
الكائن الدائم LogDO هو الكاتب الوحيد (blockConcurrencyWhile، مرآةً لـ QuotaDO / EntitlementDO) — فالإلحاق بسجل مشترك هو قراءة-تعديل-كتابة على حالة مشتركة فلذا يَجِب أن يمر عبر DO، لا عبر KV أبدًا. يخزّن مؤقتًا جبهة الحافة اليمنى للشجرة (تجزئات O(log n)) كي يكون إغلاق دفعة O(batch). الدفعة هي مجموعة الأوراق المُرسَّاة معًا؛ ومُحفِّزاتها قابلة للتهيئة، لا مُجمَّدة في البروتوكول: تقدّم tree_size بمقدار LOG_BATCH_MAX_LEAVES على الأقل (افتراضيًا 4096)، أو بلوغ العمر وتيرة الترسيخ، أو دفق قسري عند نزول ختم T3 مدفوع. root_i هو تجزئة شجرة Merkle التراكمية على الأوراق 0 .. tree_size_i.
16.6 رأس الشجرة الموقّع عبر مرساة Arweave
معاملة مرساة Arweave هي رأس الشجرة الموقّع وتحل محل توقيع العامل لرأس الشجرة نفسه: لا تحتاج المرساة اليومية إلى مفتاح qub لأن owner معاملة Arweave هو التوقيع. تصمد أطروحة الخندق — الركيزة غير القابلة للتغيير، لا سرّ يحمله qub، هي الحاملة للحمل للجذر المُرسَّى.
يوجد بالضبط مفتاح توقيع qub دافئ واحد في التصميم، وهو مثبَّت: مفتاح الإيصال لكل ختم (§16.10). يُلتزَم بمفتاحه العام في LogProfile (يُوزَّع مع المُتحقِّق) ويُوقَّع عليه تقاطعيًا بواسطة anchor_owner، كي يصادق المُتحقِّق على إيصال مقابل الجذر المثبَّت نفسه للمرساة. هذا حل §16.15 Q2 — مفتاح إيصال غير مثبَّت قابل لتدوير العامل سيكون قابلًا للإنكار (يستطيع العامل إنكار أن المفتاح له)، ما يُبطل قيمة مساءلة الإيصال أمام الخصم على مستوى العامل الذي يوجد الإيصال لردعه. إذن: لا يحمل qub مفتاح توقيع سجل غير مثبَّت؛ مفتاح الإيصال مثبَّت ومُوقَّع تقاطعيًا بـ anchor_owner.
SignedTreeHead هو CBOR قانوني (مفاتيح حسب طول الترميز): size:u64، root:bstr[32]، batch:u64، prev:bstr[32] (sth_hash السابق؛ التكوين = 32 بايت صفرية)، log_id:bstr[32]، first_seq:u64، anchored_at:i64. وتجزئته sth_hash = SHA3-256(0x03 || canonical_cbor(SignedTreeHead)).
جذر الثقة المثبَّت. log_id = SHA3-256("QUB_TLOG_V1" || anchor_owner_address). يَجِب على مُتحقِّق مطابق أن يشترط anchor_tx.owner == LogProfile.anchor_owner، حيث anchor_owner (والمفتاح العام لمفتاح الإيصال) مخبوز في qub_core كـ LogProfile — إلى جانب ثوابت quicknet الموجودة أصلًا في DrandTimelockProvider::quicknet() — ويُوزَّع مع ثنائي المُتحقِّق. يَجِب على المُتحقِّق أيضًا أن يتحقق من ربط بيانات معاملة Arweave ← tx_id محليًا بدلًا من الوثوق باستجابة /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 → تأكيد المرساة (≤ الوتيرة + نهائية Arweave). داخلها تكون الضمانات الوحيدة هي إيصال الختم المثبَّت (§16.10) وسلامة qub التشغيلية. ثلاثة مصنوعات مساءلة تجعل هذا صادقًا لا مُموَّهًا (نموذج الشاهد هو حل §16.15 Q2):
- إيصال ختم موقّع مثبَّت — نظير SCT المُعاد في استجابة الرفع (§16.10)، موقّع بمفتاح الإيصال المثبَّت المُوقَّع تقاطعيًا بـ
anchor_owner. ورقة أُسقطت قبل مرساتها تترك للضحية إيصالًا غير قابل للإنكار لنشره، ما يُغلق ثغرة الحذف الصامت. - منهجية مراقبة منشورة + سير سلسلة prev — تُسار سلسلة
prevللمرساة من الرأس ← التكوين؛ التشعّب (مرساتان عندsizeواحد بـrootمختلف، أوprevمكسور) هو دليل قابل للنشر على سوء السلوك. كشف المراوغة التزام تشغيلي مذكور، لا افتراض صامت. - رؤوس ثنائية منشورة ذاتيًا — يُنشر كل رأس جديد
{sth_hash, tree_size}إلى مستودع GitHub عام إلحاقي فقط مملوك لـ qub مخصَّص (الساق الحاملة للحمل من النشر الذاتي الكاشف للعبث)، مع منشور اجتماعي كتأييد أفضل-جهد فقط. يَجِب أن يُنبّه نشر فاشل (لا أن يفشل صامتًا).
حد الصدق (قيد مُلزِم). لأن qub يتحكم في كلا سطحي النشر، فهذا منشور ذاتيًا، لا مشهود مستقلًا. لا يَجوز لأي سطح منتج أو تسويق أو قانوني أن يدّعي أن السجل "مشهود مستقلًا"؛ الادّعاء المسموح هو أن المراوغة قابلة للكشف وتترك إيصالًا غير قابل للإنكار. يُؤجَّل شاهد طرف ثالث مستقل حقيقي إلى رفع حوكمة §15 مستقبلي.
received_at مؤكَّد من العامل ولا يَجوز أن يستند إليه أي ادّعاء — لا يُعرَض أبدًا كإثبات أو كتأييد نزاع على أي سطح منتج / قانوني / API / تصيير إثبات. وقت كتلة مرساة Arweave T هو الطابع الزمني الوحيد عديم الثقة (حد أعلى على "سُجِّل بحلول"). أي فحص سلامة مراقبة على 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 النافذة — quicknet)، ودفق CBOR لأوراق الدفعة بترتيب seq كي تكون المرساة قائمة بذاتها: يعيد المراقب اشتقاق root من الجسم بلا أي اعتماد على qub. (إن صار دفق الأوراق كبيرًا عند حجم عالٍ، فقد تلتزم مراجعة مستقبلية بنطاق أوراق بالمرجع فقط؛ مذكور، غير معتمد في v1.)
وسوم Arweave قابلة للتعداد عمدًا — المقصود أن يُعثر على السجل، بخلاف qubs الخاصة: 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 هو السلطة الوحيدة.
الوتيرة: يومية افتراضيًا، يُعاد النظر فيها مع الحجم (مُحفِّز الحجم يُقصّر الوتيرة الفعلية تلقائيًا تحت الحمل)؛ يفرض ختم T3 مدفوع مرساة كي لا ينتظر العملاء الدافعون يومًا. محفظة المرساة مخصَّصة ومنخفضة السرعة، منفصلة عن محفظة الرفع — يَجِب أن تكون JWK خاصها (مفتاح متمايز، لا دور منطقي على محفظة الرفع) كي لا يستطيع تسريب محفظة الرفع تزوير المراسي — مع ميزانية صارمة لمعاملات المرساة في اليوم (ARWEAVE_DAILY_CEILING المُخفَّض). وضعية الحفظ مذكورة بصراحة: مفتاح دافئ ضيق النطاق بقاطع دائرة محكم ورصيد منخفض، لا "بارد" — محفظة توقّع تلقائيًا يوميًا لا يمكن أن تكون باردة، والمواصفة لا تتظاهر بخلاف ذلك.
16.8 مُجمِّع ANS-104
مُرمِّز DataItem داخلي بصيغة ANS-104 وموقّع تجزئة عميقة، نحو 300 سطر، Web Crypto فقط، بصفر تبعيات npm (كلتا حزمتَي Turbo SDK تفشلان في بوابة سلسلة التوريد npm ci --ignore-scripts). تخطيط بايتات DataItem:
signatureType (2, LE) || raw_signature || owner || target(flag+0|32) || anchor(flag+0|32) || num_tags(8, LE) || tags_len(8, LE) || avro_tags || data
التوقيع هو deepHash لـ Arweave — مُلخّص SHA-384 تكراري (متطلب سلكي لـ Arweave، crypto.subtle.digest("SHA-384")) على ["dataitem", "1", sig_type, owner, target, anchor, encoded_tags, data] — ثم RSA-PSS على التجزئة العميقة بـ JWK المحفظة عبر crypto.subtle؛ id = base64url(SHA-256(signature)). الـ SHA-384 هنا محجور كعنصر أولي سلكي لـ Arweave فقط، لا عنصر ثقة أولي لـ qub أبدًا (تسجّل §15 الحاجز؛ تجزئة ثقة qub هي SHA3-256 في كل مكان).
مسار كود واحد يخدم ثلاثة مستهلكين: دوام T3 لكل qub مدفوع، واحتياطي عدم توفر Arweave (صفّ DataItem في الطابور، أعد إقرار R2-أولًا بغض النظر — هذا يُغلق المأزق الحالي ARWEAVE_UNAVAILABLE 503)، وكتابة AnchorBundle. مخطط التوقيع (محسوم — §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، وإثبات إدراج، وإثبات اتساق — في اتجاهَي التوقيع والتحقق كليهما (يهمّ اتجاه التحقق لأن فحص §16.6 المحلي معاملة ← tx_id يجرّ التجزئة العميقة إلى كل مُتحقِّق مستقل، لا الكاتب فقط). - جولة تشغيل بيني لمرة واحدة عبر مُجمِّع 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 مخزن عقد Merkle دائم مُفهرسًا بالإحداثية المطلقة للشجرة (level, index) — لا فروقات عقد لكل batch. مع مخزن مُفهرس بالإحداثيات، يكون أي مسار تدقيق لـ (leaf i, size N) مجموعة من O(log N) عمليات GET مباشرة على R2 بلا إعادة حوسبة عبر حدود الدفعات؛ مع مخزن مُفهرس بالدفعة ليس كذلك، وهي فجوة تخطيط التخزين التي يُغلقها هذا الحل. كذلك تكون أجسام الأوراق قابلة للعنونة بالمحتوى بـ seq. يَجِب أن يثبت متّجه اختبار W5 ورقة باردة من حقبة التكوين مقابل جذر لاحق بكثير باستخدام R2 + Arweave فقط مع محو تخزين LogDO، كي يكون ادّعاء سلامة الاستصلاح في §16.13 مدعومًا لا مُؤكَّدًا فحسب. عمليات GET المتسلسلة O(log N) على R2 تنتمي فقط إلى نقطة نهاية الإثبات اللامتزامنة — لا إلى مسار الختم الساخن (§16.10) ولا إلى cron لكل تكّة.
16.10 ترتيب إقرار R2-أولًا
يصبح تسلسل POST /api/v1/upload:
- بوّابات النصف الأمامي (مصادقة، تحقق، مفتاح شارد العمليات العديمة الأثر) — بلا تغيير.
- بشكل متزامن
await QUB_CACHE.put(qub-cache/<tx_id>, wrappedBytes)— أرضية المتانة؛ يُغلق أيضًا سباق الذاكرة المؤقتة المسبقة لـ W1 (سابقًاctx.waitUntilبعد تقديم Arweave). - بشكل متزامن
await LogDO.append(leaf)— استدعاء RPC واحد لـ DO داخل المركز؛ يسنِد الكاتب الوحيدseq، ويمدّد سلسلة الإدخالات، ويحدّث الجبهة. (قراءة-تعديل-كتابة على حالة مشتركة → DO، لا KV أبدًا.) يفعل RPCappendذلك فقط — يجري عمل إغلاق دفعة MerkleO(batch)خارج هذا الـ RPC على إنذار LogDO، وإلا ارتفع p95 للإلحاق كل ختم رقمه مضاعف لـLOG_BATCH_MAX_LEAVES. - أعد الإقرار الآن — مع إيصال الختم (موقّع بمفتاح الإيصال المثبَّت، §16.6) و
{ tx_id, log_seq, anchor_status: "pending" }. يُزال انتشار Arweave متعدد الثواني من المسار الحرج. ctx.waitUntilواحد يصفّ العمل المؤجَّل في الطابور: تقديم Arweave لكل qub (الآن أفضل-جهد / مدفوع؛ عند الفشل يُوجَّه إلى صفّ احتياطي المُجمِّع بدلًا من إعطاء المستخدم 503) إضافةً إلى كتابات البيانات الوصفية المؤقتة الموجودة. يجري إغلاق الدفعة والترسيخ مستقلين من إنذار LogDO و cron المرساة اليومي. لاctx.waitUntilداخل حلقة؛ يُحفَظ مفتاح شارد العمليات العديمة الأثر الموجود.
ميزانية زمن الانتظار (محسوم — §16.15 Q7). هدف < 300 ms p95 هو بوّابة إطلاق مقيسة، لا افتراض. المسار الحرج الصادق هو قراءات KV للنصف الأمامي + عملية PUT واحدة على R2 + كائنان دائمان متسلسلان — خصم حصة ختم QuotaDO الموجود وإلحاق LogDO الجديد — لذا يَجِب أن تحسب الميزانية لجولتَي DO داخل المركز، لا واحدة. اشحن إنذار زمن انتظار لـ LogDO يحاكي إنذار QuotaDO، وعامِل انحدار p95 كحاجب إصدار.
16.11 نموذج الثقة — الادّعاء الدقيق، محدَّد بنوع الورقة
لـ kind=0x01 (مُصدَّق): "هذا المحتوى — جسم يطابق body_hash، مُعرَّف بـ qub_id — التُزِم به في سجل qub الإلحاقي فقط عند الموضع seq ووُجِد في موعد لا يتجاوز وقت كتلة Arweave T؛ وكان غير قابل للقراءة تشفيريًا حتى جولة drand R = unlock_round(unlock_at)." هذا الثلاثي الكامل {ربط جولة tlock + إدراج Merkle + جذر مُرسَّى}.
لـ kind=0x02 (مؤكَّد، الافتراضي): "نص مشفّر غامض بعنوان المحتوى chash، مدّعيًا qub_id وunlock_at، التُزِم به في السجل الإلحاقي فقط عند الموضع seq ووُجِد في موعد لا يتجاوز وقت كتلة Arweave T." تُقدَّم ساقا الجولة والجسم من التحقق القائم من حزمة .qub في §11 (qub_core::unlock)، لا من السجل؛ ما يضيفه السجل فوق معاملة لكل qub مجردة هو ترتيب كاشف للعبث، ووقت التزام أقصى عديم الثقة، ومقاومة للمراوغة.
يستثني كلا الادّعاءين، وفق §11: التأليف بلا sig_alg ≥ 0x01، والنية، والتوقيت بدقّة دون المرساة. ولا يجعل أيٌّ منهما أي ادّعاء يستند إلى received_at.
سقف الادّعاء (قيد إطلاق مُلزِم — محسوم §16.15 Q1). لـ qubs المجانية / الافتراضية (kind=0x02)، فإن الادّعاء المحدَّد لـ kind=0x02 أعلاه هو السقف على ما يَجوز أن يؤكّده أي سطح منتج أو تسويق أو شروط أو تصيير إثبات. لا يَجوز لأي سطح أن يذكر أو يُلمِح إلى أن السجل يثبت محتوى qub افتراضي أو جولة فكّ قفله — يثبت السجل الترتيب + وقت التزام أقصى عديم الثقة لنص مشفّر غامض. يأتي إثبات المحتوى والجولة حصرًا من التحقق القائم من حزمة .qub في §11، وهو مستقل عن السجل. هذا حاجب إطلاق صلب على النصوص، لا تفضيل أسلوبي؛ وهو الحل الذي يُبقي المسار الافتراضي الأعمى بايتيًا صادقًا.
16.12 الإصدارات وتنسيق W3
لا رفع للصيغة السلكية لـ SealedQub ومن ثَمّ لا رفع لإصدار البروتوكول (§12.1): السجل ركيزة جانبية تلتزم بحقول وبايتات موجودة، فلا يدخل سجل إصدار البروتوكول في §12.2. drand_chain_version الاختياري لـ W3 سليم ويبقى حقل SealedQub الاختياري الوحيد. يُدخل السجل بدلًا من ذلك مساحات إصدار مستقلة خاصة به — LOG_VERSION_1، ANCHOR_FORMAT_1، InclusionProof.ver — مرآةً لاستقلال إصدار الغلاف في §12.4 (يحمل الغلاف بايت إصدار مستقلًا عن إصدار البروتوكول، وتتبع إصدارات السجل الفصل نفسه).
تسليم الإثبات مجلوب افتراضيًا، مع توصيلة اختيارية. لا يمكن أن يوجد إثبات وقت الختم (لم تُكتب المرساة بعد)، لذا تبقى حزمة .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) قابل للاستصلاح بعد الترسيخ؛ ومادته التدقيقية — مخزن عقد Merkle المُفهرس بالإحداثيات (level, index) + أجسام الأوراق المعنونة بـ seq (§16.9) + مراسي Arweave — دائمة. استصلاح ورقة باردة من DO لا يُبطل أبدًا إثباتًا صادرًا، لأن الإثبات يُحَل مقابل مخزن عقد R2 الدائم ذاك ومرساة Arweave، لا DO (ومتّجه اختبار DO الممحو في §16.9 يثبت ذلك).
16.14 متّجهات الاختبار
تشحن W5 عُدّة الاختبار عابرة اللغات tlog_v1.json (§16.8) إضافةً إلى متّجهات معمولة: ورقة kind=0x01 وورقة kind=0x02 → leaf_hash؛ والجذر التراكمي لـ 5 أوراق؛ وإثبات إدراج واحد؛ وإثبات اتساق واحد؛ وAnchorBundle واحد؛ ومعرّف DataItem واحد. تعيش هذه إلى جانب متّجهات الغلاف الخارجي في §14.5، ويختبرها تنفيذا Rust (qub-core) وTypeScript (العامل) كلاهما.
16.15 قرارات المراجعة (W5 — محسومة)
اكتملت مراجعة W5 الخارجية (تمريرة تصميم خصامية + توقيع المالك). كل قرار أدناه مستقر ومنعكس في نص §16 أعلاه؛ تُعاد صياغة قيود الإطلاق المُلزِمة في النهاية. يَجوز أن يمضي التنفيذ تحتها.
- صدق ورقة المسار الافتراضي (
kind=0x02) — محسوم. اشحن انقسام نوعَي الورقة كما هو محدَّد: تلتزمkind=0x02بلاbody_hashولاdrand_round. لا حقل*_body_hashعلى المسار الأعمى بايتيًا (سيكون أكثر إشارة "موثَّقة" زائفة وضوحًا للمدمِجين، وهو يُسر يوفّره §11 أصلًا من الحزمة). لا تشترط ختم خادم لـ qubs المسجَّلة (إذ سيدفع النص العادي عبر العامل ويدمّر خندق تمزيق التشفير). أي اختصار وصف ذاتي ينتمي إلى حزمة.qub/ مغلّف الإثبات كحقل يعيد المُتحقِّق حسابه، لا حقل ورقة أبدًا. سقف الادّعاء المؤكَّد من المالك: §16.11. - مساءلة المراوغة / الحذف — محسوم. مفتاح إيصال الختم مثبَّت في
LogProfile+ مُوقَّع تقاطعيًا بـanchor_owner(يُغلق تناقض "لا مفتاح توقيع" السابق؛ §16.6). نموذج شاهد الإطلاق: إيصال مثبَّت + منهجية مراقبة + سير سلسلة prev + رؤوس ثنائية منشورة ذاتيًا (مستودع GitHub عام مملوك لـ qub، اجتماعي أفضل-جهد)، يُسوَّق كـ قابل للكشف + موصَل بإيصال، لا مشهود مستقلًا أبدًا. يُؤجَّل شاهد طرف ثالث حقيقي إلى رفع حوكمة §15. - جذر ثقة المالك المثبَّت للمرساة + إعادة التدوير — محسوم. اعتمد تثبيت
LogProfile(§16.6)؛ يفحص المُتحقِّقanchor_tx.owner == anchor_ownerويتحقق من ربط بيانات المعاملة ← tx_id محليًا. حوكمة إعادة التدوير هي امتداد لـ §15 يُبنى (مُحفِّز §15.3 مُضاف)، لا إعادة استخدام؛ إعادات التدوير المخطَّطة توقّع تقاطعيًا، وإعادات التدوير المدفوعة بتسريب ترتد إلى رفع §15 مع فحص التشعّب محدِّدًا للضرر. - إعماء ورقة qub الخاص — محسوم. أبقِ الإعماء لـ qubs الخاصة (
ref = SHA3-256(qub_id ‖ log_blind_secret))، وqub_idخام لـ qubs العامة (أصلًا §16.2.1)، وchashكالرابط المستقل.log_blind_secretسرّ من رتبة الترابط/Sybil، يُدوَّر أمامًا فقط (§16.2.1). received_at— محسوم. أبقِه في الورقة، ملتزَمًا به لكن غير استدلالي صراحةً؛ لا يُعرَض أبدًا كإثبات أو تأييد نزاع على أي سطح. أي فحص سلامة مراقبة يقارن مقابل وقت كتلة ArweaveT، لا مقابلanchored_atالذي يتحكم فيه العامل (§16.6).- التوقيت القابل للإثبات للطبقة المجانية — محسوم (توقيع المالك). المتانة لا تتراجع؛ فقط وقت الالتزام الأقصى القابل للإثبات يخشن إلى وقت كتلة الترسيخ. نصوص الطبقة المجانية لا تستخدم SLA رقميًا ("…يُضاف عند الترسيخ التالي للسجل، عادة يوميًا")؛ إثبات الساعة الدقيقة خاصية مدفوعة لـ T3، يُفصح عنها في سطح مقارنة الطبقات + في الشروط (§16.1).
- الشجرة التراكمية على Workers — محسوم. شجرة RFC 9162 تراكمية واحدة + LogDO كاتب وحيد مخزَّن جبهته مؤقتًا (هامش مريح مقابل سقف DO نحو 1k كتابة/ثانية؛ أجِّل تشظية شجرة-من-جذور-الشظايا حتى الاقتراب منه). شرط مسبق حاجب: مخزن عقد R2 مُفهرس بالإحداثيات
(level, index)+ متّجه اختبار الورقة الباردة لـ DO الممحو (§16.9)؛< 300 msبوّابة إطلاق مقيسة على كائنين دائمين متسلسلين (§16.10). - مخطط توقيع ANS-104 + التجزئة العميقة — محسوم. RSA-PSS (نوع التوقيع 1، بإعادة استخدام JWK محفظة المرساة المخصَّصة)؛ يُؤجَّل Ed25519 إلى مسار PQ في §15. التجزئة العميقة SHA-384 المكتوبة يدويًا مبوَّبة على عُدّة اختبار عابرة التنفيذ ثنائية الاتجاه، وفحص تشغيل بيني ثابت-فقط مع مُجمِّع مرجعي، وجولة
crypto.subtleالمشتركة، ومراقب قبول Arweave لاحق للتجميع (§16.8).
قيود الإطلاق المُلزِمة (تُحمل إلى التنفيذ + المراجعة المنتجية/القانونية):
- سقف الادّعاء (Q1/Q6). لا يَجوز لأي سطح أن يقول إن السجل يثبت محتوى qub افتراضي أو جولة فكّ قفله؛ الادّعاء المسموح هو مُرتَّب، بشكل كاشف للعبث، بوقت التزام أقصى عديم الثقة. نصوص الطابع الزمني للطبقة المجانية لا تحمل زمن انتظار رقميًا؛ إثبات الساعة الدقيقة مدفوع لـ T3 فقط.
- صدق الشاهد (Q2). سوّق المراوغة كـ قابلة للكشف + موصَلة بإيصال، لا مشهودة مستقلًا أبدًا.
- مفتاحا الإيصال والمرساة (Q2/Q8). مفتاح الإيصال مثبَّت + موقّع تقاطعيًا؛ محفظة المرساة JWK خاصها متمايز عن محفظة الرفع.
- بوّابة التجزئة العميقة (Q8). لا تُشحَن مرساة أو معاملة T3 حتى تجتاز عُدّة الاختبار ثنائية الاتجاه + فحص التشغيل البيني؛ مراقب القبول يُنبّه عند الفشل.
- شرط التخزين المسبق (Q7). مخزن العقد المُفهرس بالإحداثيات + متّجه الورقة الباردة لـ DO الممحو شرطان مسبقان لضمان "الاستصلاح لا يُبطل إثباتًا أبدًا".