> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# التشفير في البلوكتشين: الهاش والمنحنيات والتوقيعات
- URL: https://arabroot.io/articles/التشفير-في-البلوكتشين-الهاش-والمنحنيات-والتوقيعات/
- Published: 2026-10-28T06:03:35.000Z
- Updated: 2026-10-28T07:45:59.000Z
- Description: كيف تصنع المفاتيح والعناوين والتوقيعات في Bitcoin وEthereum، ولماذا يكشف رقم مكرر واحد في التوقيع المفتاح الخاص.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, البلوكتشين والعملات الرقمية, الاستضافة الذاتية

إذا كتبت يوماً كوداً يولد عنوان استلام على شبكة Ethereum ويتحقق من وصول الدفعات، أو وصلك طلب دمج Pull Request فيه مثل هذا الكود لتراجعه، فقد يبدو لك كل شيء سليماً: الكود مرتب، والاختبارات تمر. ثم تجرب العنوان الذي يولده لأحد مفاتيح الاختبار المعروفة فتجده لا يطابق العنوان الذي تعرضه أدوات Ethereum نفسها. والمشكلة ليست في سطر غريب، وإنما في كلمة واحدة: الكود يستخدم `hashlib.sha3_256` لأن كاتبه ظن أنها نفس دالة Keccak التي تستخدمها الشبكة. وهذا النوع من الأخطاء لا يكتشفه إلا من يعرف ما الذي يحدث فعلاً تحت الواجهة، لذلك سوف نراجع في هذا المقال كوداً من هذا النوع سؤالاً بعد سؤال، ونفتح مع كل سؤال الأداة التشفيرية التي يقوم عليها.

وفي [مقال البلوكتشين للمبتدئين](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D9%85%D8%AA%D9%89-%D9%84%D8%A7-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC%D9%87/#building-blocks) مررنا على اللبنات بسرعة: الهاش وسلسلة الكتل وشجرة Merkle وتوقيع ECDSA بأداة OpenSSL، وفي [مقال التشفير للمطورين](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B4%D9%81%D9%8A%D8%B1-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D9%87%D8%A7%D8%B4-%D9%88%D9%83%D9%84%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B1%D9%88%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD/) شرحنا الهاش والمفاتيح بشكل عام. أما هنا فسوف نفتح الصندوق: نبني منحنى secp256k1 من معاملاته المنشورة بكود Python قصير، ونشتق منه عنوان Ethereum وعنوان Bitcoin ونطابقهما مع أدوات الشبكتين، ونرى لماذا تكرار رقم واحد في التوقيع يكشف المفتاح الخاص، ونتحقق من توقيعات Schnorr بمتجهات الاختبار الرسمية، ونبني إثبات Merkle لمعاملة في كتلة حقيقية. والمقال تعليمي تقني، ولا يتضمن أي نصيحة استثمارية، وكل المفاتيح فيه مفاتيح اختبار لا تصلح لأي مال حقيقي.

وسوف نناقش في هذا المقال ما يلي:

- خريطة أدوات التشفير في Bitcoin وEthereum، وما الذي يحدث إذا ضعفت كل واحدة منها.
- دوال الهاش في الشبكتين: SHA-256 مرتين وRIPEMD-160 وKeccak-256، ولماذا Keccak-256 ليست SHA3-256.
- المنحنى الإهليلجي secp256k1 من الداخل: النقطة G، وجمع النقاط وضربها، ولماذا لا يمكن الرجوع من المفتاح العام إلى الخاص.
- من المفتاح الخاص إلى العنوان في الشبكتين، بتجربة نطابق فيها حساباتنا مع cast وBitcoin Core.
- توقيع ECDSA بالمعادلات، وتجربة تكرار الرقم k التي تكشف المفتاح الخاص، والحل في RFC 6979، وحادثة Android سنة 2013، ثم قابلية التطويع Malleability.
- توقيعات Schnorr وTaproot، وتوقيعات BLS في إجماع Ethereum.
- إثبات Merkle لمعاملة في كتلة حقيقية، ومشكلة ترتيب البايتات التي ظهرت لنا، وعيب التكرار CVE-2012-2459.
- الحاسوب الكمومي: ما الذي يتأثر فعلاً، ومعايير NIST الجديدة، ثم قائمة مراجعة لأي كود يتعامل مع المفاتيح.

## خريطة أدوات التشفير في الشبكتين

أول سؤال في المراجعة بسيط: ما الأدوات التشفيرية التي يحتاجها هذا الكود أصلاً؟ والإجابة أن كل البلوكتشين العام يقوم على ثلاث عائلات فقط: دوال الهاش Hash Functions التي تربط الكتل وتلخص المعاملات وتشتق العناوين، والتوقيع الرقمي Digital Signature على المنحنيات الإهليلجية Elliptic Curves الذي يثبت أن صاحب المفتاح هو من أرسل المعاملة، وطرق الترميز Encoding التي تحول البايتات إلى نص يكتبه الإنسان، وقد شرحنا الترميز بالتفصيل في [مقال الترميز](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/). ولاحظ ما لا يوجد في القائمة: التشفير بمعنى إخفاء البيانات Encryption، فالبيانات على السلسلة العامة علنية، والتشفير هنا يثبت ولا يخفي.

والصورة التالية تبين كل وظيفة، وما تستخدمه كل شبكة لها، وماذا يحدث إذا ضعفت، وهذا العمود الأخير هو الذي يجعلك تفهم لماذا يهتم مصممو هذه الشبكات بتفاصيل تبدو صغيرة:

![جدول من خمسة صفوف: ربط الكتل بـ SHA-256 مرتين في Bitcoin وKeccak-256 في Ethereum، وتلخيص المعاملات بشجرة Merkle في Bitcoin وMerkle Patricia Trie في Ethereum، والتوقيع بـ ECDSA ثم Schnorr منذ Taproot في Bitcoin وECDSA للحسابات وBLS للمدققين في Ethereum، ومن المفتاح إلى العنوان بـ Hash160 ثم Bech32 أو Base58Check في Bitcoin وآخر 20 بايت من Keccak-256 مع EIP-55 في Ethereum، والعشوائية بمولد آمن وRFC 6979، مع عمود يبين ماذا يحدث لو ضعفت كل أداة](https://arabroot.io/content/images/2026/10/blockchain-cryptography-01-primitives.webp)

ثلاث عائلات فقط: الهاش والتوقيع والترميز، ولكل شبكة اختياراتها داخل كل عائلة

ونقطة البداية لكل التجارب في هذا المقال ملف Python واحد يعرف المنحنى، ثم نبني عليه ملفاً بعد آخر. وكل التجارب تعمل في Container مؤقت من `python:3.13.16-slim`، ونثبت فيه مكتبة `pycryptodome` بإصدار محدد لأننا نحتاج منها دالتي Keccak وRIPEMD-160 فقط، أما كل حسابات المنحنى والتوقيع فنكتبها بأيدينا حتى نراها. والكود مكتوب للتعليم، وأي نظام حقيقي يستخدم مكتبة مراجعة مثل libsecp256k1، والسبب أن الكود البسيط يأخذ وقتاً مختلفاً بحسب المفتاح، وهذا يسرب معلومات لمن يقيس الوقت Timing Attack.

## دوال الهاش في الشبكتين

### ثلاث خصائص، ولكل استخدام خاصيته

قبل أن ندخل في الدوال نفسها، يجب أن نفصل بين ثلاث خصائص يخلط بينها كثير من المطورين، لأن كل جزء في البلوكتشين يعتمد على خاصية مختلفة، وضعف واحدة منها لا يعني بالضرورة ضعف الأخرى:

- **مقاومة الرجوع إلى المدخل Preimage Resistance**: إذا أعطيتك هاشاً فلا تستطيع أن تجد أي مدخل يعطيه. وعلى هذه الخاصية تعتمد العناوين، فالعنوان هاش للمفتاح العام، ولا يجب أن يستطيع أحد أن يجد مفتاحاً عاماً آخر لنفس العنوان.
- **مقاومة المدخل الثاني Second Preimage Resistance**: إذا أعطيتك مدخلاً وهاشه فلا تستطيع أن تجد مدخلاً آخر بنفس الهاش. وعلى هذه الخاصية يعتمد ربط الكتل، فلا يجب أن يستطيع أحد أن يستبدل كتلة قديمة بأخرى لها نفس الهاش.
- **مقاومة التصادم Collision Resistance**: لا تستطيع أن تجد أي مدخلين مختلفين بنفس الهاش، حتى لو اخترتهما أنت بحرية. وهذه أصعب الخصائص الثلاث، وعليها تعتمد شجرة Merkle والتوقيع، لأن من يجد رسالتين بنفس الهاش يستطيع أن يأخذ توقيعاً على واحدة ويستخدمه للأخرى.

والمثال الواقعي على سقوط الخاصية الثالثة هو SHA-1، فقد أعلن مشروع [SHAttered](https://shattered.io/?ref=arabroot.io) سنة 2017 أول تصادم عملي لها بملفي PDF مختلفين لهما نفس الهاش. وقد استخدم طابع Hashcash الذي جربناه في مقال التاريخ دالة SHA-1، ولم يتأثر بهذا التصادم، لأن الطابع يعتمد على صعوبة إيجاد هاش يبدأ بأصفار وليس على مقاومة التصادم، وهذا بالضبط ما نعنيه بأن لكل استخدام خاصيته. أما الشبكات الحديثة فاختارت SHA-256 وKeccak-256 اللتين لا يعرف لهما تصادم حتى اليوم.

### SHA-256 مرتين في Bitcoin

Bitcoin يحسب هاش رأس الكتلة ومعرف المعاملة txid بتطبيق SHA-256 على البيانات، ثم SHA-256 مرة ثانية على الناتج، ويسمى هذا Double SHA-256 أو SHA256d. والسبب المعروف لهذا الاختيار حماية من هجوم تمديد الطول Length Extension الذي تتعرض له دوال عائلة SHA-2 عندما تستخدم بطريقة معينة، والتطبيق مرتين يغلق هذا الباب. وقد حسبنا هاش كتلة التكوين بهذه الطريقة في مقال تاريخ العملات الرقمية، وسوف نستخدمها بعد قليل لنبني إثبات Merkle.

وللعناوين يستخدم Bitcoin تركيبة أخرى اسمها Hash160: يحسب SHA-256 للمفتاح العام، ثم RIPEMD-160 للناتج، فيخرج هاش بطول 20 بايت. والسبب أن العنوان يجب أن يكون قصيراً بما يكفي لينسخه الناس، وأن يبقى المفتاح العام نفسه مخفياً حتى أول مرة يصرف فيها صاحبه من العنوان، وسوف نرى أهمية هذه النقطة الأخيرة في قسم الحاسوب الكمومي.

### Keccak-256 في Ethereum، ولماذا ليست SHA3-256

Ethereum يستخدم دالة واحدة تقريباً في كل مكان، وهي التي يسميها [الورقة الصفراء Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf?ref=arabroot.io) باسم KEC، أي Keccak-256: لهاش الكتل والمعاملات، ولاشتقاق العناوين، ولحساب معرف الدالة Function Selector في العقود الذكية، ولمواقع التخزين داخل العقود. والقصة هنا هي القصة التي بدأنا بها: Keccak هي الخوارزمية التي فازت في مسابقة NIST لاختيار SHA-3، ولكن NIST عدلت طريقة الحشو Padding قبل أن تنشر المعيار النهائي [FIPS 202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf?ref=arabroot.io) في أغسطس 2015، فأضافت بتات لفصل المجال Domain Separation في نهاية الرسالة. وEthereum كان قد اعتمد Keccak بشكلها الأصلي قبل التعديل، فبقيت الشبكة على النسخة القديمة.

والنتيجة أن الدالتين لهما نفس البنية الداخلية ونفس الطول، ومع ذلك تعطيان مخرجين مختلفين تماماً لنفس المدخل. والملف التالي هو الذي نستخدمه في المراجعة لاكتشاف الخطأ، وهو يبدأ بتعريف المنحنى الذي سوف نحتاجه في الأقسام التالية أيضاً. احفظ الملف الأول باسم `ec.py`:

```python
# secp256k1 from its published parameters (SEC 2, section 2.4.1). Pure Python, for learning only.
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F   # field prime
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141   # number of points (group order)
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
     0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)  # generator point
# the curve is y^2 = x^3 + 7 (mod p)

def add(P, Q):
    if P is None: return Q
    if Q is None: return P
    if P[0] == Q[0] and (P[1] + Q[1]) % p == 0: return None          # P + (-P) = point at infinity
    if P == Q: lam = 3 * P[0] * P[0] * pow(2 * P[1], -1, p) % p       # tangent (doubling)
    else:      lam = (Q[1] - P[1]) * pow(Q[0] - P[0], -1, p) % p      # chord
    x = (lam * lam - P[0] - Q[0]) % p
    return (x, (lam * (P[0] - x) - P[1]) % p)

def mul(k, P=G):                                                      # double-and-add
    R = None
    while k:
        if k & 1: R = add(R, P)
        P = add(P, P); k >>= 1
    return R

def on_curve(P):
    return (P[1] * P[1] - P[0] ** 3 - 7) % p == 0
```

ثم الملف الثاني باسم `keys.py`، وسوف نشرح النصف الأول منه في القسم التالي، أما السطران الأخيران فيحسبان هاش النص الفارغ بالدالتين:

```python
import hashlib
from Crypto.Hash import keccak, RIPEMD160
from ec import G, mul, on_curve, n

def keccak256(b):
    k = keccak.new(digest_bits=256); k.update(b); return k.digest()

print("G on curve        :", on_curve(G))
print("n*G               :", mul(n))                       # back to the point at infinity

priv = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80   # anvil test key 0, public knowledge
P = mul(priv)
print("public key x      :", hex(P[0]))
print("public key y      :", hex(P[1]))
pub64 = P[0].to_bytes(32, "big") + P[1].to_bytes(32, "big")
print("ethereum address  : 0x" + keccak256(pub64)[-20:].hex())

compressed = bytes([2 + (P[1] & 1)]) + P[0].to_bytes(32, "big")
h160 = RIPEMD160.new(hashlib.sha256(compressed).digest()).hexdigest()
print("compressed pubkey :", compressed.hex())
print("bitcoin hash160   :", h160)

print("keccak256('')     :", keccak256(b"").hex())
print("sha3_256('')      :", hashlib.sha3_256(b"").hexdigest())
```

```bash
docker run --rm --name lab-crypto-py -e PIP_DISABLE_PIP_VERSION_CHECK=1 -v "$PWD:/lab" -w /lab python:3.13.16-slim \
  bash -c "pip install -q --root-user-action=ignore pycryptodome==3.23.0 && python keys.py"
```

والمخرج سوف يكون كما يلي:

```text
G on curve        : True
n*G               : None
public key x      : 0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75
public key y      : 0x3547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5
ethereum address  : 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
compressed pubkey : 038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75
bitcoin hash160   : a55476015c13afb8afb92160329a8cde976f1f2e
keccak256('')     : c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
sha3_256('')      : a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
```

ونتأكد من قيمة Keccak بأداة Ethereum نفسها، أي `cast` من مجموعة Foundry، والتي يمكنك أن تشغلها دون أي عقدة:

```bash
docker run --rm --name lab-crypto-cast ghcr.io/foundry-rs/foundry:v1.8.6 "cast keccak ''"
```

والمخرج سوف يكون كما يلي:

```text
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
```

الهاش الذي حسبته مكتبة Keccak يطابق ما تحسبه أداة Ethereum، أما `hashlib.sha3_256` فأعطت قيمة أخرى تماماً، والسبب كما ذكرنا أن المكتبة القياسية في Python تطبق المعيار FIPS 202 وليس Keccak الأصلية. والصورة التالية تضع القيمتين جنباً إلى جنب:

![مقارنة من بطاقتين: Keccak-256 الأصلية التي يستخدمها Ethereum ويبدأ حشوها بالقيمة 0x01 وهاش النص الفارغ بها c5d2460186f7233c، وSHA3-256 المعيارية من FIPS 202 في أغسطس 2015 التي أضافت بتات فصل المجال فأصبح الحشو 0x06 وهاش النص الفارغ بها a7ffc6f8bf1ed766، وتحتهما تنبيه بالخطأ الشائع: استخدام hashlib.sha3_256 لحساب عنوان Ethereum يعطي قيماً لا تطابق السلسلة](https://arabroot.io/content/images/2026/10/blockchain-cryptography-03-keccak-sha3.webp)

اسمان متشابهان ومخرجان مختلفان، وهاش النص الفارغ هو أسرع اختبار لأي مكتبة

⚠️

قبل أن تبني أي شيء على مكتبة تشفير، احسب بها قيمة معروفة ومنشورة، مثل هاش النص الفارغ أو عنوان مفتاح اختبار معروف، وقارنها بالقيمة الرسمية. هذا الاختبار يأخذ دقيقة واحدة، وكان سيمنع الخطأ الذي بدأنا به المقال قبل أن يصل إلى المراجعة.

## المنحنى secp256k1 من الداخل

السؤال الثاني في المراجعة: من أين يأتي المفتاح العام؟ والكود الذي نراجعه يستخدم دالة جاهزة اسمها `privateToPublic`، وهذا صحيح، ولكن فهم ما تفعله هو الذي يجعلك تعرف ما الذي يجب أن تحميه وما الذي يمكنك أن تنشره. والمنحنى الذي تستخدمه الشبكتان اسمه secp256k1، وقد نشرت معاملاته مجموعة SECG في وثيقة [SEC 2](https://www.secg.org/sec2-v2.pdf?ref=arabroot.io)، ومعادلته بسيطة جداً: y² = x³ + 7، ولكن الحساب فيها لا يتم على الأعداد العادية، وإنما باقي القسمة على عدد أولي p كبير جداً، وهو أول قيمة في ملف `ec.py`.

وعلى هذا المنحنى عملية واحدة مهمة: جمع نقطتين. فإذا رسمت خطاً يمر بنقطتين على المنحنى فإنه يقطعه في نقطة ثالثة، وانعكاسها هو ناتج الجمع، وإذا جمعت النقطة مع نفسها تستخدم المماس بدلاً من الخط، وهذا ما تفعله الدالة `add` بالمعادلات نفسها في السطرين اللذين فيهما `lam`. ومن الجمع يأتي الضرب: ضرب النقطة في رقم k يعني أن تجمعها مع نفسها k مرة، والدالة `mul` تفعل ذلك بطريقة المضاعفة والجمع Double-and-Add، فتحتاج قرابة 256 مضاعفة بدلاً من أن تجمع النقطة مع نفسها عدداً خيالياً من المرات.

والآن نعرف المفتاحين: المفتاح الخاص Private Key رقم عشوائي من 256 بت أصغر من n، والمفتاح العام Public Key هو النقطة الناتجة من ضرب نقطة البداية G في هذا الرقم. والضرب سريع، فقد حسبه Python في جزء من الثانية، أما الرجوع من النقطة الناتجة إلى الرقم فهو ما يسمى مشكلة اللوغاريتم المتقطع على المنحنيات الإهليلجية ECDLP، ولا توجد طريقة معروفة لحلها على حاسوب عادي في وقت معقول مع أرقام بهذا الحجم. وهذا هو الأساس الذي يقوم عليه كل شيء: تستطيع أن تنشر المفتاح العام دون أن تخاف على الخاص.

في المخرج السابق لاحظ التالي:

- السطر `G on curve : True` يتحقق أن نقطة البداية المنشورة تحقق المعادلة فعلاً، وهذه أول خطوة تفعلها أي مكتبة جادة مع أي نقطة تستقبلها من الخارج، لأن النقطة التي ليست على المنحنى قد تستخدم في هجمات.
- السطر `n*G : None` يعني أن ضرب G في n يرجعها إلى نقطة اللانهاية، أي العنصر المحايد، وهذا ما يجعل n هو عدد النقاط التي تولدها G، ولهذا يجب أن يكون المفتاح الخاص بين 1 وn ناقص واحد.
- المفتاح الخاص الذي استخدمناه هو أول مفتاح اختبار في Anvil، وهو منشور في كل مكان، فأي مال يرسل إلى عنوانه على شبكة حقيقية يسرق في ثوان.

### كم مفتاحاً خاصاً يوجد، ولماذا لا يخمنه أحد؟

عدد المفاتيح الممكنة هو n ناقص واحد، أي قرابة 1.158 ضرب 10 أس 77، وهو رقم يصعب تخيله، لذلك نحسبه بطريقة أخرى: لو كان عند المهاجم جهاز يجرب مليار مليار مفتاح في الثانية، أي 10 أس 18، فإنه يحتاج في المتوسط إلى تجربة نصف المفاتيح ليجد مفتاحاً معيناً، وهذا يأخذ قرابة 1.83 ضرب 10 أس 51 سنة بحساب بسيط في Python، وعمر الكون أقل من 14 مليار سنة. ولهذا السبب لا يحاول أحد أن يخمن المفاتيح الخاصة بالتجربة العشوائية.

ولكن هذا الحساب يفترض شيئاً واحداً: أن المفتاح اختير عشوائياً فعلاً من كل هذه المساحة. فإذا ولد الكود المفتاح من مولد أرقام ضعيف، أو من كلمة مرور يحفظها المستخدم، أو من الوقت الحالي، أو من دالة `random` في Python التي صممت للمحاكاة وليس للتشفير، فإن المساحة الحقيقية تصبح أصغر بكثير، وقد تكون بحجم ملايين قليلة يجربها المهاجم في دقائق كما جربنا أرقام الجوال في مقال الاختبار من خمسة أسئلة. لذلك أول سطر تبحث عنه في مراجعة أي كود يولد مفاتيح هو مصدر العشوائية، ويجب أن يكون مولداً آمناً للتشفير مثل وحدة `secrets` في Python أو مولد نظام التشغيل، وهذا ما استخدمناه في كل تجارب هذا المقال. وأما كيف تولد المحافظ مفاتيح كثيرة من عبارة واحدة يحفظها المستخدم فهو موضوع مقال المحافظ والمفاتيح.

## من المفتاح الخاص إلى العنوان

المفتاح العام نقطة لها إحداثيان، كل منهما 32 بايت، والشبكتان تحولانه إلى عنوان أقصر بطريقتين مختلفتين، وهنا مكان الخطأ الذي وجدناه في المراجعة.

في Ethereum كما في [توثيق الحسابات](https://ethereum.org/en/developers/docs/accounts/?ref=arabroot.io) نأخذ الإحداثيين متتاليين، أي 64 بايت دون أي بادئة، ونحسب Keccak-256 لهما، ثم نأخذ آخر 20 بايت. والعنوان الذي حسبناه `0xf39fd6e5…` نطابقه مع ما تحسبه أداة `cast` من نفس المفتاح:

```bash
docker run --rm --name lab-crypto-cast ghcr.io/foundry-rs/foundry:v1.8.6 \
  "cast wallet address --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
```

والمخرج سوف يكون كما يلي:

```text
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
```

نفس العنوان، ولكن لاحظ أن بعض الحروف كبيرة وبعضها صغير. وهذا ليس زينة، وإنما مجموع تحقق Checksum موصوف في EIP-55: يحسب Keccak-256 للعنوان نفسه بالحروف الصغيرة، وكل حرف في العنوان يصبح كبيراً إذا كان الرقم المقابل له في الهاش 8 أو أكثر. فإذا أخطأ المستخدم في حرف واحد عند النسخ، تكتشف المحفظة أن نمط الحروف الكبيرة لا يطابق، وهذه نفس فكرة مجموع التحقق في Base58Check التي شرحناها في مقال الترميز، ولكن دون تغيير طول العنوان.

أما في Bitcoin فالعناوين الحديثة من نوع P2WPKH تستخدم المفتاح العام المضغوط Compressed: بايت واحد يقول هل y زوجي أم فردي، القيمة 02 أو 03، ثم الإحداثي x فقط، أي 33 بايت بدلاً من 64، والسبب أن x وحده مع زوجية y يكفيان لحساب y من معادلة المنحنى. ثم Hash160 كما ذكرنا، ثم ترميز Bech32\. ونطابق ما حسبناه مع Bitcoin Core نفسه في وضع regtest، فنطلب منه أن يشتق العنوان من المفتاح العام بدالة الواصف Descriptor، ثم نسأله عن محتوى العنوان:

```bash
docker run -d --name lab-crypto-btc bitcoin/bitcoin:31.1 bitcoind -regtest -printtoconsole=0
alias btc='docker exec --user bitcoin lab-crypto-btc bitcoin-cli -regtest'
btc getdescriptorinfo "wpkh(038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75)" | grep '"descriptor"'
btc deriveaddresses "wpkh(038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75)#56q0ky8x"
btc validateaddress bcrt1q5428vq2uzwhm3taey9sr9x5vm6tk78ew8pf2xw
```

والمخرج سوف يكون كما يلي:

```text
  "descriptor": "wpkh(038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75)#56q0ky8x",
[
  "bcrt1q5428vq2uzwhm3taey9sr9x5vm6tk78ew8pf2xw"
]
{
  "isvalid": true,
  "address": "bcrt1q5428vq2uzwhm3taey9sr9x5vm6tk78ew8pf2xw",
  "scriptPubKey": "0014a55476015c13afb8afb92160329a8cde976f1f2e",
  "isscript": false,
  "iswitness": true,
  "witness_version": 0,
  "witness_program": "a55476015c13afb8afb92160329a8cde976f1f2e"
}
```

في الإعداد أعلاه لاحظ التالي:

- الحقل `witness_program` هو نفس قيمة `bitcoin hash160` التي حسبناها بأيدينا: `a55476015c13…`، أي أن العنوان هو هذا الهاش بعد ترميزه بـ Bech32.
- الحقل `scriptPubKey` يبدأ بـ `0014`، أي إصدار الشاهد Witness صفر ثم طول 20 بايت، ثم الهاش، وهذا هو القفل الذي سوف يوضع على أي مبلغ يرسل إلى هذا العنوان، وسوف نشرحه في مقال كيف يعمل Bitcoin.
- الأمر `deriveaddresses` يرفض الواصف دون مجموع التحقق `#56q0ky8x` الذي أعطاه `getdescriptorinfo`، ويرجع الخطأ `Missing checksum`، وهذا سلوك مقصود في Bitcoin Core حتى لا يشتق عناوين من واصف فيه خطأ إملائي.
- نفس المفتاح الخاص أنتج عنواناً في Ethereum وعنواناً في Bitcoin، وهذا يعني أن تسرب مفتاح واحد يكشف ما في الشبكتين معاً إذا استخدمته فيهما.

والصورة التالية تجمع الطريق كله بالقيم الحقيقية:

![مسار من اليمين إلى اليسار: المفتاح الخاص ac0974bec39a17e3 رقم عشوائي من 256 بت، ثم ضرب النقطة k في G فينتج المفتاح العام بإحداثيين x يبدأ بـ 8318535b وy يبدأ بـ 3547f11c على المنحنى y تربيع يساوي x تكعيب زائد 7، ثم هاش باتجاه واحد إلى بطاقتين: Ethereum حيث Keccak-256 للإحداثيين وآخر 20 بايت تعطي 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266، وBitcoin حيث المفتاح المضغوط ثم SHA-256 ثم RIPEMD-160 تعطي hash160 a55476015c13 ثم Bech32 تعطي bcrt1q5428vq2uzwhm3taey9sr9x5vm6tk78ew8pf2xw](https://arabroot.io/content/images/2026/10/blockchain-cryptography-02-key-to-address.webp)

كل سهم في هذا المسار باتجاه واحد، لذلك تنشر العنوان والمفتاح العام دون أن تكشف المفتاح الخاص

## توقيع ECDSA من الداخل

السؤال الثالث في المراجعة هو الأخطر: كيف يولد الكود الرقم العشوائي عند التوقيع؟ وحتى تفهم لماذا هذا السؤال مهم، يجب أن ترى التوقيع بمعادلاته. ففي ECDSA يكون عندك المفتاح الخاص d، وهاش الرسالة كرقم نسميه z، ولكل توقيع تختار رقماً سرياً جديداً k يسمى Nonce، ثم تحسب النقطة k·G وتأخذ إحداثيها x فيكون هو r، ثم تحسب s = k⁻¹ (z + r·d) mod n، والتوقيع هو الزوج (r, s). والمتحقق الذي يملك المفتاح العام فقط يحسب نقطة من z وr وs والمفتاح العام، ويتأكد أن إحداثيها x يساوي r. والمعادلة الثانية فيها مجهولان بالنسبة لمن يراقب، هما k وd، ولهذا السبب لا يستطيع أحد أن يحسب d من توقيع واحد، بشرط واحد: أن يبقى k سراً، وألا يتكرر أبداً. ولاحظ أن المتحقق لا يحتاج k ولا d، وإنما يعيد بناء النقطة k·G من القيم العلنية، فإذا تطابق إحداثيها مع r فالتوقيع صنعه من يملك d، لأن أحداً غيره لا يستطيع أن يحسب s يحقق هذه المعادلة لهذه الرسالة بالذات.

### نجرب العطل: نفس الرقم k في توقيعين

الملف التالي يوقع رسالتين بنفس المفتاح ونفس k، كما يحدث عندما يكون مولد الأرقام العشوائية معطلاً أو ضعيفاً، ثم يتصرف كأي شخص يقرأ السلسلة: يأخذ التوقيعين العلنيين ويحسب منهما المفتاح الخاص. احفظه باسم `ecdsa_reuse.py`:

```python
# ECDSA by hand on secp256k1, then the classic mistake: the same nonce k for two messages.
import hashlib, secrets
from ec import G, n, mul, add

def z(msg):                                   # message hash as a number
    return int.from_bytes(hashlib.sha256(msg).digest(), "big")

def sign(d, msg, k):
    r = mul(k)[0] % n
    s = pow(k, -1, n) * (z(msg) + r * d) % n
    return r, s

def verify(Q, msg, r, s):
    w = pow(s, -1, n)
    X = add(mul(z(msg) * w % n), mul(r * w % n, Q))
    return X is not None and X[0] % n == r

d = secrets.randbelow(n - 1) + 1              # a fresh private key for this demo only
Q = mul(d)

k = secrets.randbelow(n - 1) + 1
m1, m2 = b"pay 1 BTC to shop A", b"pay 2 BTC to shop B"
r1, s1 = sign(d, m1, k)
r2, s2 = sign(d, m2, k)                       # BUG: same k again
print("signature 1 valid :", verify(Q, m1, r1, s1))
print("signature 2 valid :", verify(Q, m2, r2, s2))
print("same r in both    :", r1 == r2)

# Anyone who sees the two signatures can solve two equations for k, then for d.
k_found = (z(m1) - z(m2)) * pow(s1 - s2, -1, n) % n
d_found = (s1 * k_found - z(m1)) * pow(r1, -1, n) % n
print("recovered key     :", hex(d_found)[:20] + "...")
print("equals the secret :", d_found == d)

r3, s3 = sign(d, m2, secrets.randbelow(n - 1) + 1)   # what a correct wallet does: new k
print("fresh k, new r    :", r3 != r1)
```

```bash
docker run --rm --name lab-crypto-py -v "$PWD:/lab" -w /lab python:3.13.16-slim python ecdsa_reuse.py
```

والمخرج سوف يكون كما يلي، والمفتاح المستخرج يختلف في كل تشغيل لأن المفتاح نفسه عشوائي:

```text
signature 1 valid : True
signature 2 valid : True
same r in both    : True
recovered key     : 0xea9786b24b0753efe4...
equals the secret : True
fresh k, new r    : True
```

في الإعداد أعلاه لاحظ التالي:

- التوقيعان صحيحان تماماً، ولا يوجد أي خطأ تكتشفه الشبكة أو المحفظة، فالعقدة تقبل المعاملتين دون أي تحذير.
- السطر `same r in both : True` هو العلامة التي يبحث عنها المهاجم: r يعتمد على k فقط، فإذا تكرر r في توقيعين من نفس المفتاح فقد تكرر k، وأي شخص يستطيع أن يفحص السلسلة كلها بحثاً عن r مكرر.
- بطرح المعادلتين يختفي d، فيخرج k من قيم كلها علنية، ثم يخرج d من أي توقيع منهما، والسطر `equals the secret : True` يعني أن من يملك التوقيعين يملك الآن المحفظة كلها.
- السطر الأخير يبين السلوك الصحيح: k جديد لكل توقيع يعطي r جديداً، فلا توجد معادلتان بنفس المجهول.

والصورة التالية تلخص الخطوات الثلاث:

![ثلاث خطوات: توقيعان من نفس المحفظة بنفس k فيظهر نفس r، ثم طرح المعادلتين يعطي k يساوي فرق هاش الرسالتين مقسوماً على فرق s1 وs2، ثم المفتاح الخاص d يساوي s1 ضرب k ناقص z1 مقسوماً على r، وفي الأسفل الحل في RFC 6979 بحساب k من المفتاح وهاش الرسالة، وحادثة Android في أغسطس 2013 حين نبه bitcoin.org إلى ضعف في مولد الأرقام العشوائية جعل المحافظ معرضة للسرقة](https://arabroot.io/content/images/2026/10/blockchain-cryptography-04-nonce-reuse.webp)

توقيعان صحيحان بنفس k يكفيان لأي شخص ليحسب المفتاح الخاص

📌

في أغسطس 2013 نشر موقع bitcoin.org [تنبيهاً أمنياً](https://bitcoin.org/en/alert/2013-08-11-android?ref=arabroot.io) يقول إن مكوناً في Android مسؤولاً عن توليد الأرقام العشوائية الآمنة فيه نقاط ضعف خطيرة، وإن المحافظ التي أنشئت بأي تطبيق على Android معرضة للسرقة، وذكر منها Bitcoin Wallet وblockchain.info وBitcoinSpinner وMycelium، وطلب من المستخدمين توليد عناوين جديدة بمولد مصلح ونقل أموالهم إليها، وعدم إرسال أي مبلغ إلى العناوين القديمة. والدرس أن التشفير الصحيح رياضياً يسقط كله إذا ضعف مصدر العشوائية، والتوقيع في ECDSA من أكثر الأماكن حساسية لذلك كما رأينا في التجربة.

### الحل: k محسوب وليس عشوائياً، RFC 6979

الحل الذي تستخدمه المحافظ الحديثة لا يعتمد على مولد الأرقام العشوائية لحظة التوقيع أصلاً، وإنما يحسب k بطريقة محددة من المفتاح الخاص وهاش الرسالة، كما يصف [RFC 6979](https://www.rfc-editor.org/rfc/rfc6979?ref=arabroot.io) بعنوان التوقيع الحتمي Deterministic DSA وECDSA. والنتيجة أن k يبقى سرياً لأنه مشتق من المفتاح الخاص، ويختلف لكل رسالة لأنه مشتق من هاشها، ولكن نفس الرسالة بنفس المفتاح تعطي دائماً نفس التوقيع. ونرى ذلك بأداة `cast` التي توقع رسالة مرتين:

```bash
docker run --rm --name lab-crypto-cast ghcr.io/foundry-rs/foundry:v1.8.6 \
  "cast wallet sign --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 'warranty SN-1001'; \
   cast wallet sign --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 'warranty SN-1001'"
```

والمخرج سوف يكون كما يلي:

```text
0x41563f6de5ac05dc2cacdde5bda7e6184c6b67de4686fa10f3a9be8a938e6def49e08bff3cc8add410a4b51b7c8d97f460dd3cd42a8beaca1d4711b8cfd0753f1c
0x41563f6de5ac05dc2cacdde5bda7e6184c6b67de4686fa10f3a9be8a938e6def49e08bff3cc8add410a4b51b7c8d97f460dd3cd42a8beaca1d4711b8cfd0753f1c
```

التوقيعان متطابقان تماماً، وهذا دليل على أن الأداة لا تستخدم k عشوائياً. والتوقيع 65 بايت: أول 32 هي r، والتالية s، والبايت الأخير `1c` هو قيمة الاسترداد Recovery ID التي تسمح لأي شخص أن يستخرج المفتاح العام من التوقيع نفسه، ولهذا السبب لا ترسل معاملات Ethereum المفتاح العام معها. وفي المراجعة هذا هو التعديل الثاني الذي نطلبه: أن يستخدم الكود مكتبة تطبق RFC 6979، بدلاً من دالة توقيع كتبها كاتب الكود بنفسه وتأخذ k من `random.randint`، وهي دالة غير آمنة للتشفير أصلاً.

### قابلية التطويع Malleability: توقيعان صحيحان لنفس الرسالة

بقيت خاصية في ECDSA تفاجئ من يراها أول مرة: إذا كان التوقيع (r, s) صحيحاً، فالتوقيع (r, n − s) صحيح أيضاً لنفس الرسالة ونفس المفتاح، ويستطيع أي شخص يرى المعاملة أن يصنعه دون أن يعرف المفتاح الخاص. والملف التالي يجرب ذلك بنفس دوال المنحنى، احفظه باسم `malleable.py`:

```python
# ECDSA malleability: if (r, s) is valid, (r, n - s) is valid too, for the same message and key.
import hashlib, secrets
from ec import n, mul, add

z = lambda msg: int.from_bytes(hashlib.sha256(msg).digest(), "big")

def sign(d, msg):
    k = secrets.randbelow(n - 1) + 1
    r = mul(k)[0] % n
    return r, pow(k, -1, n) * (z(msg) + r * d) % n

def verify(Q, msg, r, s):
    w = pow(s, -1, n)
    X = add(mul(z(msg) * w % n), mul(r * w % n, Q))
    return X is not None and X[0] % n == r

d = secrets.randbelow(n - 1) + 1
Q = mul(d)
msg = b"pay 1 BTC to shop A"
r, s = sign(d, msg)
for label, sv in (("original (r, s)    ", s), ("flipped  (r, n - s)", n - s)):
    print(label, "valid:", verify(Q, msg, r, sv), "| low-S:", sv <= n // 2)
```

```bash
docker run --rm --name lab-crypto-py -v "$PWD:/lab" -w /lab python:3.13.16-slim python malleable.py
```

والمخرج سوف يكون كما يلي، وقيمة low-S في السطر الأول تختلف بين تشغيل وآخر لأن k عشوائي:

```text
original (r, s)     valid: True | low-S: True
flipped  (r, n - s) valid: True | low-S: False
```

التوقيعان صحيحان، ولكن بايتاتهما مختلفة، وبالتالي إذا كان معرف المعاملة يحسب من بايتات التوقيع، كما كان الحال في Bitcoin قبل الشاهد المنفصل SegWit، يستطيع أي عقدة في الطريق أن تغير معرف المعاملة دون أن تغير معناها، فيبحث المرسل عن معاملته بالمعرف القديم فلا يجدها. ويصف [BIP146](https://github.com/bitcoin/bips/blob/master/bip-0146.mediawiki?ref=arabroot.io) هذه المشكلة ويعالجها بقاعدة low-S، أي رفض أي توقيع قيمة s فيه أكبر من نصف n، فيبقى لكل توقيع شكل واحد مقبول، وفي Ethereum فعل [EIP-2](https://eips.ethereum.org/EIPS/eip-2?ref=arabroot.io) نفس الشيء، ويذكر أن السبب أن المهاجم يستطيع أن يجعل المعاملة التي تدخل الكتلة بمعرف يختلف عن المعرف الذي أرسله المستخدم، وهذا يربك الواجهات التي تتابع المعاملات بمعرفها. والدرس للمراجعة: إذا كان الكود يتابع الدفعات بمعرف المعاملة وحده، فيجب أن يتعامل مع احتمال تغيره، أو يتابع بالعنوان والمبلغ.

## Schnorr وTaproot، وBLS في Ethereum

ECDSA ليس التوقيع الوحيد. ففي Bitcoin فعلت ترقية Taproot عند الكتلة 709,632 كما يذكر [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki?ref=arabroot.io)، ومعها جاءت توقيعات Schnorr الموصوفة في [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki?ref=arabroot.io). ويذكر BIP340 أسباب التغيير: التوقيع بطول ثابت 64 بايت بدلاً من ترميز DER المتغير الذي يصل إلى 72 بايت، وخاصية الخطية Linearity التي تسمح لعدة أطراف أن ينتجوا توقيعاً واحداً صالحاً لمجموع مفاتيحهم العامة، والتحقق الدفعي Batch Verification الذي يسرع التحقق من توقيعات كثيرة معاً، وأن الخوارزمية محددة بالكامل على مستوى البايت، لأن غياب هذا التحديد في ترميز DER سبب مشاكل لـ Bitcoin في الماضي.

وأفضل طريقة لتفهم معيار توقيع هي أن تكتب دالة التحقق منه وتشغلها على متجهات الاختبار Test Vectors الرسمية، وهي ملف CSV ينشره المعيار نفسه فيه مفاتيح ورسائل وتوقيعات، بعضها صحيح وبعضها مصمم ليفشل لسبب محدد. نحمل الملف:

```bash
curl -sL -o test-vectors.csv https://raw.githubusercontent.com/bitcoin/bips/master/bip-0340/test-vectors.csv
```

ثم نكتب التحقق كما يصفه BIP340 فوق نفس ملف `ec.py`، واحفظه باسم `schnorr.py`:

```python
# BIP340 Schnorr verification, run against the official test vectors (bip-0340/test-vectors.csv).
import csv, hashlib
from ec import p, n, G, add, mul

def tagged(tag, msg):
    t = hashlib.sha256(tag.encode()).digest()
    return hashlib.sha256(t + t + msg).digest()

def lift_x(x):                                 # the point with this x and an even y, or None
    if x >= p: return None
    c = (pow(x, 3, p) + 7) % p
    y = pow(c, (p + 1) // 4, p)
    if pow(y, 2, p) != c: return None
    return (x, y if y % 2 == 0 else p - y)

def verify(pub, msg, sig):
    P = lift_x(int.from_bytes(pub, "big"))
    r, s = int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")
    if P is None or r >= p or s >= n: return False
    e = int.from_bytes(tagged("BIP0340/challenge", sig[:32] + pub + msg), "big") % n
    R = add(mul(s), mul(n - e, P))             # R = s*G - e*P
    return R is not None and R[1] % 2 == 0 and R[0] == r

ok = 0
rows = list(csv.DictReader(open("test-vectors.csv")))
for row in rows:
    got = verify(bytes.fromhex(row["public key"]), bytes.fromhex(row["message"]), bytes.fromhex(row["signature"]))
    want = row["verification result"] == "TRUE"
    ok += got == want
    if not want:
        print(f"vector {row['index']:>2}: rejected as expected ({row['comment'][:60]})")
print(f"{ok}/{len(rows)} vectors match the expected result")
```

```bash
docker run --rm --name lab-crypto-py -v "$PWD:/lab" -w /lab python:3.13.16-slim python schnorr.py
```

والمخرج سوف يكون كما يلي:

```text
vector  5: rejected as expected (public key not on the curve)
vector  6: rejected as expected (has_even_y(R) is false)
vector  7: rejected as expected (negated message)
vector  8: rejected as expected (negated s value)
vector  9: rejected as expected (sG - eP is infinite. Test fails in single verification if ha)
vector 10: rejected as expected (sG - eP is infinite. Test fails in single verification if ha)
vector 11: rejected as expected (sig[0:32] is not an X coordinate on the curve)
vector 12: rejected as expected (sig[0:32] is equal to field size)
vector 13: rejected as expected (sig[32:64] is equal to curve order)
vector 14: rejected as expected (public key is not a valid X coordinate because it exceeds th)
19/19 vectors match the expected result
```

في الإعداد أعلاه لاحظ التالي:

- المفتاح العام في Schnorr هو الإحداثي x فقط، 32 بايت، والدالة `lift_x` تعيد النقطة ذات y الزوجي، وهذا اتفاق في المعيار يوفر بايتاً في كل مفتاح.
- الدالة `tagged` تضيف هاش اسم الغرض مرتين قبل الرسالة، وهذا يسمى الهاش الموسوم Tagged Hash، والسبب ألا يصلح هاش محسوب لغرض في مكان غرض آخر.
- كل متجه مرفوض يختبر حالة حدية بعينها: مفتاح ليس على المنحنى، أو s يساوي n، أو r يساوي p، وهذه بالضبط الحالات التي تنساها الكتابة السريعة للكود، ولو أهملنا شرط `s >= n` لفشل المتجه 13.
- المعادلة في السطر `R = s*G - e*P` خطية، وهذا هو ما يسمح بجمع توقيعات عدة أطراف، على عكس ECDSA التي فيها مقلوب k داخل المعادلة.

وفي Ethereum ذهب الإجماع في اتجاه ثالث: حسابات المستخدمين ما زالت تستخدم ECDSA على secp256k1، أما المدققون Validators الذين يصوتون على الكتل فيستخدمون توقيعات BLS، وكما يذكر [توثيق مفاتيح المدققين](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/keys/?ref=arabroot.io) فالسبب أن BLS يسمح بدمج التوقيعات Aggregation بكفاءة عالية، وهذا ضروري مع العدد الكبير من الرسائل بين المدققين. وقد شرحنا التصويت نفسه في [قسم الخانات والحقب](https://arabroot.io/articles/%D8%A7%D9%84%D8%A7%D8%AC%D9%85%D8%A7%D8%B9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%88%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%B5%D8%A9/#slots-epochs). والصورة التالية تضع الأنواع الثلاثة جنباً إلى جنب:

![ثلاث بطاقات: ECDSA على secp256k1 في معاملات Bitcoin القديمة وكل حسابات Ethereum بترميز DER متغير حتى 72 بايت ونقطة ضعفها رقم k المكرر، وSchnorr من BIP340 في Bitcoin منذ تفعيل Taproot عند الكتلة 709,632 بطول ثابت 64 بايت مع الخطية والتحقق الدفعي، وBLS لتصويت المدققين في إجماع Ethereum لأنه يدمج آلاف التوقيعات في توقيع واحد](https://arabroot.io/content/images/2026/10/blockchain-cryptography-05-signatures.webp)

نفس الفكرة بثلاث صيغ، لكل صيغة مكان يناسب خصائصها

## إثبات Merkle: كيف تتحقق المحفظة الخفيفة؟

والطلب الأخير في أي تطبيق يستلم دفعات هو أن يتحقق تطبيق الجوال من وصول الدفعة دون أن يثق بخادم الشركة ودون أن يحمل السلسلة كلها. والأداة لذلك هي إثبات Merkle Proof: إذا كان عند التطبيق رأس الكتلة، وفيه جذر Merkle، فيكفيه أن يحصل على المعاملة ومعها الهاشات المجاورة لها على طريقها إلى الجذر، فيحسب الجذر بنفسه ويقارنه. وقد بنينا الجذر لأربع معاملات بسيطة في المقال الأول، والآن نفعلها على كتلة حقيقية من Bitcoin Core، وفيها عدد فردي من المعاملات.

ننشئ في نفس Container محفظة، ونعدن 101 كتلة حتى تصبح مكافأة الأولى قابلة للصرف كما شرحنا في المقال الأول، ثم نرسل أربع معاملات وننشئ كتلة تجمعها:

```bash
btc createwallet lab
A=$(btc getnewaddress)
btc generatetoaddress 101 $A > /dev/null
for i in 1 2 3 4; do btc -named sendtoaddress address=$(btc getnewaddress) amount=0.$i fee_rate=2 > /dev/null; done
H=$(btc generatetoaddress 1 $A | grep -oE '[0-9a-f]{64}')
btc getblock $H 1 | grep -E '"(merkleroot|nTx|height)"'
btc getblock $H 1 | sed -n '/"tx"/,/]/p'
```

والمخرج سوف يكون كما يلي، بعد سطر اسم المحفظة:

```text
  "height": 102,
  "merkleroot": "568cfcd76ea638476caca215fafc72c973c832219de92e94b61d5cdb4ce9a558",
  "nTx": 5,
  "tx": [
    "18018ce98731f30fb281d0784962fd3c8b2b5a8744624711d91be4d34299c4da",
    "a28f306863e9594e20a26e07b521178a412eca7d00c88319e0bc5bd32488ce31",
    "baa737c920931d08b43fd2430c68c4a075a58bcf66b50027cc939961e2b07981",
    "a1a1b7b6a738375600fa4c6de52341cefb5932b26f5a426e37333258fc261995",
    "b78c512e0e947b1fbc3ffd833549653a9fd9b6aae9edac8a1a65614d2de47969"
  ]
```

خمس معاملات: الأولى معاملة المكافأة Coinbase، والأربع التي أرسلناها. والملف التالي يحسب الجذر من هذه القائمة، ثم يبني إثباتاً للمعاملة رقم 3 ويتحقق منه كما تفعل المحفظة الخفيفة. احفظه باسم `merkle.py`:

```python
# Recompute a real block's Merkle root from its txids, then prove one transaction with a short branch.
import hashlib, sys
dsha = lambda b: hashlib.sha256(hashlib.sha256(b).digest()).digest()
txids, header_root, target = sys.argv[1:-2], sys.argv[-2], int(sys.argv[-1])

def root(leaves):
    level = leaves[:]
    while len(level) > 1:
        if len(level) % 2: level.append(level[-1])          # odd count: repeat the last hash
        level = [dsha(level[i] + level[i + 1]) for i in range(0, len(level), 2)]
    return level[0]

naive = root([bytes.fromhex(t) for t in txids]).hex()
print("as displayed (wrong) :", naive[:32], naive == header_root)
leaves = [bytes.fromhex(t)[::-1] for t in txids]             # RPC shows hashes byte-reversed
good = root(leaves)[::-1].hex()
print("byte-reversed (right):", good[:32], good == header_root)

# branch for one transaction: the sibling at each level
branch, idx, level = [], target, leaves[:]
while len(level) > 1:
    if len(level) % 2: level.append(level[-1])
    branch.append(level[idx ^ 1])
    level = [dsha(level[i] + level[i + 1]) for i in range(0, len(level), 2)]
    idx //= 2
print(f"proof for tx {target}: {len(branch)} hashes, {32 * len(branch)} bytes")

h, idx = leaves[target], target                             # what a light wallet does with the proof
for sib in branch:
    h = dsha(sib + h) if idx & 1 else dsha(h + sib)
    idx //= 2
print("proof reaches root   :", h[::-1].hex() == header_root)
```

ونمرر له قائمة المعاملات والجذر ورقم المعاملة:

```bash
TX=$(btc getblock $H 1 | sed -n '/"tx"/,/]/p' | grep -oE '[0-9a-f]{64}' | tr '\n' ' ')
R=$(btc getblock $H 1 | grep merkleroot | grep -oE '[0-9a-f]{64}')
docker run --rm --name lab-crypto-py -v "$PWD:/lab" -w /lab python:3.13.16-slim python merkle.py $TX $R 3
```

والمخرج سوف يكون كما يلي:

```text
as displayed (wrong) : 429a583ebfcdf280184310eb39b05e9b False
byte-reversed (right): 568cfcd76ea638476caca215fafc72c9 True
proof for tx 3: 3 hashes, 96 bytes
proof reaches root   : True
```

والسطر الأول هو المشكلة التي ظهرت لنا فعلاً في أول محاولة: حسبنا الجذر من قيم txid كما تعرضها RPC فخرج جذر خاطئ. والسبب أن Bitcoin يحسب الهاشات على البايتات بترتيب، ويعرضها للإنسان مقلوبة البايتات، وهي عادة قديمة من أول إصدار تعامل الهاش كرقم صغير البايت أولاً Little Endian. فإذا قلبت كل txid قبل الحساب، ثم قلبت الجذر الناتج عند العرض، يظهر الجذر الصحيح `568cfcd7…` المطابق لرأس الكتلة. وإذا رأيت في أي كود يتعامل مع Bitcoin حساباً لا يطابق الشبكة، فأول ما تبحث عنه هو ترتيب البايتات.

ولاحظ أيضاً حجم الإثبات: ثلاثة هاشات أي 96 بايتاً، وهو يزيد هاشاً واحداً فقط كلما تضاعف عدد المعاملات في الكتلة، فكتلة فيها ألفا معاملة تحتاج قرابة أحد عشر هاشاً. والآن نطلب نفس الإثبات من Bitcoin Core نفسه ونتحقق منه:

```bash
P=$(btc gettxoutproof '["a1a1b7b6a738375600fa4c6de52341cefb5932b26f5a426e37333258fc261995"]')
echo ${#P}
btc verifytxoutproof $P
```

والمخرج سوف يكون كما يلي:

```text
430
[
  "a1a1b7b6a738375600fa4c6de52341cefb5932b26f5a426e37333258fc261995"
]
```

الإثبات الذي يعطيه [gettxoutproof](https://developer.bitcoin.org/reference/rpc/gettxoutproof.html?ref=arabroot.io) طوله 430 حرف Hex، أي 215 بايتاً، لأنه يحمل معه رأس الكتلة كاملاً بطول 80 بايت وعدد المعاملات والهاشات اللازمة وبتات تصف شكل الشجرة، و`verifytxoutproof` فك الإثبات وأرجع المعاملة التي يثبتها. ولاحظ حدود هذه الطريقة: الإثبات يقول إن المعاملة في كتلة لها هذا الرأس، ولكن المحفظة يجب أن تتأكد أيضاً أن هذا الرأس جزء من السلسلة التي فيها أكبر عمل، أي أن تحمل رؤوس الكتل وتتحقق من إثبات العمل فيها كما شرحنا في مقال الإجماع. والصورة التالية تبين الشجرة والإثبات:

![شجرة Merkle لكتلة فيها خمس معاملات: في الأسفل tx0 المكافأة وtx1 وtx2 وtx3 وtx4 ونسخة مكررة من tx4، ثم H(0,1) وH(2,3) وH(4,4) ونسخة مكررة منها، ثم H(0..3) وH(4,4,4,4)، ثم الجذر 568cfcd7، والمعاملة tx3 بلون برتقالي وأشقاؤها tx2 وH(0,1) وH(4,4,4,4) بلون أزرق، وتحت الشجرة ثلاث بطاقات: البرهان 3 هاشات بحجم 96 بايت، والمشكلة التي ظهرت أن الحساب من txid كما تعرضه RPC أعطى جذراً خاطئاً حتى قلبنا البايتات، وتحقق Bitcoin Core ببرهان حجمه 215 بايت](https://arabroot.io/content/images/2026/10/blockchain-cryptography-06-merkle-proof.webp)

المحفظة الخفيفة تحتاج المعاملة وثلاثة هاشات فقط لتصل إلى الجذر في رأس الكتلة

### عيب التكرار في شجرة Bitcoin

لاحظت في التجربة أن عدد المعاملات الفردي يجعل الشجرة تكرر آخر هاش في كل مستوى، وهذا الاختيار نفسه له ثغرة موثقة. ففي ملف [merkle.cpp](https://github.com/bitcoin/bitcoin/blob/master/src/consensus/merkle.cpp?ref=arabroot.io) في كود Bitcoin Core تعليق تحذيري موجه لمن يتعلم التشفير أو يصمم نظاماً جديداً بشجرة Merkle، يقول إن هذه الخوارزمية فيها عيب خطير مرتبط بمعرفات المعاملات المكررة، وهو الثغرة [CVE-2012-2459](https://nvd.nist.gov/vuln/detail/CVE-2012-2459?ref=arabroot.io). ويشرح التعليق أن قائمة المعاملات \[1,2,3,4,5,6\] والقائمة \[1,2,3,4,5,6,5,6\] تعطيان نفس الجذر، لأن هاش العقدة F وهاش الزوج (F,F) متساويان. وكانت المشكلة أن المهاجم يرسل الكتلة بالقائمة المكررة، فتفشل في التحقق، وإذا علمت العقدة الكتلة كغير صالحة للأبد رفضت بعدها النسخة الصحيحة التي لها نفس الهاش. والحل في الكود أن يكتشف التحقق هذه الحالة. والدرس إذا صممت شجرة Merkle في نظامك: لا تكرر آخر عنصر، وضع طول القائمة أو علامة مختلفة للأوراق والعقد الداخلية في الهاش، فهكذا تبني الأنظمة الأحدث أشجارها، ومنها سجلات شفافية الشهادات.

## الحاسوب الكمومي: ما الذي يتأثر فعلاً؟

وفي نهاية المراجعة يبقى سؤال يطرحه كثيرون بعد أن يقرأوا خبراً: «هل يستطيع الحاسوب الكمومي أن يحسب مفاتيحنا الخاصة؟». والإجابة الدقيقة تحتاج أن نفصل بين العائلتين. التوقيعات على المنحنيات الإهليلجية، ECDSA وSchnorr وBLS، تقوم على صعوبة ECDLP، وخوارزمية Shor تستطيع نظرياً على حاسوب كمومي كبير بما يكفي أن تحسب المفتاح الخاص من المفتاح العام. أما دوال الهاش فخوارزمية Grover تسرع البحث فيها ولكنها لا تلغي صعوبتها، وأقصى ما تفعله أنها تخفض هامش الأمان، ولهذا السبب يبقى طول 256 بت مريحاً. ولا يوجد اليوم حاسوب كمومي يقترب من هذا المستوى، ولكن الترحيل في شبكة لا مالك لها يحتاج اتفاقاً واسعاً وسنوات، لذلك يبدأ النقاش مبكراً.

وفي 13 أغسطس 2024 [نشرت NIST أول ثلاثة معايير نهائية](https://www.nist.gov/news-events/news/2024/08/nist-releases-first-3-finalized-post-quantum-encryption-standards?ref=arabroot.io) للتشفير المقاوم للحوسبة الكمومية: FIPS 203 لتبادل المفاتيح ML-KEM، وFIPS 204 للتوقيع ML-DSA، وFIPS 205 للتوقيع SLH-DSA المبني على الهاش كبديل احتياطي، وقالت إنها جاهزة للاستخدام الفوري ودعت إلى بدء دمجها. والصعوبة العملية في البلوكتشين أن توقيعات هذه المعايير أكبر بكثير من 64 بايت، وكل بايت في الشبكة تحفظه كل عقدة للأبد، لذلك لن يكون الترحيل مجرد تغيير مكتبة.

![ثلاث بطاقات: التوقيعات على المنحنيات ECDSA وSchnorr وBLS تتأثر نظرياً بخوارزمية Shor التي تحسب المفتاح الخاص من العام، فالعنوان الذي ظهر مفتاحه العام هو الأكثر عرضة، ودوال الهاش SHA-256 وKeccak-256 تسرعها خوارزمية Grover ولكنها لا تجعل عكسها ممكناً، والبدائل المعيارية من NIST في 13 أغسطس 2024: FIPS 204 وFIPS 205 للتوقيع وFIPS 203 لتبادل المفاتيح، وتحتها نصيحة بعدم إعادة استخدام العنوان بعد الصرف منه](https://arabroot.io/content/images/2026/10/blockchain-cryptography-07-quantum.webp)

التوقيعات هي الحلقة المعرضة، والهاش يصمد، والمعايير البديلة موجودة ولكن حجمها مشكلة في الشبكات

وهنا تظهر فائدة عملية لاختيار Bitcoin أن يكون العنوان هاشاً للمفتاح العام وليس المفتاح نفسه: المفتاح العام لا يظهر على السلسلة حتى أول مرة تصرف فيها من العنوان، لذلك المبالغ في عناوين لم يصرف منها أحد محمية بالهاش وليس بالمنحنى فقط. والقاعدة التي تستفيد منها اليوم بغض النظر عن الكم: لا تعيد استخدام العنوان نفسه بعد الصرف منه، والمحافظ الحديثة تولد عنواناً جديداً لكل استلام تلقائياً، وسوف نشرح كيف في مقال المحافظ والمفاتيح.

## قائمة مراجعة لأي كود يتعامل مع المفاتيح

هذه هي القائمة التي خرجنا بها من مراجعة طلب الدمج، وتصلح لأي كود يولد مفاتيح أو يوقع معاملات أو يتحقق منها:

| البند                  | كيف تتحقق منه                                                                                 | ماذا يحدث إذا أهملته                     |
| ---------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------- |
| دالة الهاش الصحيحة     | احسب هاش النص الفارغ وقارنه بالقيمة المنشورة، وKeccak-256 لـ Ethereum وليس SHA3-256           | عناوين ومعرفات دوال لا تطابق الشبكة      |
| مصدر العشوائية للمفتاح | مولد آمن للتشفير مثل secrets في Python أو مولد النظام، وليس random                            | مفاتيح يمكن تخمينها                      |
| رقم k في التوقيع       | مكتبة تطبق RFC 6979، والتوقيع المتكرر لنفس الرسالة يعطي نفس الناتج                            | كشف المفتاح الخاص من توقيعين             |
| مكتبة المنحنى          | مكتبة مراجعة مثل libsecp256k1 أو ما يبنى عليها، وليس كوداً مكتوباً يدوياً كالذي في هذا المقال | تسرب عبر الوقت، وأخطاء في الحالات الحدية |
| التحقق من المدخلات     | كل نقطة على المنحنى، وr وs ضمن الحدود، كما في متجهات BIP340                                   | قبول توقيعات أو مفاتيح غير صالحة         |
| ترتيب البايتات         | قارن نتيجة حسابك مع العقدة نفسها في كل خطوة                                                   | جذور وهاشات لا تطابق الشبكة              |
| العنوان ومجموع التحقق  | EIP-55 في Ethereum، وBech32 أو Base58Check في Bitcoin، ورفض العنوان الذي لا يطابق             | إرسال أموال إلى عنوان فيه خطأ إملائي     |
| مفاتيح الاختبار        | ابحث في المستودع عن مفاتيح Anvil المعروفة وعن أي مفتاح خاص في ملفات الإعداد                   | سرقة فورية لأي مبلغ حقيقي                |

## أخطاء شائعة

- **«التشفير في البلوكتشين يخفي البيانات»**: لا، البيانات على السلسلة العامة علنية، والتشفير هنا يثبت من أرسل وأن البيانات لم تتغير، وأي سر تضعه على السلسلة يقرؤه الجميع.
- **كتابة التشفير بنفسك في الإنتاج**: الكود في هذا المقال صحيح رياضياً ومر على متجهات الاختبار، ومع ذلك لا يصلح للإنتاج، لأنه يأخذ وقتاً مختلفاً بحسب قيمة المفتاح، ولأن المكتبات المراجعة عالجت حالات لا تظهر في عشرين متجه اختبار.
- **استخدام نفس المفتاح في كل مكان**: رأينا أن نفس المفتاح يعطي عنواناً في الشبكتين، فتسربه في تطبيق تجريبي يكشف أموالاً في شبكة أخرى.
- **تحويل العنوان إلى حروف صغيرة قبل حفظه**: بعض قواعد البيانات أو الواجهات تحول كل النصوص إلى حروف صغيرة، فيضيع مجموع التحقق الذي يحمله عنوان Ethereum في حالة حروفه كما يصف [EIP-55](https://eips.ethereum.org/EIPS/eip-55?ref=arabroot.io)، ويصبح الخطأ الإملائي في حرف واحد غير قابل للاكتشاف. احفظ العنوان كما هو، وتحقق من مجموعه عند الإدخال، وارفض العنوان المكتوب بحالة مختلطة لا تطابق المجموع بدلاً من أن تصلحه بصمت، والسبب أن الحالة المختلطة الخاطئة دليل على خطأ في النسخ.
- **الوثوق بخادم واحد لحالة الدفعة**: إذا كان تطبيقك يعرض «تم الاستلام» لأن خادماً قال ذلك، فهذا ليس تحققاً، والتحقق الحقيقي إثبات Merkle مع رؤوس كتل تتحقق من إثبات عملها أو عقدة تشغلها بنفسك.

## إزالة التجربة

```bash
docker rm -f -v lab-crypto-btc
docker image rm bitcoin/bitcoin:31.1 ghcr.io/foundry-rs/foundry:v1.8.6
```

## تدريب عملي

1. عدل `keys.py` ليحسب عنوان Ethereum بـ `hashlib.sha3_256` بدلاً من Keccak، وقارن الناتج بعنوان `cast`، فهذا هو الخطأ الذي بدأنا به المقال.
2. في `schnorr.py` احذف الشرط `s >= n` وشغل الملف، ولاحظ أي متجه يفشل ولماذا.
3. أنشئ كتلة فيها تسع معاملات، وتحقق أن الإثبات لأي معاملة فيها أربعة هاشات.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- البلوكتشين العام يقوم على ثلاث عائلات: الهاش والتوقيع على المنحنيات والترميز، والتشفير فيه يثبت ولا يخفي.
- Bitcoin يستخدم SHA-256 مرتين وHash160، وEthereum يستخدم Keccak-256 الأصلية، وهي تختلف عن SHA3-256 المعيارية بسبب الحشو.
- المفتاح العام هو المفتاح الخاص مضروباً في النقطة G على المنحنى secp256k1، والضرب سهل والرجوع منه غير ممكن عملياً، والعنوان هاش للمفتاح العام.
- تكرار الرقم k في توقيعي ECDSA يكشف المفتاح الخاص لأي شخص، والحل RFC 6979، وحادثة Android سنة 2013 تبين ثمن العشوائية الضعيفة.
- Schnorr في Bitcoin منذ Taproot يعطي توقيعاً بطول 64 بايت مع الخطية والتحقق الدفعي، وBLS في Ethereum يدمج توقيعات المدققين.
- إثبات Merkle يسمح بالتحقق من معاملة بهاشات قليلة، وانتبه لترتيب البايتات في Bitcoin.
- الحاسوب الكمومي يهدد التوقيعات وليس الهاش، ومعايير NIST البديلة موجودة منذ أغسطس 2024، وحجمها هو الصعوبة الكبرى في الشبكات.

[![](https://arabroot.io/content/images/2026/10/blockchain-basics-cover-cover.webp)دليلكيف يعمل البلوكتشين Blockchain ومتى لا تحتاجهما المشكلة التي يحلها البلوكتشين أصلاً وكيف يعمل من الداخل، ومتى تكون قاعدة البيانات العادية هي الحل الأصح.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D9%88%D9%85%D8%AA%D9%89-%D9%84%D8%A7-%D8%AA%D8%AD%D8%AA%D8%A7%D8%AC%D9%87/)[![](https://arabroot.io/content/images/2026/10/crypto-for-devs-cover-cover.webp)دليلالتشفير للمطورين: ماذا تستخدم ولماذا لا تخترع خوارزميتكأي هاش تستخدم لكلمات المرور، ومتى تحتاج التشفير المتماثل أو غير المتماثل، وأين تحفظ المفاتيح بأمان في تطبيقك.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B4%D9%81%D9%8A%D8%B1-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D9%87%D8%A7%D8%B4-%D9%88%D9%83%D9%84%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B1%D9%88%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD/)[![](https://arabroot.io/content/images/2026/10/encodings-cover-cover.webp)دليلالترميز Encoding للمطورين: Base64 وأخواتهلماذا نحول البايتات إلى حروف حتى تمر في البريد والروابط، وكيف تفك أي سلسلة منها، ولماذا لا يحمي Base64 أي سر.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-base64-%D9%88-base32-%D9%88-base58/)[![](https://arabroot.io/content/images/2026/10/consensus-cover-cover.webp)دليلكيف تتفق شبكة البلوكتشين؟ إثبات العمل وإثبات الحصةكيف تتفق آلاف العقد التي لا يثق بعضها ببعض على سجل واحد، ولماذا يكلف الغش كهرباء في شبكة ومالاً محجوزاً في أخرى.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A7%D8%AC%D9%85%D8%A7%D8%B9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D9%84%D9%88%D9%83%D8%AA%D8%B4%D9%8A%D9%86-%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%88%D8%A7%D8%AB%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%B5%D8%A9/)[![](https://arabroot.io/content/images/2026/10/crypto-history-cover-cover.webp)دليلتاريخ العملات الرقمية: من Cypherpunks إلى Bitcoinكيف حاول المهندسون صنع نقد رقمي أربعين سنة، ولماذا تعثرت المحاولات قبل Bitcoin، وما الذي جمعه Bitcoin منها.](https://arabroot.io/articles/%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-%D9%85%D9%86-cypherpunks-%D8%A7%D9%84%D9%89-bitcoin/)[![](https://arabroot.io/content/images/2026/10/bitcoin-how-it-works-cover-cover.webp)دليلكيف يعمل Bitcoin؟ المعاملة والمخرجات غير المنفقة UTXOنبني معاملة Bitcoin بأيدينا ونتتبعها حتى الكتلة، لنفهم لماذا لا يوجد رصيد، وأين تذهب الرسوم، ومتى تصبح الدفعة دفعة فعلاً.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-bitcoin-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%85%D9%84%D8%A9-%D9%88%D8%A7%D9%84%D9%85%D8%AE%D8%B1%D8%AC%D8%A7%D8%AA-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D9%86%D9%81%D9%82%D8%A9-utxo-%D9%88%D8%A7%D9%84%D9%83%D8%AA%D9%84%D8%A9/)[![](https://arabroot.io/content/images/2026/10/crypto-wallets-keys-cover-cover.webp)دليلالمحافظ والمفاتيح: العبارة السرية وكيف تحمي أصولكما الذي تحفظه المحفظة فعلاً، وكيف تولد اثنتا عشرة كلمة كل مفاتيحك، وكيف تفصل شركة صغيرة بين معرفة الأرصدة والتوقيع.](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%AD%D8%A7%D9%81%D8%B8-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D8%A7%D9%84%D8%B9%D8%A8%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%88%D9%83%D9%8A%D9%81-%D8%AA%D8%AD%D9%85%D9%8A-%D8%A7%D8%B5%D9%88%D9%84%D9%83/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، واختبار التجارب على Docker Engine 29.8.0 مع Python 3.13.16 ومكتبة pycryptodome 3.23.0، وFoundry v1.8.6، وBitcoin Core v31.1.0 في وضع regtest.