تُبنى هذه البرامج الأربعة كل منها تطبيقًا كاملًا لاستدعاء سري: مسألة، وواجهة، وبرنامج ضيف (guest) بلغة Rust، وأدوات من جانب المضيف، ونشر واستدعاء على testnet الحية. وهي أطول من الدروس التعليمية، ويعتمد كل منها على جزء مختلف من الآلة. اكتب برنامجًا بلغة Rust أو C هو الدليل إلى النموذج الذي تستخدمه جميعًا. اقرأه أولًا إن كانت read_input أو المقطع العام أو خانات الإخراج الثماني جديدة عليك.
شُغّل كل ما هنا أول مرة على chain 18 مع fullnode v0.6.7 بتاريخ 2026-09-30، وكل مخرجات هذه الصفحات منسوخة من تلك التشغيلات. لا تنتقل البرامج عبر قطع السلسلة، ولذلك نُشرت البرامج الأربعة مجددًا، دون تغيير، بتاريخ 2026-10-01 على chain 19 ثم على chain 20 (fullnode v0.6.8)، وتكررت الاستدعاءات في كل مرة. معرّف البرنامج هو تجزئة للشيفرة والمقطع العام لا للسلسلة، ولذلك فالمعرّفات كلها واحدة؛ أما تجزئات المعاملات في السجلات المنسوخة فهي لـ chain 18، ويربط الجدول أدناه بمعاملات chain 20. وعلى chain 20 يمكن للبرنامج أيضًا أن يحتفظ بحالة بين الاستدعاءات (RPL-2)؛ والبرامج الأربعة هذه لا تستخدمها، وتغطي حالة البرنامج ومستودع example-programs البرامج التي تفعل.
البرامج الأربعة
| البرنامج | ما الذي يثبته الاستدعاء | يعتمد على | الكلمات | المستوى |
|---|---|---|---|---|
| تصويت مجهول الهوية | أن المستدعي عضو في قائمة منشورة دون أن يذكر أيّ الأعضاء هو، وأنه لم يصوّت من قبل | Keccak-256، ومقطع عام | 427 | 12 |
| إثبات الالتزامات | أن أرصدة العملاء الخاصة لا يتجاوز مجموعها احتياطيًا منشورًا، تحت جذر يستطيع كل عميل التحقق منه | SHA-256، وشجرة تُبنى داخل البرنامج | 994 | 14 |
| دفتر أستاذ خارج السلسلة | أن دفعة من التحويلات تنقل دفتر الأستاذ من حالة إلى التالية، مع تفويض كل مُرسِل وعدم إنشاء أي مبلغ | Poseidon2، وحالة تنتقل بين الاستدعاءات | 773 | 14 |
| قرار ائتماني خاص | أن نموذجًا منشورًا يوافق على بيانات مقدِّم طلب خاصة زكّاها شاهد (attester) أو يرفضها | الحساب بالأعداد الصحيحة، ونموذج في المقطع العام | 462 | 12 |
كلها حية على chain 20. كل استدعاء في الجدول يكرّر، بالمدخلات نفسها، استدعاء chain 18 المعروض في صفحة ذلك البرنامج:
| البرنامج | معرّف البرنامج | نُشر على chain 20 في | أول استدعاء على chain 20 |
|---|---|---|---|
| anonymous vote | ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 | f6ae5528…0f82 | 3a304e7a…427b |
| proof of liabilities | fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0 | a8bc281b…17d9 | 6116c15f…d62a |
| off-chain ledger | c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab | 72cc95f2…3d51 | 9c9998bb…66eb |
| private credit decision | 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c | 413e6658…e42b | d3ceb306…124a |
يعرض المستكشف كل برنامج واستدعاءاته في randscan.org/programs. تُستبدل سلسلة testnet من حين إلى آخر ولا تنتقل البرامج معها: بعد القطع التالي ستختفي هذه المعرّفات وسيلزم نشر البرامج من جديد.
الإعداد
سلسلة الأدوات. تُبنى برامج الضيف باستخدام rand-guest من مستودع الدوائر (circuits)، عند الإيداع (commit) الذي يضمّنه fullnode v0.6.7 وv0.6.8 كليهما:
git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31 # branch feat/v067-circuits
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"
يهمّ الإيداع لسببين. فـ hc دالة في الشيفرة المصدرية وسلسلة الأدوات، ولذلك قد يحصل برنامج ضيف أُعيد بناؤه عند إيداع آخر من guest-sdk على hc مختلف. كما تغيّرت توقيعات الـ SDK: ففي aeacf31 تأخذ keccak وsha256_compress مراجع إلى مصفوفات، بينما كانت الإيداعات الأقدم تأخذ مؤشرات خام.
المصادر. برامج الضيف الأربعة وسكربتات المضيف الخاصة بها وبيانات العرض كلها في تنزيل واحد. فُكّ ضغطها داخل نسخة العمل (checkout)، لأن rand-guest build يجد guest-sdk بالبحث صعودًا من مجلد برنامج الضيف:
mkdir -p my-guests
curl -sSf https://randprotocol.org/examples/rand-example-programs.tar.gz | tar -xz --strip-components=1 -C my-guests
ls my-guests
# allowlist ledger ledger-host score solvency
يمكن أيضًا تصفّح الملفات واحدًا واحدًا تحت https://randprotocol.org/examples/programs/، مثل allowlist/src/main.rs.
المحفظة. يحتاج النشر والاستدعاء إلى محفظة rand من fullnode v0.6.8 (cargo build --release -p randprotocol-client في نسخة fullnode عند الوسم v0.6.8، أو rand المرفقة بإصدار v0.6.8)، ومفتاح، وعملة RAND. لا تستطيع محفظة أقدم إجراء معاملات على chain 20: فـ chain 20 تربط كل إثبات وكل رسالة موقّعة بنشأتها (genesis)، ومحفظة v0.6.7 لا تفعل ذلك.
نقطة RPC. النقطة العامة https://rpc.randprotocol.org تكفي لكل ما في هذه الصفحات: قراءة البرامج والإيصالات، ومزامنة المحفظة، والنشر والاستدعاء. تقرأ المزامنة الأولى للمحفظة كل ترويسة كتلة وكل كتلة تحمل مذكرة، أي 645 طلبًا عند ارتفاع chain 18 بتاريخ 2026-09-30، واستغرقت نحو ثلاث دقائق عبر النقطة. تسمح النقطة لكل عنوان بـ 1,200 قراءة و6 عمليات إرسال في الدقيقة. أما العقدة الخاصة بك (تشغيل عقدة) فلا قيود فيها.
export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json
على chain 18، أُثبت أحد الأصوات المحسوبة في صفحة التصويت مجهول الهوية، وهو fdbcc960…fc2b، على حاسوب محمول وأُرسل عبر النقطة العامة وحدها. وعلى chain 19 وchain 20 مرّت بالطريقة نفسها المعاملات الثلاث عشرة كلها في كل مرة، أي عمليات النشر الأربع والاستدعاءات التسعة.
ما الذي غيّرته chain 18 على البرنامج
يعلن الاستدعاء حدًّا للغاز ويدفع ثمنه. تقيس مجموعة القيود 8 كل تشغيل. الدورة العادية تساوي 1 غاز. وتشذّ عن ذلك استدعاءات التجزئة (syscalls):
| التعليمة | الغاز |
|---|---|
أي تعليمة RV32IM، وHALT، وWRITE_OUTPUT، وREAD_INPUT، وREAD_PUBLIC | 1 |
POSEIDON2 على n كلمة | 3 + 3 × ⌈n / 4⌉ |
SHA256، ضغطة واحدة | 64 |
KECCAK، تبديل واحد | 192 |
يحمل الإثبات قيمة عامة جديدة واحدة هي الحد المعلن، وتفرض الدائرة ألا يتجاوز غاز التشغيل هذا الحد. أما الغاز الفعلي فيبقى خاصًا. يشغّل rand call البرنامج في المحاكي أولًا ويطبع القيمتين:
gas: 5636 (declaring 6144, tier 12)
افتراضيًا تقرّب المحفظة الغاز الفعلي إلى الأعلى إلى أقرب مضاعف لـ 2^(tier − 2)، أي 1,024 عند المستوى 12 و4,096 عند المستوى 14، ولذلك فالحد المعلن لا يكشف إلا القليل عن التشغيل. يعلن --gas-limit max سقف المستوى ولا يقول شيئًا يتجاوز المستوى. ويعلن --gas-limit N رقمًا محددًا.
تتبع الرسوم الحد المعلن والبايتات.
floor = 0.001 RAND + gas price × declared limit + byte price × ⌈bytes / 1024⌉
بدأت الأسعار عند 100 وحدة للغاز الواحد و800 وحدة لكل KiB (الوحدة تساوي 10⁻⁹ RAND)، وتتحرك قليلًا في كل كتلة نحو هدف. تقدّم rand_getLimits الأسعار الحالية. وتضيف المحفظة هامشًا قدره خطوتان سعريتان، أي معامل 1.125²، لأن الاستدعاء يستقر بعد كتلتين أو ثلاث من تسعيره.
تفصيل واحد يحسم معظم الرسوم. فمنذ chain 18 صار إثبات الاستدعاء مرتبطًا بمعاملته نفسها، ولذلك يتعيّن على المحفظة تثبيت الرسم قبل أن يوجد الإثبات، فتسعّر الإثبات عند سقف السلسلة البالغ 4 MiB بدل حجمه الفعلي. وفي التصويت مجهول الهوية يساوي ذلك (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6,191,437 وحدة، أي 0.0062 RAND، والغاز منها نحو الثُّمن. وتكلّف الاستدعاءات الأربعة في هذه الصفحات ما بين 0.0057 و0.0075 RAND لكل منها. ويتجاوز --fee القيمة الافتراضية.
كل حزمة هي إثباتان. تعمل chain 18 بتفويض مجزّأ: فالحزمة التي تدفع رسمًا تحمل إثبات تفويض صغيرًا من المستوى 10 بجانب إثبات الحزمة من المستوى 14. تنشئ المحفظة الاثنين وتطبع سطرًا لكل منهما. فالاستدعاء إذن ثلاثة إثباتات: الاستدعاء والتفويض والحزمة. وعلى حاسوب محمول بمعالج Apple Silicon استغرقت الاستدعاءات الأربعة هنا ما بين ثلاث دقائق وخمس دقائق ونصف لكل منها، بذروة ذاكرة بين 6.1 و6.6 GB. وإثبات الحزمة نحو دقيقتين من ذلك في كل مرة. ويضيف إثبات الاستدعاء من المستوى 12 نصف دقيقة، ومن المستوى 14 دقيقتين.
السقوف التي يجب أن يلائمها الاستدعاء.
| الحد | chain 18 إلى 20 |
|---|---|
| المستوى | 14 على الأكثر: 16,383 دورة، بما فيها صفوف التلخيص |
| تبديلات Poseidon2 | 2,048 عند المستوى 14، بما فيها تلخيصات البرنامج والمدخلات والمقطع العام |
| تبديلات Keccak | 128 |
| ضغطات SHA-256 | 128 |
| إثبات الاستدعاء | 4,194,304 بايت (4 MiB) |
| البرنامج | 8,180 كلمة دون مقطع عام، وأقل منها ببضع كلمات معه: فـ chain 20، مثل chain 18 وchain 19، ترفض نشرًا لا يستطيع أي استدعاء من المستوى 14 أن يستوعبه (ProgramUncallable)، أيًّا كان ما تسمح به max_program_words (65,535) |
| المقطع العام | 32,768 كلمة، تُثبَّت عند النشر |
| المدخلات الخاصة مع سجل مختوم | 16,071 كلمة |
يفحص rand-guest run --tier 14 الحدّين الأولين قبل النشر. وجدول التجزئة يكبّر الإثبات. فإثبات الدفتر من المستوى 14، بلا جدول تجزئة، حجمه 1.50 MB. وجدول SHA-256 يرفع إثبات الالتزامات إلى 1.91 MB، وجدول Keccak يرفع إثبات التصويت إلى 3.35 MB، وهذا معظم سقف 4 MiB.
الأعراف التي تشترك فيها البرامج الأربعة
- الخانة 0 هي حالة، والصفر لا يكون قيمة صالحة لها أبدًا. لكل نتيجة، بما فيها حالات الفشل، رقم يبدأ من 1 فصاعدًا، ويتوقف الفشل توقفًا سليمًا كي يمكن إثباته. ولا يمكن الخلط بين إيصال كل كلماته أصفار ونتيجة فعلية.
- الخانة 7 تُترك دون مساس. يكتب البرنامج الضيف المكتوب بلغة Rust والذي يُصاب بحالة panic القيمة
0xdeadbeef(3,735,928,559) فيها. فيفحص القارئ الخانة 7 أولًا. - تحمل الخانات من 2 إلى 6 قيمة من 160 بتًا: nullifier أو جذرًا أو رابطًا أو التزامًا. أما تجزئة كاملة من 256 بتًا فهي ثماني كلمات، ولن تترك مكانًا للحالة. ويُبقي الاقتطاع إلى خمس كلمات على 80 بتًا من مقاومة التصادم، وتذكر كل صفحة ما الذي تحميه هذه المقاومة.
- ترتيب البايتات يتبع دالة التجزئة. قيم Keccak كلمات little-endian، وقيم SHA-256 كلمات big-endian، لأن هذه هي الطريقة التي تقرأ بها كل استدعاء نظام (syscall) كتلتها. وإذا اختلف نص برمجي على المضيف مع برنامج ضيف في ذلك أنتجا ملخصات لا تتطابق أبدًا.
- كل ما يتغير من نسخة إلى أخرى يوجد في المقطع العام، وكل ما يتغير من استدعاء إلى آخر فهو مُدخل خاص. فجذر استطلاع الرأي ورقم الاحتياطي وأوزان النموذج تُثبَّت عند النشر وتُربط بمعرّف البرنامج. ولا يحمل الاستدعاء أي كلمات عامة خاصة به.
- كلمات الإدخال عشرية في سطر الأوامر. تحوّل الصفحات ملف الشاهد إلى خيارات بالعبارة
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt).
استدعِ بنفسك
بعد فك حزم المصادر وبناء سلسلة الأدوات ووجود محفظة v0.6.8 ممولة، يتم التصويت بوصفك أحد الأعضاء التجريبيين في أربعة أوامر:
cd my-guests/allowlist
python3 allowlist.py witness 10 1 demo-secrets.txt 6 1 > witness.txt # member 6, choice 1
rand-guest build . --max-words 65535 # only needed to run it in the emulator first
rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
لا يحتاج الاستدعاء إلى الصورة: يجلب rand call شيفرة البرنامج ومقطعه العام من العقدة، ويتحقق من أن تجزئتهما تطابق المعرّف، ثم يُنتج الإثبات عليهما.
لا تحتاج قراءة النتيجة إلى محفظة ولا إلى عقدة. فأي إيصال هو طلب واحد إلى نقطة النهاية العامة:
$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"rand_getReceipt","params":["8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"]}'
{"id":1,"jsonrpc":"2.0","result":{"h_in":"ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6","h_pub":"2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510","height":98335,"index":0,"outputs":[1,2,392974773,873526743,1921224601,3950868247,2248722827,0],"program":"ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970","tier":12,"tx":"8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"}}
تعرض واجهة API الخاصة بالمستكشف استدعاءات برنامج ما في طلب واحد: يُرجع https://randscan.org/api/v1/programs/<program id> القيمتين call_count وrecent_calls (Explorer API).
ما لا يفعله أيٌّ منها
البرنامج على Rand هو إثبات لحساب ما، ولا تتصرف السلسلة بناءً على أي من مخرجاته. لا يحرّك أيٌّ من هذه البرامج RAND ولا أي رمز، ولا يحتفظ برصيد، ولا يتذكر استدعاءً سابقًا. يُحصي الأصوات من يقرأ الإيصالات. وتُقارن الالتزامات بالاحتياطيات التي تحتاج إلى أدلتها الخاصة. أما أرصدة دفتر الأستاذ فهي أرقام في شجرة خارج السلسلة. وقرار الائتمان بِت يختار المُقرض أن يلتزم به. وتنتهي كل صفحة بقائمة بما يتركه إثباتها لمن يستخدمونها.