تأخذ هذه الصفحة برنامج Rust صغيرًا من الشيفرة المصدرية إلى برنامج منشور على Rand. كل أمر من أوامر rand-guest أدناه جرى تشغيله، ومخرجاته ملصوقة كما هي، لا ملخصة. أُجريت التشغيلات في 2026-09-19 عند إيداع circuits 7ef3220. شبكة testnet هي الآن السلسلة 20، وسلسلة أدواتها، مثل سلسلتي الأدوات للسلسلتين 18 و19، مبنية عند aeacf31 (يثبّت fullnode v0.6.8 الإيداع نفسه الذي يثبّته v0.6.7)، ولم تُعد المخرجات هناك. يشرح RISC-V ISA الآلة واستدعاءات النظام وصيغة الملف.
1. تثبيت سلسلة الأدوات
يعتمد hc على المترجم بدقة، ولذلك فإن سلسلة الأدوات مثبّتة على إصدارات محددة.
| الأداة | الإصدار | السبب |
|---|---|---|
| Rust | 1.98.1 | يترجم الضيف (guest) وrand-guest نفسه |
الهدف riscv32im-unknown-none-elf | للإصدار 1.98.1 | هدف الضيف |
| نسخة مسحوبة من circuits | randprotocol/zkp-circuits عند aeacf31 للسلاسل 18 إلى 20 | تحوي rand-guest وguest-sdk ونص الرابط (linker script) |
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits, what fullnode v0.6.7 and v0.6.8 pin
rustup toolchain install 1.98.1
rustup target add riscv32im-unknown-none-elf --toolchain 1.98.1
(cd rand-guest && cargo +1.98.1 build --release)
alias rand-guest="$PWD/rand-guest/target/release/rand-guest"
يحتوي rand-guest على خمسة أوامر فرعية: build وcheck وpack وrun وinfo.
للإيداع أهمية لأن hc دالة في الشيفرة المصدرية وسلسلة الأدوات: فالضيف الذي يُعاد بناؤه على إيداع آخر من guest-sdk قد يحصل على hc مختلف. بين 7ef3220 وaeacf31 لم يتغير المجلد rand-guest، وتغير guest-sdk/src/lib.rs (الخطوة 2).
2. كتابة الضيف
الضيف حزمة ثنائية بنمط no_std وno_main تعتمد على guest-sdk. ويجب أن يقع داخل نسخة circuits المسحوبة: يعثر rand-guest build على النسخة بالبحث عن guest-sdk/ فوق مجلد الضيف، ويرفض ضيفًا خارجها.
يقرأ هذا الضيف عتبة وعددًا وبعدها ذلك العدد من الأرصدة الخاصة. وينشر 1 والفائض إذا كانت الأرصدة تغطي العتبة. ولا ينشر الأرصدة أبدًا.
circuits/
guest-sdk/
rand-guest/
my-guests/
threshold/
Cargo.toml
src/main.rs
my-guests/threshold/Cargo.toml:
[package]
name = "threshold"
version = "0.1.0"
edition = "2021"
[dependencies]
guest-sdk = { path = "../../guest-sdk" }
[profile.release]
opt-level = 3
lto = true
panic = "abort"
codegen-units = 1
[workspace]
يُبقي [workspace] الفارغ الحزمة خارج أي مساحة عمل أب، كما يفعل الضيف fib المودَع.
my-guests/threshold/src/main.rs:
#![no_std]
#![no_main]
use guest_sdk::{halt, read_input, write_output};
/// Input 0 is a threshold, input 1 a count n, inputs 2..2+n are private balances.
/// Output 0 is 1 if the balances sum to at least the threshold, else 0.
/// Output 1 is the surplus when they do. The balances themselves are never published.
#[no_mangle]
pub extern "C" fn main() -> ! {
let threshold = read_input(0);
let n = read_input(1);
let mut sum: u32 = 0;
for i in 0..n {
sum = sum.wrapping_add(read_input(2 + i));
}
if sum >= threshold {
write_output(0, 1);
write_output(1, sum - threshold);
}
halt();
}
يصدّر guest-sdk الدوال read_input وread_public وwrite_output وposeidon2 وkeccak وkeccak256 وsha256_compress وsha256 وhalt، ويوفر _start الذي يضبط مؤشر المكدس ويستدعي main. لا يوجد مخصّص ذاكرة ولا فاصلة عائمة.
عند aeacf31 تختلف ثلاثة من هذه التواقيع عنها عند 7ef3220، وهناك تصدير جديد واحد:
| الدالة | عند 7ef3220 | عند aeacf31 |
|---|---|---|
keccak | fn keccak(ptr: *mut u32) | fn keccak(state: &mut [u32; 50]) |
sha256_compress | fn sha256_compress(ptr: *mut u32) | fn sha256_compress(buf: &mut [u32; 24]) |
poseidon2 | fn poseidon2(ptr: *mut u32, n: usize) | unsafe fn poseidon2(ptr: *mut u32, n: usize) |
poseidon2_len | غير موجودة | fn poseidon2_len(buf: &mut [u32], n: usize) -> [u32; 8]، استدعاء النظام 7 |
تربط poseidon2_len طول الرسالة بالإسفنجة (sponge) وهي الاستدعاء الآمن. ما زال rand-guest check عند aeacf31 يسرد استدعاءات النظام من 0 إلى 6 فقط، ولذلك فإن ضيفًا يستدعيها يُبنى إلى صورة يرفضها build برسالة ecall with a7 = 7, which is no syscall the machine implements (المسألة رقم 1 في zkp-circuits). إلى أن يُصلح ذلك، يجري الضيف التجزئة بـ unsafe { poseidon2(ptr, n) } على رسائل موسومة ذات طول ثابت. لا يستخدم الضيف في هذه الصفحة أيًّا من الأربعة.
قواعد مجلد الضيف:
- لا
.cargo/configولا.cargo/config.tomlفيه، ولا في أي مجلد أب، ولا في$CARGO_HOME. يرفضbuildويسمّي كل ملف، لأن cargo كان سيدمجه في البناء. - لا أعلام مترجم من عندك. يضبطها
buildكلها: نص الرابط، و-unaligned-scalar-mem، وإعادة تعيين مسار تجعل الصورة مستقلة عن موضع النسخة المسحوبة. - ملف
.ldواحد على الأكثر. يستخدمbuildملف.ldالوحيد في مجلد الضيف، أوguest-sdk/guest.ldإن لم يوجد. وإذا وُجد أكثر من واحد فسمِّه بالعلم--ld.
3. البناء والفحص
$ rand-guest build my-guests/threshold --out threshold.bin
Compiling guest-sdk v0.1.0 (…/circuits/guest-sdk)
warning: unstable feature specified for `-Ctarget-feature`: `unaligned-scalar-mem`
|
= note: this feature is not stably supported; its behavior can change in the future
warning: `guest-sdk` (lib) generated 1 warning
Compiling threshold v0.1.0 (…/circuits/my-guests/threshold)
warning: `threshold` (bin "threshold") generated 1 warning (1 duplicate)
Finished `release` profile [optimized] target(s) in 0.15s
40 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote threshold.bin and its .sha256 (40 words, hc a491df99f289d81e2b6e09849a850b14e36e2bd6edc5f5a6de46c3d67c703665, program id 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853)
التحذير unaligned-scalar-mem متوقع: فسلسلة الأدوات تعطّل الوصول غير المحاذى عمدًا.
يترجم build بالأمر cargo +1.98.1 build --release --target riscv32im-unknown-none-elf، ثم يشغّل check على الناتج، ويكتب حاوية الصورة وملف التثبيت .sha256. وإذا رفض check البرنامج، يكتب build الصورة مع ذلك ويخرج برمز غير صفري.
| العلم | الافتراضي | المعنى |
|---|---|---|
--out <path> | <dir>/image.bin | موضع كتابة الصورة |
--max-words <n> | 4096 | الحد الذي يقيس check عليه؛ مرِّر حد السلسلة المستهدفة (65,535 على السلسلة 20) |
--ld <path> | ملف .ld الوحيد في المجلد، وإلا guest-sdk/guest.ld | نص الرابط |
--lang rust|c | rust | المترجم |
4. تشغيله على المحاكي
مرِّر المدخلات الخاصة بـ --input، كلمة لكل علم. ويُبلغ --tier t أيضًا عما إذا كان التشغيل يلائم المستوى t.
$ rand-guest run threshold.bin --input 1000 --input 4 --input 400 --input 250 --input 300 --input 75 --tier 10
out[0] = 1
out[1] = 25
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 52
tier 10
tier 10: fits (cycles 66 of 1023, Poseidon2 permutations 14 of 128)
مجموع الأرصدة 1,025، فينشر البرنامج 1 وفائضًا قدره 25. وعند الرصيدين 400 و250 يكون المجموع ناقصًا، وتكون كل المخرجات صفرًا. يطبع الاعتراض (trap) trap: <error> ويخرج بالرمز 2.
5. فحص الصورة
$ rand-guest info threshold.bin
form: image container
text 40 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 40 words from base_pc 0x1000
hc a491df99f289d81e2b6e09849a850b14e36e2bd6edc5f5a6de46c3d67c703665
program id 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853
40 words against a cap of 4096: fits
hc هو ما يُتحقق منه إثبات الاستدعاء مقابله. ومعرّف البرنامج هو ما تنشره وتستدعيه به.
6. النشر
استخدم المحفظة rand من fullnode v0.3 أو أحدث؛ فالمحافظ الأقدم لا تقرأ حاوية الصورة. ومنذ السلسلة 14 يُتحقق من كل إثبات حزمة مقابل ربط معاملته، ولذلك فإن محفظة أقدم من بناء السلسلة 14 (b3c594c) تُثبت حزمة ترفضها السلسلة. على السلسلة 20، السلسلة الحية، استخدم rand من fullnode v0.6.8 أو أحدث (يشغّل الأسطول v0.6.8، ويُرفق rand مبني على Linux بإصداره على GitHub). لا تستطيع محفظة v0.6.7 الإرسال على السلسلة 20: فجينيسيس السلسلة 20 يضبط binding_domain: 1، ولذلك يتضمن كل ربط إثبات وكل رسالة موقّعة تجزئة الجينيسيس، بينما تربط محفظة v0.6.7 بمعرّف السلسلة وحده. تكفي نقطة النهاية العامة https://rpc.randprotocol.org لمزامنة المحفظة والنشر والاستدعاء (الإعداد).
rand fee deploy 40 # the floor for a 40-word program
rand program deploy threshold.bin
يطبع program deploy معرّف البرنامج وعدد الكلمات وhc. ثم يسأل العقدة عن رسوم النشر بـ rand_estimateFee، وهي ترفض البرنامج الذي يتجاوز حد السلسلة قبل أي إثبات. ثم يُثبت حزمة تحويل ذاتي بقيمة صفر تدفع الرسوم، ويرسلها، وينتظر الالتزام بها. الحزمة هي حزمة الأصول المخفية ذات 4 مدخلات و4 مخرجات التي تحملها كل معاملة. ومنذ السلسلة 17 هي إثباتان، وتطبع المحفظة سطرًا لكل منهما: تفويض إنفاق من المستوى 10 (ثوانٍ، نحو 1.37 MB) وحزمة المستوى 14 (نحو دقيقتين على حاسوب محمول، نحو 1.5 MB). وعلى السلسلة 15 كان إثباتًا واحدًا من المستوى 14 بنحو 100 s ونحو 1.42 MB. يعرض تصويت مجهول عملية نشر على السلسلة 18 كما طبعتها المحفظة.
رسوم النشر هي 0.001 RAND + 0.0001 RAND × words، على السلسلة 20 كما كانت من قبل. يُسعِّر الغاز الاستدعاءات لا عمليات النشر:
| البرنامج | الكلمات | رسوم النشر |
|---|---|---|
threshold | 40 | 0.005 RAND |
| برنامج عند الحد الافتراضي (السلسلة 12) | 4,096 | 0.4106 RAND |
| أكبر برنامج تنشره السلسلة 20 بلا مقطع عام | 8,180 | 0.819 RAND |
| برنامج عند حد 65,535 كلمة (السلاسل 13 إلى 15) | 65,535 | 6.5545 RAND |
يقل threshold عن 4,096 كلمة، فيلائم الحد الافتراضي وحد السلسلة 20 البالغ 65,535. والبرنامج الذي يتجاوز 4,096 كلمة يحتاج إلى سلسلة يضبط جينيسيسها max_program_words عند هذا الحد أو أعلى، مثل السلسلة 20 (RISC-V ISA). وللسلسلة 20، كالسلسلة 19، حد أدنى ثانٍ: فهي ترفض نشر برنامج لا يستطيع أي استدعاء من المستوى 14 استيعابه (ProgramUncallable)، وهو ما يزيد على 8,180 كلمة بلا مقطع عام، وأقل من ذلك مع مقطع عام. يشغّل rand program deploy --input <word> … (أو --check-call لاستدعاء بلا مدخلات) الاستدعاء الذي تقصده على المحاكي أولًا، ويرفض النشر ما لم يقع عند المستوى 14 أو دونه.
تحقق من السجل:
rand program show 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853
قيمة code_hash فيه هي hc الذي طبعه rand-guest، مكتوبًا بصورة مختلفة. يطبع rand-guest كل كلمة من الكلمات الثماني للملخص بترتيب big-endian، وتطبع المحفظة وrand program show وrand_getProgram بايتات كل كلمة بترتيب little-endian. لذلك يظهر a491df99…3665 على صورة 99df91a4…707c: كل مجموعة من ثمانية أرقام ست عشرية معكوسة البايتات. أما معرّف البرنامج فيكتبه الاثنان بالصورة نفسها.
7. استدعاؤه
rand call 1b129cbce89df252150ab3d387a927beb1650a40e6cb0b04e86cdc203ca1b853 \
--input 1000 --input 4 --input 400 --input 250 --input 300 --input 75
تجلب المحفظة الشيفرة، وتشغّل الاستدعاء على المحاكي، وتطبع gas: <exact> (declaring <limit>, tier <t>) والرسوم قبل أن تُثبت أي شيء. ثم تختم نسخة المدخلات، وتُثبت التشغيل محليًا، وتدفع الرسوم عبر حزمة، وتطبع الإيصال. وعلى السلسلة 20 يكون ذلك ثلاثة إثباتات: الاستدعاء، وتفويض الإنفاق من المستوى 10، وحزمة المستوى 14.
لم تعد الرسوم على السلسلة 20 هي 0.001 + call_fee(tier)، التي جعلت هذا الاستدعاء يكلّف 0.002 RAND على السلسلة 15. الحدّ الأدنى هو BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉: أي 0.001 RAND، مضافًا إليها الحدّ المعلن للغاز مضروبًا في سعر الغاز (100 وحدة عند التكوين الأول، والوحدة 10⁻⁹ RAND)، مضافًا إليها بايتات الإثبات والمغلّف مضروبة في سعر البايت (800 وحدة لكل KiB عند التكوين الأول). على السلسلة 20 لا يمكن أن يتجاوز السعران 10,000 و80,000 وحدة. أما الغاز الفعلي فيبقى سرّيًا. افتراضيًا تعلن المحفظة الغاز مقرَّبًا إلى الأعلى إلى أقرب مضاعف لـ 2^(tier − 2)، وتسعّر الإثبات عند الحدّ الأقصى للسلسلة البالغ 4,194,304 بايت لأن الرسوم تُحدَّد قبل أن يوجد الإثبات، وتضيف هامشًا قدره خطوتان سعريتان. كلّفت أربعة استدعاءات مقيسة على السلسلة 18 ما بين 0.0057 و0.0075 RAND لكل منها (برامج الأمثلة). لم يُجرَ هذا الاستدعاء على السلسلتين 18 إلى 20، لذلك لا يُذكر له رقم.
ترى السلسلة الـ tier والحدّ المعلن للغاز وH_IN وكلمات المخرجات الثماني [1, 25, 0, 0, 0, 0, 0, 0]. ولا ترى الأرصدة أبدًا.
يتناول البرامج الإيصالات ومغلّفات الإدخال وrand open-call. والاستدعاء Call من هذا النوع عديم الحالة؛ وعلى السلسلة 20 يمكن للبرنامج أيضًا أن يحتفظ بخلايا وبخزنة عبر Invoke (حالة البرنامج (RPL-2)).
دع الآخرين يتحقّقون من hc
البناء قابل لإعادة الإنتاج. يقوم build بتنقية بيئة المترجم (RUSTFLAGS وتجاوزات CARGO_* وأغلفة RUSTC وغيرها)، ويرفض ملفات إعداد cargo، ويعيد تعيين مسار النسخة المسحوبة إلى /rand-circuits. المصدر نفسه مع Rust 1.98.1 يعطي hc نفسه على أي جهاز. ولكي يتأكد الآخرون مما نشرتَه، انشر مصدر الضيف (guest) وإيداع الدارات (circuits commit) الخاص بك (aeacf31 لضيف مبني للسلاسل 18 إلى 20). عندئذ يستطيع أي شخص إعادة البناء ومقارنة hc بـ code_hash من rand_getProgram. فالسلسلة تخزّن الكلمات فقط، لا المصدر.
استكشاف الأخطاء وإصلاحها
| الرسالة | السبب |
|---|---|
… is not inside a circuits checkout (no guest-sdk/ above it) | انقل مجلد الضيف إلى داخل النسخة المسحوبة |
build يذكر ملف .cargo/config.toml | احذفه أو انقله؛ لا يمكن تنقيته |
REJECTED مع Cap | البرنامج يتجاوز --max-words؛ ارفعه إلى الحدّ الأقصى للسلسلة المستهدفة، أو قلّص البرنامج (opt-level = "s") |
REJECTED مع Syscall وa7 = 7 | الضيف يستدعي poseidon2_len؛ وrand-guest check عند aeacf31 لا يعرف استدعاء النظام 7 بعد (zkp-circuits issue #1) |
REJECTED مع Fence أو Csr أو Ebreak | أصدر البناء تعليمة لا توجد في الآلة؛ راجع ما يُرفض |
trap: … من run | خطأ وقت التشغيل، مثل عنوان غير محاذى أو رقم استدعاء نظام غير منفَّذ |
يقول run إن التشغيل لا يلائم أي tier | التشغيل يتجاوز 2²⁰ صفًّا أو ميزانية Poseidon2 الخاصة به؛ ولا يمكن إثباته |