Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

دراسة حالة: توسيع مكتبة التشفير (٥ مراحل + Argon2id)

بعد توحيد هاش/شفّر (هاش/شفّر/فك_تشفير في وحدة تأكيدات)، وُسِّعت اللغة بمكتبة تشفير حديثة كاملة في وحدة منفصلة تشفير — عبر RFC كامل (sadlang-rfcs/text/0000-توسيع-مكتبة-التشفير.md) نُفِّذ على ٥ مراحل + مرحلة إضافيّة (Argon2id) بطلب صريح من المالك رغم تأجيلها في نصّ RFC الأصليّ. المرجع اللغويّ للمستخدم في sadlang-docs؛ هذه الصفحة للمساهم في التنفيذ والمنهجيّة. (نصّ RFC نفسه موجود على فرع rfc/expand-crypto-library في sadlang-rfcs — لم يُدمَج إلى main بعد.)

المراحل

المرحلةالدوالالمعيارPR
٠عشوائي_آمنCSPRNG النظاممدموج
١بلايك3 / هاش_مفتاحBLAKE3 (keyed mode)مدموج
٢اشتق_مفتاح_مرور / اشتق_مفتاحPBKDF2-HMAC-SHA256 (RFC 2898/8018) / HKDF-SHA256 (RFC 5869)#217
أرجون2Argon2id (RFC 9106) — مؤجَّلة أصلًا، أُدرِجت بطلب صريح#219
٣شفر_موثق / فك_تشفير_موثقChaCha20-Poly1305 AEAD (RFC 8439)#220
٤ولّد_مفتاح_خاص_x25519 وعائلتها (7 دوال)X25519 (RFC 7748) + Ed25519 (RFC 8032) + SHA-512 ذاتيّ#221

كل الدوال ذاتيّة التنفيذ (self-rolled، بلا OpenSSL/libsodium) حفاظًا على قابليّة العمل على هدف الوضع الحرّ (نفس قيد [[crypto-unification|توحيد هاش/شفّر]])، وسطح اللغة لكل دالّة سلسلة تدخل ⇒ سلسلة تخرج حصرًا على كلا المحرِّكين — لا كائنات مركَّبة عابرة للحدود.

المنهجيّة: كل مرحلة عبر worktree معزول

flowchart LR
  RFC["sadlang-rfcs: نصّ المرحلة"] --> WT["worktree معزول<br/>wt-crypto-phaseN"]
  WT --> REF["مرجع C مستقلّ (scratchpad)<br/>يُختبَر مقابل شعاعات RFC الرسميّة"]
  REF -->|"تطابق تامّ"| IMPL["نقل الكود لثلاث نسخ إنتاج:<br/>مفسّر C++ / وقت تشغيل C / رابط أندرويد C"]
  IMPL --> AMELIA["مراجعة أميليا مستقلّة<br/>(متزامنة لا خلفيّة)"]
  AMELIA -->|"إجازة"| PR["PR ضدّ dev<br/>«لا دمج بلا إذن صريح»"]
  AMELIA -->|"عِلَّة"| WT

كل مرحلة استُنسِخت بنفس القالب: (1) مرجع C مستقلّ في scratchpad يُختبَر بايتًا بايت مقابل شعاعات الاختبار الرسميّة للمعيار (لا بيانات اختبار مؤلَّفة يدويًّا)؛ (2) بعد التطابق التامّ فقط، نقل نفس المنطق إلى ثلاث نسخ إنتاج متطابقة — المفسّر (interpreter/src/builtins/builtin_module_crypto.cpp)، وقت تشغيل المترجم (tools/compiler/runtime/sad_embedded_runtime.c)، ورابط أندرويد (tools/compiler/compiler_driver_android_linker.cpp — نسخة ثالثة منفصلة لأنّ هذا الهدف لا يشارك رابط سطح المكتب؛ يصطدم عادةً بحدّ سلاسل MSVC الخام، انظر أدناه)؛ (3) مراجعة أميليا مستقلّة متزامنة إلزاميّة قبل أيّ git push (لا خلفيّة — راجع فخّ التوقّف الموثَّق في الذاكرة الداخليّة)؛ (4) PR ضدّ dev بلا دمج تلقائيّ، وسطر «لا دمج بلا إذن صريح» حرفيًّا في كل PR.

الدرس الأهمّ: تحيّز اختيار شعاعات الاختبار (Argon2id)

أثناء Argon2id، طابق المرجع المستقلّ libargon2 الرسميّة (عبر argon2-cffi) تطابقًا تامًّا عبر 12 حالة اختبار اخترتُها بنفسي. رغم ذلك كشفت مراجعة أميليا المستقلّة عِلَّة حقيقيّة: التجزئة الأوّليّة H0 (RFC 9106 §3.2) كانت تُطعَم بقيمة تكلفة الذاكرة المقرَّبة داخليًّا لمضاعِف 4 (m') بدل القيمة الخام — والسبب أنّ كل الحالات الاثنتَي عشرة صدفةً استعملت تكلفة ذاكرة مضاعِفة لـ4 (8/1024/2048/4096)، فكان m'==m دومًا وأخفى العلّة رغم “التطابق التامّ” المُعلَن.

الدرس: اختيار حالات الاختبار بنفسك — حتى مع تنويع البارامترات — قد يحمل تحيّزًا غير واعٍ ينبع من نفس سوء الفهم الذي قد يكون وقع فيه التنفيذ نفسه. “طابق تنفيذًا مرجعيًّا مستقلًّا” ليس كافيًا وحده لكود تشفيريّ حسّاس — لازم أيضًا مراجعة تقرأ الخوارزميّة سطرًا سطرًا مقابل نصّ المعيار مباشرة، لا تكتفِ بالثقة بنتائج الاختبار. لخوارزميّة فيها تقريب/تقليم داخليّ، اختبر عمدًا حالة لا تقع على حدود التقريب.

فخّ الدمج المتسلسل: دالّة مشتركة الجسم مختلفة الاسم

تفرّعت PR #219 (Argon2id) وPR #220 (AEAD) وPR #221 (X25519/Ed25519) من نفس القاعدة (قبل #219) وتمسّ نفس الملفّات المشتركة (SIR opcode enum، مولّدات LLVM، المفسّر، وقت تشغيل C، رابط أندرويد، YAML SoT). دمج كلّ فرع لاحق كشف جولة تعارض جديدة، والقاعدة المتّبعة دومًا: أبقِ إضافات الجانبين معًا، لا تُسقِط أيًّا منهما. الفخّ: كل فرع أضاف دالّة توليد عشوائيّة مساعدة باسم مختلف (sadx_random_bytes مقابل sad_crypto_random_bytes) لكن جسمها شبه متطابق — والنصّ “المشترك” الظاهر بعد علامة >>>>>>> في تعارض git ثلاثيّ ينتمي في الحقيقة لدالّة واحدة فقط. محاولة حلّ هذا بتحرير واحد ضخم يفترض إعادة استخدام هذا النصّ لكلتا الدالّتين تنتج فسادًا صامتًا: دالّة مكرَّرة، تعليق مبتور، جسم دالّة ناقص — دون أيّ خطأ بناء فوريّ يكشفه.

الاكتشاف: فحص توازن الأقواس (python -c "text.count('{')==text.count('}')") ثمّ إعادة الملفّ لحالته المتعارضة (git checkout --conflict=merge -- <path>) والبدء من جديد بتحريرات صغيرة متسلسلة على حدود كل علامة تعارض على حدة، مع كتابة جسم كل دالّة كاملًا صراحة (تكرار المنطق بدل محاولة مشاركته نصّيًّا). طُبِّق هذا الدرس بنجاح من المحاولة الأولى على الملفّ التالي المصاب بنفس النمط.

فخّ ما بعد الدمج: حدّ MSVC C2026 لسلاسل C الخام

رابط أندرويد يُصدِر runtime التشفير كسلسلة C++ خام (R"( ... )") مقسَّمة استباقيًّا لتفادي حدّ MSVC (٦٥٥٣٥ بايت لكل حرفيّ سلسلة). كل فرع (PR #220 وPR #221) قسَّم محتواه الخاصّ تحت الحدّ بمعزل عن الآخر — لكن دمج X25519/Ed25519 مع AEAD في نفس القطعة بعد حلّ التعارض أعاد تجاوز الحدّ، وهذا لا يكشفه أيّ حارس ساكن أو مولِّد — يظهر فقط كخطأ بناء C2026: string too big وقت الترجمة الفعليّة على MSVC.

القاعدة العامّة: أيّ دمج لسلاسل C/C++ خام مقسَّمة مسبقًا بسبب حدّ حجم يحتاج تحقّقًا من الحدّ من جديد بعد الدمج — لا تفترض بقاء التقسيم الأصليّ كافيًا لمجرّد أنّ كل فرع وحده كان تحته. الحلّ: إغلاق )"; بعد حدّ دالّة نظيف وفتح R"( جديدة، فيتحوّل تقسيم كل فرع (قطعتان) إلى ثلاث/أربع قطع بعد الدمج.

قرار تصميميّ: X25519/Ed25519 بدوال مقسَّمة لا كائن

نصّ RFC اقترح دالّة توليد زوج مفاتيح تُرجع كائنًا بحقلين (عامّ وخاصّ). التصميم الفعليّ استعمل دالّتين منفصلتين بدلًا من ذلك (ولّد_مفتاح_خاص_x25519 + اشتق_مفتاح_عام_x25519) — تجنّبًا لبناء آليّة تمرير كائنات مركَّبة جديدة عابرة للمحرِّكين حين يكفي عقد “سلسلة ⇒ سلسلة” الموجود أصلًا لكل دالّة أخرى في المكتبة. لا خسارة أمنيّة: المفتاح العامّ دومًا دالّة حتميّة للخاصّ (تقييد سلميّ + ضرب بالنقطة الأساس)، فالفصل بلا أثر جانبيّ.

قرار تصميميّ: تباعُد فشل مقصود بين AEAD ونظيراتها

فك_تشفير_موثق (AEAD) يفشل مُغلَقًا (fail-closed) دومًا على فشل مصادقة — بخلاف فك_تشفير (وحدة تأكيدات، تشفير بلا مصادقة) الذي يُرجع مدخله كما هو صامتًا على خطأ تنسيق. لكن آليّة الإبلاغ عن هذا الفشل المُغلَق تتباعد بين المحرِّكين، بنفس السبب الجذريّ الموثَّق سلفًا: المفسّر يرمي استثناءً قابلًا للالتقاط بـحاول/امسك؛ المترجم يطبع رسالة عامّة على stderr (لا تُسرّب الوسم/المفتاح) ثمّ exit(1)، لأنّه لا يوجد اليوم مسار استثناء C-callable من وقت تشغيل المترجم إلى LLVM. بناء هذا المسار العامّ عمل لاحق موثَّق، خارج نطاق هذه الحملة — لكن الفشل نفسه مُغلَق على الحالتين، فلا يُرجع أيّ محرّك نصًّا مُحرَّفًا صامتًا أبدًا (أأمن من تباعُد فك_تشفير غير الموثَّق فيه فشل مفتوح).

الاختبار

كل مرحلة أضافت ملفّات إلى tests/behavior/sections/09_المكتبة_القياسية/04_تشفير/ (14 ملفًّا إجمالًا)، كلّ واحد يُشغَّل عبر sad-run.exe وsadc.exe ويُقارَن الناتج حرفيًّا — شعاعات RFC/مسابقة رسميّة لكل معيار (BLAKE3-team، RFC 2898/5869/7748/8032/8439/ 9106)، إضافة إلى حالات رفض صريحة (سرّ مشترك كلّه أصفار في X25519، توقيع/مفتاح مُعبَث بهما في Ed25519، بارامترات خارج الحدود الآمنة في KDFs).


اقرأ بعده: توحيد هاش/شفّر/فك_تشفير · دوال مضمنة ووحدات · نظام معالجة الأخطاء.