Rand Protocol
الوثائق
برامج RISC-V

مجموعة تعليمات RISC-V

ماذا يعني RV32IM على Rand: التعليمات التي يُثبتها zkVM، وما يرفضه المدقِّق، واستدعاءات النظام الثمانية، والذاكرة، وحاوية الصورة التي يُشحن بها البرنامج، والحد الأقصى لحجم البرنامج.

اقرأ هذه الصفحة قبل أن تكتب برنامجًا لـ 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
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL 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 أو مقدار إزاحة لا تنفذه الآلة
FenceFENCE أو FENCE.I؛ فالآلة لا تملك ترتيبًا للذاكرة
Csrأي تعليمة CSR؛ فالآلة لا تملك CSRs
EbreakEBREAK؛ فالفخاخ غير مُنمذجة
Syscallecall تُضبط قيمة 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)
0HALTينهي التشغيل1halt()rand_halt()
1WRITE_OUTPUT slot wordيضبط المخرج العام slot < 8، مرة واحدة على الأكثر لكل خانة؛ والخانات غير المكتوبة تساوي صفرًا1write_output(slot, word)rand_write_output
2READ_INPUT idxيعيد كلمة المدخل الخاص ذات الفهرس idx، المرتبطة بالالتزام المملّح H_IN1read_input(idx)rand_read_input
3POSEIDON2 ptr nيحسب تجزئة n ≤ 4096 كلمة عند عنوان الكلمة ptr ويكتب الملخص المؤلف من 8 كلمات فوق ptr..ptr+8؛ بلا حشو3 + 3·⌈n/4⌉unsafe poseidon2(ptr, n)rand_poseidon2
4KECCAK ptrتبديل Keccak-f[1600] واحد في موضعه على 50 كلمة192keccak(state)، keccak256(msg)rand_keccak
5SHA256 ptrضغطة SHA-256 واحدة في موضعها على 24 كلمة64sha256_compress(buf)، sha256(msg)rand_sha256_compress
6READ_PUBLIC idxيعيد كلمة المقطع العام ذات الفهرس idx، المرتبطة بـ H_PUB غير المملّح1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr nPOSEIDON2 مع ربط الطول 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 }:

  1. يكتب زوج li/sw لكل كلمة بيانات غير صفرية. وتُتخطى الكلمات الصفرية، لأن الذاكرة RAM تُقرأ صفرًا أصلًا.
  2. يضع تلك المقدمة مباشرة تحت text_base، فتكون base_pc = text_base − 4 × prologue words. وتنتقل المقدمة تلقائيًا إلى _start الخاصة بالبرنامج.
  3. يُحسب 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_bytes4,194,304 (4 MiB)؛ و8 MiB في السلاسل من 13 إلى 16
max_block_bytes20,971,520 (20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,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، مفسّر EVM18,009لانعملا
sbpf.bin، مفسّر sBPF8,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 هذه الميزانية خطوة بخطوة.

آخر تحقق مقابل الإيداع c0878c3f في fullnode (السلسلة 21) بتاريخ 2026-10-12، وcircuits `aeacf31` (`rand-guest/README.md`, `rand-guest/src/check.rs`, `research/docs/01-isa.md`, `guest-sdk/`), fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `ledger/program_state.rs`, `deploy/cut-chain20-genesis.sh`), chain 20's genesis and `rand_getLimits`, read on 2026-10-01, fullnode tag v0.8.0 `c0878c3f` (`docs/zkvm.md` §8a, constraint set 9), `deploy/genesis-chain21.json` and `rand_getLimits` on chain 21, read on 2026-10-12. إذا اختلف الكود عن هذه الصفحة فالكود هو الصحيح؛ أبلغونا بذلك.