اقرأ هذه الصفحة قبل أن تكتب برنامجًا لـ Rand. فهي تبيّن الآلة التي يعمل عليها شيفرتك، وما ترفضه سلسلة الأدوات، وكيف يتخاطب البرنامج مع العالم الخارجي، وأي ملف تنشره. وتعتمد عليها الصفحات التالية: نشر برنامج Rust، ونشر برنامج RISC-V، والمترجمان.
الآلة
برامج Rand هي RV32IM: مجموعة الأعداد الصحيحة الأساسية 32 بت من RISC-V مع امتداد الضرب والقسمة. وهي تعمل داخل Rand zkVM، الذي يُثبت كل تشغيل بإثبات Plonky3 batch STARK. لا يرى المتحقق أي تعليمة. إنه يفحص الإثبات مقابل hc، وهو ملخص Poseidon2 للبرنامج داخل الدارة.
| المجموعة | التعليمات |
|---|---|
| علوية / قفزات | LUI AUIPC JAL JALR |
| تفرعات | BEQ BNE BLT BGE BLTU BGEU |
| تحميل / تخزين | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| النظام | ECALL فقط |
تمسح JALR البت 0 من هدفها كما تنص RV32I، ويجب أن يكون ترميزها بقيمة funct3 = 0. جاءت القاعدتان مع مجموعة القيود 7 (السلسلة 16)؛ وقبلها كان الهدف الفردي لا يمكن جلبه ولا يمكن إثبات التشغيل. أما الهدف الزوجي الذي ليس من مضاعفات أربعة فلا يزال غير قابل للجلب.
لا توجد أعداد عائمة، ولا عمليات ذرية (امتداد A)، ولا تعليمات مضغوطة (امتداد C)، ولا مسجلات 64 بت. وتتحول القسمة 64 بت في C إلى استدعاء لدالة مساعدة برمجية؛ انظر مسار C.
ما يُرفض
يمرّر rand-guest check كل كلمة من النص البرمجي عبر مفكك الترميز الخاص بالآلة نفسها. والبرنامج الذي يجتاز الفحص لا يمكن أن يفشل في الدارة لسبب يتعلق بالترميز أو برقم استدعاء النظام أو بالتخطيط. ويرفض الفاحص ما يلي:
| النتيجة | ما هي |
|---|---|
Compressed | ترميز RVC بطول 16 بت؛ فالآلة تفك ترميز الكلمات ذات 32 بت فقط |
Undecodable | رمز عملية أو funct أو مقدار إزاحة لا تنفذه الآلة |
Fence | FENCE أو FENCE.I؛ فالآلة لا تملك ترتيبًا للذاكرة |
Csr | أي تعليمة CSR؛ فالآلة لا تملك CSRs |
Ebreak | EBREAK؛ فالفخاخ غير مُنمذجة |
Syscall | ecall تُضبط قيمة a7 فيها بتعليمة li قبلها مباشرة ولا تسمّي أي استدعاء نظام |
Cap | كلمات أكثر من --max-words، محسوبة كما يحسبها المحمّل |
Layout | قاعدة نص غير محاذاة على الكلمة |
برنامج من أربع كلمات فيه fence وebreak وecall برقم 9، كما يبلغ عنه rand-guest check (رمز الخروج 1):
$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000 0x0ff0000f Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004 0x00100073 Ebreak: EBREAK: traps are not modelled
0x0000100c 0x00000073 Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED
أما ecall التي لا يستطيع الفاحص رؤية قيمة a7 فيها (إذا ضُبطت بغير li، أو بعد تفرع) فتُحتسب ضمن unresolved_ecalls ولا تُرفض. وتظل الآلة تُطلق فخًّا عند التشغيل إذا كان الرقم سيئًا.
لا يفحص الفاحص قيم المؤشرات ولا الأطوال ولا فهارس المدخلات، ولا المكدس البالغ 64 KiB قياسًا إلى الذاكرة RAM، ولا محاذاة العناوين المحسوبة وقت التشغيل. والوصول غير المحاذى انتهاك للقيود، لذا تُترجم سلسلة الأدوات بالخيار -unaligned-scalar-mem. ويبلغ rand-guest run عن أي فخ وقت التشغيل.
استدعاءات النظام
تقرأ ECALL رقم استدعاء النظام من a7، والوسيط الأول من a0، والثاني من a1. وتعود القيمة في a0.
| # | استدعاء النظام | الأثر | الغاز | Rust (guest-sdk) | C (guest.h) |
|---|---|---|---|---|---|
| 0 | HALT | ينهي التشغيل | 1 | halt() | rand_halt() |
| 1 | WRITE_OUTPUT slot word | يضبط المخرج العام slot < 8، مرة واحدة على الأكثر لكل خانة؛ والخانات غير المكتوبة تساوي صفرًا | 1 | write_output(slot, word) | rand_write_output |
| 2 | READ_INPUT idx | يعيد كلمة المدخل الخاص ذات الفهرس idx، المرتبطة بالالتزام المملّح H_IN | 1 | read_input(idx) | rand_read_input |
| 3 | POSEIDON2 ptr n | يحسب تجزئة n ≤ 4096 كلمة عند عنوان الكلمة ptr ويكتب الملخص المؤلف من 8 كلمات فوق ptr..ptr+8؛ بلا حشو | 3 + 3·⌈n/4⌉ | unsafe poseidon2(ptr, n) | rand_poseidon2 |
| 4 | KECCAK ptr | تبديل Keccak-f[1600] واحد في موضعه على 50 كلمة | 192 | keccak(state)، keccak256(msg) | rand_keccak |
| 5 | SHA256 ptr | ضغطة SHA-256 واحدة في موضعها على 24 كلمة | 64 | sha256_compress(buf)، sha256(msg) | rand_sha256_compress |
| 6 | READ_PUBLIC idx | يعيد كلمة المقطع العام ذات الفهرس idx، المرتبطة بـ H_PUB غير المملّح | 1 | read_public(idx) | rand_read_public |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 مع ربط الطول n بالإسفنجة، وتبديل واحد على الأقل (مجموعة القيود 7) | 3 + 3·max(⌈n/4⌉, 1) | poseidon2_len(buf, n) | لا يوجد |
كل تعليمة أخرى تكلّف 1 غاز: الغاز واحد لكل صف cpu، ولا يزيد إلا في استدعاءات التجزئة (مجموعة القيود 8، منذ السلسلة 18). واستدعاء Poseidon2 هو صف ecall الخاص به، وصفا كتابة راجعة، و⌈n/4⌉ من صفوف الامتصاص بثلاث وحدات غاز لكل صف.
POSEIDON2 لا يحشو. تكتب الإسفنجة فوق مساراتها الأولى بكل مقطع من أربع كلمات، فتُنتج [a] و[a, 0] و[a, 0, 0] و[a, 0, 0, 0] ملخصًا واحدًا. وعلى البرنامج الذي يجزّئ بيانات يتغير طولها أن يضع الطول في الرسالة، أو يجزّئ ترميزًا ثابت الطول، أو يستدعي POSEIDON2_LEN، الذي يزرع n في سعة الإسفنجة ويعطي ملخصًا مختلفًا للكلمات نفسها. وفي حزمة Rust SDK تكون poseidon2 دالة unsafe fn على مؤشر خام، وposeidon2_len هي الاستدعاء الآمن على شريحة؛ أما keccak وsha256_compress فتأخذان &mut [u32; 50] و&mut [u32; 24].
ثغرة واحدة في سلسلة الأدوات. عند الدارات aeacf31 تنفّذ الآلة والمحاكي وRust SDK استدعاء النظام 7، لكن rand-guest check لا ينفّذه: فقائمة استدعاءاته تتوقف عند 6، فيبلغ عن ecall بقيمة a7 = 7 على أنها نتيجة Syscall، ويخرج rand-guest build برمز غير صفري على برنامج ضيف يستدعي poseidon2_len (المسألة رقم 1 في circuits). ولا يوجد في guest.h غلاف له أيضًا. وإلى أن يُصلح ذلك، يستخدم البرنامج الضيف المبني بالأداة الافتراضية POSEIDON2 على رسائل ثابتة الطول.
ينشر الإثبات هذا بالضبط: hc وH_IN وH_PUB والطبقة وحد الغاز المعلن وكلمات المخرج الثماني. وثمة حدّان ساريان على السلسلة اليوم:
- المقطع العام يُثبَّت عند النشر. في السلسلة 20، يضبط
rand program deploy --publicالكلمات العامة للبرنامج (بحد أقصىmax_program_public_words، أي 32,768)، ويُفحص كل استدعاء مقابلها. ولا توجد كلمات عامة خاصة بالبرنامج لكل استدعاء. وتُلحق السلسلة 20، مثل السلسلة 19، ثماني كلمات بمقطع كل استدعاء بعدها، وهي ربط الاستدعاء الذي يشدّ الإثبات إلى معاملته (وفي السلسلة 20 يشمل أيضًا تجزئة التكوين الأول genesis)؛ ويقرأ البرنامج الضيف كلماته هو عند الفهارس نفسها كما كان من قبل. ويُلحقInvokeمن RPL-2 الانتقال المعلن بعد الربط، ويقرؤه البرنامج ابتداءً منpublic_len + 8(البرامج). وفي سلسلة بلا حقول الحدود، مثل السلسلة 12، يكون المقطع العام فارغًا ولا يجدREAD_PUBLICما يقرؤه. - كلمات المدخل الخاص للاستدعاء محدودة بالمظروف المختوم. يتيح
max_call_envelope_bytesفي السلسلة 20، وقيمته 65,536، ما يصل إلى 16,071 كلمة؛ وتتيح السلسلة التي بلا هذا الحقل 4,295.
يضيف استدعاء KECCAK أو SHA256 جدولًا إلى الإثبات، ثم يُظهر رأس الإثبات أن الاستدعاء أجرى تجزئة. والإثبات الحامل لـ keccak نحو 3.3 MB: فوق حد الإثبات الافتراضي 2 MiB، وتحت حد السلسلة 20 البالغ 4 MiB، ويجوز للاستدعاء أن يستخدم 128 تبديل Keccak-f و128 ضغطة SHA-256 على الأكثر (البرامج).
الذاكرة
- 32 مسجلًا؛ و
x0يساوي صفرًا. - تبدأ الذاكرة RAM من الصفر. وكل عنوان أقل من
2³⁰. - يضع سكربت الربط الافتراضي
guest-sdk/guest.ldالبرنامج عند0x1000، ثم.rodataو.dataو.bss، ثم مكدسًا حجمه 64 KiB عند__stack_top. - يحتاج البرنامج ذو مقطع البيانات إلى متسع تحت نصّه البرمجي لمقدمة المحمّل (في القسم التالي). ويرفع
.ldالخاص به قيمةORIGINإلى0x10000، كما يفعلevm.ldوsbpf.ld.
حاوية الصورة
يكتب rand-guest build وrand-guest pack صيغة ملف واحدة هي حاوية الصورة. وكل الأعداد كلمات 32 بت بترتيب little-endian:
[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…
يحوّل المحمّل الحاوية إلى برنامج { base_pc, words }:
- يكتب زوج
li/swلكل كلمة بيانات غير صفرية. وتُتخطى الكلمات الصفرية، لأن الذاكرة RAM تُقرأ صفرًا أصلًا. - يضع تلك المقدمة مباشرة تحت
text_base، فتكونbase_pc = text_base − 4 × prologue words. وتنتقل المقدمة تلقائيًا إلى_startالخاصة بالبرنامج. - يُحسب
hcومعرّف البرنامج على كاملwords، بما فيها المقدمة. فيرتبطhcبالبيانات كما يرتبط بالشيفرة.
الحاوية التي بلا بيانات لا تعطي مقدمة، وتعطي البرنامج نفسه وhc نفسه كما يعطيه ملف ثنائي مسطّح بلا ترويسة عند القاعدة نفسها.
يتعرف rand program deploy في fullnode v0.3 وما بعده على الحاوية من كلمتها الأولى. ولا يمكن أن تُفك 0x444e4152 تعليمةً أبدًا، فلا يمكن أن يُخطأ في ملف .bin خام فيُحسب حاوية. والملف الذي يبدأ بالرقم السحري لكنه مشوّه يُعدّ خطأ ولا يُلجأ معه إلى بديل.
هويتان
| الاسم | ما هو | من يستخدمه |
|---|---|---|
hc | ملخّص Poseidon2 للدارة للثلاثية (base_pc, len, words) | يُتحقَّق من كل إثبات لاستدعاء مقابله؛ ويعرضه rand_getProgram باسم code_hash |
| معرّف البرنامج | blake3("rand-program" ‖ base_pc ‖ words)؛ وعند النشر مع --public تُربط الكلمات العامة أيضًا، تحت الوسم rand-program-2 | المفتاح الذي يُخزَّن البرنامج المنشور ويُستدعى به |
يطبع rand-guest build وrand-guest info كليهما. hc ملزِم لكنه غير مُخفٍ: يستطيع أي شخص يملك برنامجًا مرشّحًا أن يتحقق منه.
سقف البرنامج
يجوز أن يحمل النشر عددًا من الكلمات لا يتجاوز سقف البرامج في السلسلة.
| السلسلة | السقف |
|---|---|
genesis بلا max_program_words (السلسلة 12) | 4,096 |
genesis يتضمن max_program_words | تلك القيمة، 1..=65535 |
| الحد الأقصى الصارم لـ zkVM، في أي سلسلة | 65,535 |
السقف جزء من تجزئة genesis، فلا يمكن تغييره في سلسلة قيد التشغيل. يتحقق rand-guest مقابل --max-words (الافتراضي 4,096). ويسأل rand program deploy العقدة عبر rand_estimateFee ويرفض أي برنامج يتجاوز السقف قبل أن يُنشئ أي إثبات.
السلسلة 20، وهي السلسلة الحية، ترفع السقف. تجزئة genesis الخاصة بها هي 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135 وقيمة max_program_words فيها 65,535. وهي تحتفظ بالحدود التي أدخلتها السلسلة 13 (سلسلة v0.4)، عدا سقف الإثبات الذي تخفضه السلاسل من 17 إلى 20 إلى النصف. وفيما يلي حدودها الأخرى:
| الحد | السلسلة 20 |
|---|---|
max_proof_bytes | 4,194,304 (4 MiB)؛ و8 MiB في السلاسل من 13 إلى 16 |
max_block_bytes | 20,971,520 (20 MiB) |
max_call_envelope_bytes | 65,536 |
max_program_public_words | 32,768 |
في السلسلة 20، كما في السلسلة 19، ليس سقف الكلمات هو الحد الملزِم. يضبط genesis فيها hardening_v6، وبموجبه يُرفض النشر (ProgramUncallable) متى لم يكن بوسع أي استدعاء أن يُثبت البرنامج. يُحدَّد الاستدعاء بالطبقة 14، التي يتسع جدول Poseidon2 فيها لـ 2,048 تبديلًا. يستهلك ملخّص البرنامج تبديلًا واحدًا لكل أربع كلمات، ويستهلك صف الملح الخاص بالمدخلات تبديلًا واحدًا، وتستهلك كلمات ربط الاستدعاء الثماني تبديلين، فيبقى 2,045: أي 8,180 كلمة لبرنامج بلا مدخل عام، وعددًا أقل مع وجود مدخل عام (8,168 مع تسع كلمات عامة). يرفض مجمّع المعاملات في كل عقدة مثل هذا النشر في أي سلسلة.
تجميع الكتل معطّل في السلسلة 20: فليس في genesis فيها قسم aggregation. وقد بدأ تشغيل السلسلة 20 في 2026-10-01 الساعة 06:17 UTC على fullnode v0.6.8، وهي fork صلب اقتُطع من حالة السلسلة 19 عند الارتفاع 7,258 (وتوقفت السلسلة 19 عند 7,700)؛ أما السلسلة 19 (التي بدأت عند 03:43 UTC في اليوم نفسه) فكانت بدورها re-genesis للسلسلة 18 عند الارتفاع 141,935. لا تنتقل البرامج عبر القطع، فلا يوجد في السلسلة 20 أي برنامج نُشر في سلسلة سابقة. والسلسلة 20 كذلك أول سلسلة فيها حالة برامج RPL-2 (Programs). وتعمل على الشبكة عبر عُقد الإقلاع (bootstrap) التالية:
/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
| الصورة | الكلمات | تناسب 4,096 | تناسب 65,535 | تُنشر في السلسلة 20 (8,180 قابلة للاستدعاء) |
|---|---|---|---|---|
fib (Rust) | 26 | نعم | نعم | نعم |
c-fib (C) | 25 | نعم | نعم | نعم |
| ERC-20، مترجَم (المرحلة الثانية) | 11,686 | لا | نعم | لا |
evm.bin، مفسّر EVM | 18,009 | لا | نعم | لا |
sbpf.bin، مفسّر sBPF | 8,317 | لا | نعم | لا |
| SPL Token، مترجَم | 65,096 | لا | نعم، مع فائض 439 | لا |
الطبقات
تحدّ طبقة الإثبات t التشغيلَ بـ 2ᵗ − 1 صفًّا: الدورات مضافًا إليها صفوف الملخّص (صف لكل أربع كلمات من البرنامج، وصف لكل أربع كلمات من المدخلات، وملخّص المدخل العام). وتحدد الطبقة أيضًا ميزانية Poseidon2 منفصلة. يعرض rand-guest run --tier t الاثنين معًا:
tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)
الطبقة علنية، وكذلك حدّ الغاز الذي يعلنه الإثبات. في السلسلة 20 تتبع رسوم الاستدعاء ذلك الحد وحجم الإثبات، لا الطبقة: أي ما لا يقل عن 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉، بالأسعار الجارية للسلسلة، التي تبدأ عند 100 و800 وحدة من 10⁻⁹ RAND، وتتحرك مع امتلاء الكتل، وتتوقف في السلسلة 20 عند 10,000 و80,000 وحدة (Fees). ولا يجوز أن يتجاوز الحد سقف الترويسة، (2ᵗ − 1) + 2ᵗ⁻² مضافًا إليه 191 لكل كتلة Keccak و63 لكل كتلة SHA-256 يعلنها الإثبات: أي 1,279 في الطبقة 10 و20,479 في الطبقة 14 بلا جدول تجزئة. لا يعرض rand-guest run الغاز، بل الدورات والتبديلات. أما rand call فيشغّل البرنامج أولًا ويطبع الغاز الذي يستهلكه والحد الذي يعلنه، وهو افتراضيًا الغاز الدقيق مقرَّبًا إلى أعلى إلى أقرب مضاعف لـ 2ᵗ⁻²، ثم يبدأ الإثبات:
gas: 5636 (declaring 6144, tier 12)
هذا السطر من استدعاء في السلسلة 18 بتاريخ 2026-09-30 (An anonymous vote). وفي سلسلة بلا قسم gas في genesis، أي السلسلة 15 وما قبلها، كانت الرسوم جدولًا بحسب الطبقة: 0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND. وتتزايد الذاكرة التي يحتاجها الإثبات بحدة مع الطبقة؛ انظر Validator and aggregator hardware.
منذ fullnode v0.5.6 (2026-09-25) لا يجوز أن يعلن الاستدعاء طبقة أعلى من 14: ترفض العقدة إثبات استدعاء من طبقة أعلى بالخطأ CallTierTooHigh قبل أن تبني مفتاح المتحقِّق، وتكون الكتلة التي تحمل واحدًا منها غير صالحة. وميزانية الطبقة 14 هي 16,383 صفًّا و2,048 تبديل Poseidon2، ويستهلك ملخّص البرنامج نفسه نحو تبديل واحد لكل أربع كلمات، فلا يمكن استدعاء برنامج يزيد على 8,180 كلمة، وترفض السلسلة 20، كالسلسلة 19، نشره. يستعرض Write a program in Rust or C هذه الميزانية خطوة بخطوة.