Rand Protocol
دستاویزات
RISC-V پروگرام

Rust یا C میں پروگرام لکھیے

اپنے خفیہ پروگرام کے لیے مکمل رہنما: پروگرام کیا کر سکتا ہے اور کیا نہیں، اس کے اِن پٹ اور آؤٹ پٹ کی ساخت کیسے بنائی جائے، Rust اور C دونوں میں لکھی گئی مہر بند بولی کی نیلامی، ٹیسٹنگ، ثبوت سازی کا بجٹ، عوامی اِن پٹ کے ساتھ ڈیپلائے کرنا، اسے کال کرنا اور نتیجے کی تصدیق۔

یہ صفحہ وہ سب کچھ بیان کرتا ہے جو Rand پر اپنا پروگرام رکھنے کے لیے درکار ہے: پروگرام کیا ہو سکتا ہے، وہ کیا پڑھے اور کیا شائع کرے اس کا ڈیزائن کیسے کیا جائے، اور ایک مکمل مثال جو دو بار لکھی گئی ہے، ایک بار Rust میں اور ایک بار C میں۔ اس کے بعد یہ ٹیسٹنگ، ثبوت سازی کے بجٹ، ڈیپلائے کرنے، کال کرنے، اور اس بات کا احاطہ کرتا ہے کہ کوئی بھی نتیجے کی جانچ کیسے کر سکتا ہے۔ ذیل میں rand-guest کا ہر آؤٹ پٹ ایک حقیقی رن سے لیا گیا ہے اور جوں کا توں چسپاں کیا گیا ہے۔ یہ رن 2026-09-25 کو circuits کمٹ 7ef3220 پر کیے گئے۔ testnet اب چین 20 ہے، جس کی ٹول چین، چین 18 اور 19 کی طرح، aeacf31 پر بنی ہے (برانچ feat/v067-circuits؛ fullnode v0.6.8 بھی اسی کمٹ پر مقرر ہے)، اور آؤٹ پٹس وہاں دوبارہ نہیں چلائے گئے۔ جہاں SDK یا چین مختلف ہے، صفحہ اس کی نشاندہی کرتا ہے۔

دو مختصر ٹیوٹوریل تیز راستہ ہیں: Rust پروگرام ڈیپلائے کریں اور RISC-V پروگرام ڈیپلائے کریں۔ یہ صفحہ فرض کرتا ہے کہ آپ Rust ٹیوٹوریل کا مرحلہ 1 مکمل کر چکے ہیں: aeacf31 پر randprotocol/zkp-circuits کا checkout، riscv32im-unknown-none-elf ٹارگٹ کے ساتھ Rust 1.98.1، rand-guest بائنری، اور C کے لیے clang 23.1.1 اور llvm-tools۔ مشین کے لیے حوالہ RISC-V ISA ہے۔ مثالی پروگرام یہاں سے آگے بڑے پروگراموں کی طرف جاتا ہے اور بتاتا ہے کہ چین 18 کی گیس پیمائش نے کیا بدلا۔ یہ صفحہ RPL-1 پروگراموں کا احاطہ کرتا ہے، جنہیں ایک Call بغیر اسٹیٹ کے چلاتا ہے؛ چین 20 پر RPL-2 پروگرام اسٹیٹ بھی موجود ہے، یعنی سیلز اور ایک والٹ جنہیں ایک Invoke تبدیل کرتا ہے (پروگرام)، اور اس کی مکمل مثالیں randprotocol/example-programs میں ہیں۔

1. پروگرام کیا ہو سکتا ہے

پروگرام کوئی بھی ایسی کمپیوٹیشن ہے جو اس شکل میں پوری اترے:

  • وہ ان پٹ کے طور پر 32-بٹ ورڈز پڑھتا ہے۔
  • وہ آؤٹ پٹ کے طور پر زیادہ سے زیادہ آٹھ 32-بٹ ورڈز شائع کرتا ہے۔
  • وہ متعین (deterministic) ہے: یکساں ان پٹس ہمیشہ یکساں آؤٹ پٹس دیتے ہیں۔
  • وہ اپنے درجے کے بجٹ کے اندر رک جاتا ہے (حصہ 7)۔

ان حدود کے اندر آپ کچھ بھی لکھ سکتے ہیں۔ آپ کو لوپس، ریکرژن، اسٹرکٹس، ارے، 64-بٹ حساب، کریٹس، اور اگر آپ ایلوکیٹر لائیں تو ہیپ بھی ملتا ہے۔ البتہ مشین خالص RV32IM ہے، اور اس کے نیچے کوئی آپریٹنگ سسٹم نہیں:

آپ کے پاس ہےآپ کے پاس نہیں ہے
RV32IM انٹیجر ہدایات، MUL/DIV/REM سمیتفلوٹنگ پوائنٹ ہارڈویئر (Rust کے سافٹ ویئر فلوٹس کام کرتے ہیں، مگر آہستہ)
ڈیفالٹ لنکر اسکرپٹ میں 0x1000 سے 1 MiB تک RAM، 64 KiB اسٹیک کے ساتھہیپ، جب تک آپ ایلوکیٹر فراہم نہ کریں (حصہ 6)
سسٹم کالز کے طور پر Poseidon2، Keccak-f اور SHA-256فائلیں، نیٹ ورک، گھڑیاں، رینڈم پن، تھریڈز، std، libc
no_std Rust کریٹس اور خود کفیل Cایسی اسٹیٹ جو ایک کال سے اگلی کال تک باقی رہے
آٹھ عوامی آؤٹ پٹ ورڈزپروگرام کے اندر سے RAND یا ٹوکن منتقل کرنے کا کوئی طریقہ

پروگرام ڈیزائن کرتے وقت آخری قطار سب سے اہم ہے۔ آؤٹ پٹس ڈیٹا ہیں، اثرات نہیں۔ ایک کال یہ ثابت کرتی ہے کہ ایک مخصوص ڈائجسٹ والا پروگرام کچھ نجی ان پٹس پر چلا اور اس نے آٹھ ورڈز شائع کیے۔ چین ثبوت کی تصدیق کرتی ہے اور آٹھ ورڈز ایک رسید میں محفوظ کرتی ہے۔ یہ کوئی بیلنس نہیں بدلتی اور کچھ متحرک نہیں کرتی۔ ان ورڈز کا مطلب پروگرام، اس کے کال کنندہ، اور رسید پڑھنے والے کے درمیان طے ہوتا ہے۔ پروگرام ان چیزوں کی فہرست دیتا ہے جن کی چین ابھی معاونت نہیں کرتی۔

2. پہلے انٹرفیس ڈیزائن کریں

پروگرام کے تین چینل ہوتے ہیں۔ کوئی بھی کوڈ لکھنے سے پہلے طے کریں کہ ہر ایک پر کیا جائے گا۔

چینلکب مقرر ہوتا ہےکس سے پڑھا جاتا ہےکون دیکھتا ہےچین 20 پر حد
نجی ان پٹہر کال پرread_input(i) / rand_read_input(i)صرف کال کنندہ، اور ہر وہ شخص جسے وہ کال کی کلید دے16,071 ورڈز (سربمہر ان پٹ لفافے کی حد)
عوامی سیگمنٹایک بار، ڈیپلائے پرread_public(i) / rand_read_public(i)ہر کوئی؛ پروگرام id اسے پابند کرتا ہے32,768 ورڈز
آؤٹ پٹسرن کے ذریعےwrite_output(slot, w) / rand_write_outputہر کوئی، رسید میں8 ورڈز

نجی ان پٹس بذاتِ خود دنیا کے بارے میں کچھ ثابت نہیں کرتے

نجی ان پٹ نمک آمیز کمٹمنٹ H_IN کے ذریعے ثبوت سے بندھا ہوتا ہے، اس لیے ثبوت سازی کے بعد وہ بدل نہیں سکتا۔ لیکن پروگرام کے پاس یہ جاننے کا کوئی طریقہ نہیں کہ ان پٹ سچا ہے۔ کال کنندہ کوئی بھی عدد درج کر سکتا ہے۔ نجی ان پٹ کو بامعنی بنانے کے لیے، پروگرام کے اندر اسے کسی عوامی چیز سے جوڑیں:

  • پہلے سے شائع شدہ ایک کمٹمنٹ (عوامی سیگمنٹ میں، یا تصدیق کنندہ کو الگ ذریعے سے دی گئی) جسے نجی ان پٹ کو کھولنا ہو، جیسا کہ ذیل کی نیلامی میں ہے؛
  • ایک Merkle روٹ جس کے تحت ایک نجی لیف کو ثابت کرنا ہو (اس کے لیے Poseidon2 سسٹم کال ہے)؛
  • ایک ڈائجسٹ جو تصدیق کنندہ کے پاس پہلے سے موجود ہو، جسے پروگرام دوبارہ حساب کر کے آؤٹ پٹ کے طور پر شائع کرتا ہے۔

خفیہ پروگراموں کے لیے یہی مرکزی ڈیزائن مہارت ہے۔ ثبوت تصدیق کنندہ کو بتاتا ہے کہ کیا حساب کیا گیا۔ عوامی ڈیٹا سے بندش اسے بتاتی ہے کہ کس چیز پر۔

ڈیٹا کو ورڈز میں انکوڈ کرنا

ہر چیز u32 کی صورت میں منتقل ہوتی ہے:

ڈیٹاانکوڈنگ
بائٹ اسٹرنگایک لمبائی ورڈ، پھر بائٹس فی ورڈ چار، little-endian، آخری ورڈ صفر سے پُر (وہ شکل جو والٹ ELF عوامی ان پٹ کے لیے استعمال کرتا ہے)
u64 یا رقمدو ورڈز، پہلے نچلا پھر اونچا
دستخط شدہ انٹیجرایک ورڈ میں two’s complement
بڑا نتیجہاس کا Poseidon2 ڈائجسٹ: 8 ورڈز، جو تمام آؤٹ پٹس بھر دیتا ہے؛ یا چند خلاصہ ورڈز اور ایک مختصر کمٹمنٹ شائع کریں

کمانڈ لائن ٹولز ان پٹ ورڈز اعشاری صورت میں لیتے ہیں: rand-guest run --input اور rand call --input میں 0x… قبول نہیں ہوتا۔ rand program deploy --public کے لیے عوامی سیگمنٹ فائل اعشاری اور 0x ہیکس دونوں قبول کرتی ہے۔

آؤٹ پٹس کی شکل طے کریں

  • سلاٹ 0 اسٹیٹس ورڈ ہے۔ ہر نتیجے کو ایک عدد دیں، ناکامیوں سمیت: ایسا کھولنا جو مطابقت نہ رکھے، بہت زیادہ آئٹمز، حد سے باہر ان پٹ۔ ناکام جانچ کو بھی ایک اسٹیٹس کے ساتھ صاف طور پر رکنا چاہیے، تاکہ کال کنندہ کو ایسا ثبوت ملے جو بتائے کہ کیا غلط ہوا۔
  • ہر سلاٹ زیادہ سے زیادہ ایک بار لکھا جاتا ہے۔ اسی سلاٹ پر دوسری بار لکھنا ٹریپ ہے (ایمولیٹر میں DoubleWrite(0))، اور جس رن میں ٹریپ ہو اس کا ثبوت نہیں بنایا جا سکتا۔ جو سلاٹ آپ کبھی نہیں لکھتے وہ صفر پڑھے جاتے ہیں۔
  • Rust میں سلاٹ 7 خالی رکھیں۔ panic ہونے والا Rust گیسٹ سلاٹ 7 میں 0xdeadbeef (3,735,928,559) لکھ کر رک جاتا ہے۔ panic سے پہلے لکھے گئے آؤٹ پٹس اپنی جگہ رہتے ہیں، اس لیے پڑھنے والے کو باقیوں پر یقین کرنے سے پہلے سلاٹ 7 جانچنا چاہیے (حصہ 6)۔
  • دیے گئے ان پٹس سے آگے پڑھنا ٹریپ ہے (ایمولیٹر میں InputIndex(5))۔ پہلے تعداد پڑھیں اور اسے جانچیں۔

3. حل شدہ مثال: مہر بند بولی کی نیلامی

ایک فروخت کنندہ ہاری ہوئی بولیاں شائع کیے بغیر دوسری قیمت والی نیلامی چلانا چاہتا ہے:

  1. ہر بولی دہندہ ایک بولی اور ایک رینڈم 128-بٹ سالٹ چنتا ہے، اور فروخت کنندہ کو ایک کمٹمنٹ دیتا ہے: ("BID1", bidder index, bid, salt) پر Poseidon2۔
  2. فروخت کنندہ نیلامی کا پروگرام ڈیپلائے کرتا ہے، جس کا عوامی سیگمنٹ کم از کم قیمت اور ہر کمٹمنٹ ہوتا ہے۔ اب پروگرام id اسی نیلامی کی نشاندہی کرتا ہے اور کسی اور چیز کی نہیں۔
  3. ہر بولی دہندہ اپنی بولی اور سالٹ نجی طور پر فروخت کنندہ کو بھیجتا ہے۔
  4. فروخت کنندہ ہر بولی اور سالٹ کو نجی ان پٹ کے طور پر دے کر پروگرام کو کال کرتا ہے۔ پروگرام ہر کھولنے کو اس کی کمٹمنٹ کے مقابل جانچتا ہے، فاتح چنتا ہے، اور [status, winner, price, bidder count] شائع کرتا ہے۔

چین کو معلوم ہوتا ہے کہ کون جیتا اور کس قیمت پر۔ وہ ہاری ہوئی بولیاں کبھی نہیں دیکھتی۔ اگر فروخت کنندہ کوئی بولی بدلے تو اسے نتیجے کے بجائے BAD_OPENING ملتا ہے، کیونکہ کمٹمنٹس پروگرام id میں طے شدہ ہیں۔

سب کچھ circuits کے checkout کے اندر رہتا ہے، کیونکہ rand-guest build گیسٹ کی ڈائریکٹری سے اوپر کی طرف دیکھ کر guest-sdk تلاش کرتا ہے:

circuits/
  guest-sdk/
  rand-guest/
  my-guests/
    commit/          a Rust guest that computes one commitment
    auction-core/    the auction rule as a plain no_std library, tested on the host
    auction/         the Rust guest
    auction-c/       the same guest in C
    auction-c-test/  a host test for the C rule, outside the guest's directory

ہیش کیلکولیٹر کے طور پر ایمولیٹر

بولی دہندگان کو Goldilocks فیلڈ پر Poseidon2 درکار ہے، بالکل اسی طرح جیسے مشین اس کا حساب کرتی ہے۔ سب سے سادہ درست ٹول ایک چھوٹا سا گیسٹ ہے جو اس کا حساب کرے اور ایمولیٹر پر چلایا جائے۔ ڈائجسٹ آٹھ ورڈز کا ہوتا ہے، جو ٹھیک آؤٹ پٹ سلاٹس کی تعداد ہے۔

my-guests/commit/Cargo.toml:

[package]
name = "commit"
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]

my-guests/commit/src/main.rs:

#![no_std]
#![no_main]

use guest_sdk::{halt, poseidon2, read_input, write_output};

/// Inputs: a bidder's index, bid and four salt words.
/// Outputs: the 8-word commitment Poseidon2("BID1", index, bid, salt[0..4]).
#[no_mangle]
pub extern "C" fn main() -> ! {
    let mut buf = [0u32; 8];
    buf[0] = 0x4249_4431; // "BID1"
    for i in 0..6 {
        buf[1 + i] = read_input(i as u32);
    }
    unsafe { poseidon2(buf.as_mut_ptr(), 7) };
    for (slot, word) in buf.iter().enumerate() {
        write_output(slot as u32, *word);
    }
    halt();
}

poseidon2(ptr, n) اپنی جگہ پر n ورڈز کو ہیش کرتا ہے اور 8-ورڈ ڈائجسٹ ptr..ptr+8 پر لکھتا ہے، اس لیے بفر میں کم از کم آٹھ ورڈز کی گنجائش ہونی چاہیے اگرچہ صرف سات ہیش کیے جاتے ہیں۔ "BID1" ٹیگ ان ڈائجسٹس کو Poseidon2 کے کسی بھی دوسرے استعمال سے الگ رکھتا ہے۔

aeacf31 پر poseidon2 ایک unsafe fn ہے، کیونکہ یہ خام پوائنٹر لیتا ہے، اس لیے کال ایک unsafe بلاک میں ہے۔ 7ef3220 پر، جہاں ذیل کے آؤٹ پٹس تیار ہوئے، یہ ایک محفوظ فنکشن تھا اور یہی سطر بلاک کے بغیر لکھی گئی تھی۔ اسپنج پیڈنگ نہیں کرتا: [a] اور [a, 0] کا ہیش یکساں ہوتا ہے۔ یہاں اس سے کوئی نقصان نہیں، کیونکہ ہر پیغام سات ورڈز کا ہے اور ایک ٹیگ سے شروع ہوتا ہے۔ ایسے پیغام کے لیے جس کی لمبائی بدل سکتی ہو، aeacf31 پر SDK میں poseidon2_len(buf, n) ہے، جو لمبائی کو پابند کرتا ہے (سسٹم کال 7)، لیکن اس کمٹ پر rand-guest check سسٹم کال 7 کو مسترد کرتا ہے، اس لیے ایسا گیسٹ ابھی بلڈ نہیں ہوتا (zkp-circuits issue #1)۔ اس کے بجائے لمبائی کو پیغام میں شامل کریں۔

$ rand-guest build my-guests/commit --out commit.bin
76 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote commit.bin and its .sha256 (76 words, hc 41007891b5c393296fdd76539c93d0b06678bc8a0bd52cfc3b6886dcf0381343, program id 11dabdf740989d43599b296c43342bf3ce890e980f5cc558d15ba8ca9e8e9438)

$ rand-guest run commit.bin --input 0 --input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514
out[0] = 3502385358
out[1] = 1187922325
out[2] = 2500412136
out[3] = 2452821097
out[4] = 2045657999
out[5] = 1866863173
out[6] = 678782893
out[7] = 3524569252
cycles 77
tier 10

یہاں اور آگے بلڈ آؤٹ پٹس میں cargo کی سطریں شامل نہیں کی گئیں۔ cargo جو unaligned-scalar-mem انتباہ دکھاتا ہے وہ متوقع ہے (Rust پروگرام ڈیپلائے کریں)۔

چار بولی دہندگان، ان بولیوں اور سالٹس کے ساتھ:

بولی دہندہبولیسالٹ (چار ورڈ)
04002654435769 2135587861 4087136352 1559071514
1250461845907 3432918353 3864292196 2246822507
29003266489909 668265263 374761393 3550635116
33004252018373 3042594569 1759714724 625341585

ہر بولی دہندہ کے لیے ایک بار commit.bin چلانے اور ترتیب وار محفوظ قیمت (100)، تعداد (4) اور چاروں ڈائجسٹ لکھنے سے عوامی حصہ، یعنی auction-public.txt، حاصل ہوتا ہے جس میں 34 ورڈ ہیں:

100 4 3502385358 1187922325 2500412136 2452821097 2045657999 1866863173 678782893 3524569252 3413403069 1515911705 3633767467 1218237979 474331206 896634170 2889783686 541804293 3111308722 99534202 1917186958 1017842926 3082732585 4215265588 467906960 2929273893 588931287 1295911076 647129560 1384102451 625140897 4060943258 2970944086 698558155

حقیقی نیلامی میں ہر بولی دہندہ commit.bin اپنی مشین پر چلاتا ہے، اور سالٹ کسی حقیقی بے ترتیب ماخذ سے آتا ہے۔

4. Rust میں پروگرام

قاعدہ، ایک ایسی لائبریری کی صورت میں جسے آپ ٹیسٹ کر سکیں

جس منطق کو syscalls کی ضرورت نہیں، اسے اس کے اپنے no_std کریٹ میں رکھیں۔ guest-sdk، riscv32 کے سوا کسی ٹارگٹ کے لیے بلڈ ہونے سے انکار کرتا ہے، اس لیے guest کریٹ آپ کے لیپ ٹاپ پر cargo test نہیں چلا سکتا۔ ایسا لائبریری کریٹ جو guest-sdk پر منحصر نہ ہو، چلا سکتا ہے۔

my-guests/auction-core/Cargo.toml:

[package]
name = "auction-core"
version = "0.1.0"
edition = "2021"

[workspace]

my-guests/auction-core/src/lib.rs:

//! The auction's rule, with no syscalls, so it builds and tests on the host.
#![no_std]

#[derive(Debug, PartialEq, Eq)]
pub enum Outcome {
    /// The highest bidder wins and pays the second-highest bid, or the reserve if higher.
    Sold { winner: u32, price: u32 },
    /// No bid reached the reserve.
    NoSale,
}

/// A second-price (Vickrey) auction. Ties go to the lower index.
pub fn settle(reserve: u32, bids: &[u32]) -> Outcome {
    let mut best: Option<(u32, u32)> = None; // (index, bid)
    let mut second = 0u32;
    for (i, &bid) in bids.iter().enumerate() {
        match best {
            Some((_, top)) if bid <= top => second = second.max(bid),
            Some((_, top)) => {
                second = top;
                best = Some((i as u32, bid));
            }
            None => best = Some((i as u32, bid)),
        }
    }
    match best {
        Some((winner, top)) if top >= reserve => Outcome::Sold { winner, price: second.max(reserve) },
        _ => Outcome::NoSale,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn highest_bid_pays_the_second() {
        assert_eq!(settle(100, &[400, 250, 900, 300]), Outcome::Sold { winner: 2, price: 400 });
    }

    #[test]
    fn the_reserve_sets_a_floor() {
        assert_eq!(settle(500, &[400, 250, 900, 300]), Outcome::Sold { winner: 2, price: 500 });
        assert_eq!(settle(1000, &[400, 250, 900, 300]), Outcome::NoSale);
    }

    #[test]
    fn a_tie_goes_to_the_lower_index() {
        assert_eq!(settle(0, &[700, 700]), Outcome::Sold { winner: 0, price: 700 });
    }

    #[test]
    fn no_bids_is_no_sale() {
        assert_eq!(settle(0, &[]), Outcome::NoSale);
    }
}
$ cd my-guests/auction-core && cargo +1.98.1 test
running 4 tests
test tests::a_tie_goes_to_the_lower_index ... ok
test tests::highest_bid_pays_the_second ... ok
test tests::no_bids_is_no_sale ... ok
test tests::the_reserve_sets_a_floor ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Guest

my-guests/auction/Cargo.toml دونوں کریٹس پر path کے ذریعے منحصر ہے:

[package]
name = "auction"
version = "0.1.0"
edition = "2021"

[dependencies]
guest-sdk = { path = "../../guest-sdk" }
auction-core = { path = "../auction-core" }

[profile.release]
opt-level = 3
lto = true
panic = "abort"
codegen-units = 1

[workspace]

my-guests/auction/src/main.rs:

#![no_std]
#![no_main]

use auction_core::{settle, Outcome};
use guest_sdk::{halt, poseidon2, read_input, read_public, write_output};

const MAX_BIDDERS: usize = 64;
const TAG: u32 = 0x4249_4431; // "BID1", the same tag the commit guest uses

// Output slot 0: what happened.
const SOLD: u32 = 1;
const NO_SALE: u32 = 2;
const BAD_OPENING: u32 = 3;
const TOO_MANY: u32 = 4;

/// Public (fixed at deploy): reserve, n, then n 8-word commitments.
/// Private (per call): for each bidder, the bid and four salt words.
/// Outputs: [status, winner, price, n, 0, 0, 0, 0]; a panic sets slot 7 to 0xdeadbeef.
#[no_mangle]
pub extern "C" fn main() -> ! {
    let reserve = read_public(0);
    let n = read_public(1) as usize;
    if n > MAX_BIDDERS {
        finish(TOO_MANY, 0, 0, n as u32);
    }

    let mut bids = [0u32; MAX_BIDDERS];
    for i in 0..n {
        let base = (5 * i) as u32;
        let bid = read_input(base);
        let mut buf = [TAG, i as u32, bid, read_input(base + 1), read_input(base + 2),
                       read_input(base + 3), read_input(base + 4), 0];
        unsafe { poseidon2(buf.as_mut_ptr(), 7) };
        let committed = 2 + 8 * i as u32;
        for w in 0..8 {
            if buf[w] != read_public(committed + w as u32) {
                finish(BAD_OPENING, i as u32, 0, n as u32);
            }
        }
        bids[i] = bid;
    }

    match settle(reserve, &bids[..n]) {
        Outcome::Sold { winner, price } => finish(SOLD, winner, price, n as u32),
        Outcome::NoSale => finish(NO_SALE, 0, 0, n as u32),
    }
}

fn finish(status: u32, winner: u32, price: u32, n: u32) -> ! {
    write_output(0, status);
    write_output(1, winner);
    write_output(2, price);
    write_output(3, n);
    halt();
}

اختتام کا ہر راستہ finish سے گزرتا ہے، جو ہر سلاٹ کو ایک بار لکھتا ہے اور رک جاتا ہے۔ لوپ کی حد عوامی حصے سے آتی ہے، جو deploy کے وقت طے ہو جاتا ہے۔ چنانچہ کوئی کالر پروگرام کو کمٹ کی گئی بولیوں سے کم بولیاں پڑھنے پر مجبور نہیں کر سکتا۔

بلڈ کریں اور چلائیں

$ rand-guest build my-guests/auction --out auction.bin
258 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote auction.bin and its .sha256 (258 words, hc aa04e4c451e986f086e9fcff53b213eb52e593317c32a979d5e24991164f050d, program id 148ab0c4b9ad5115caab4a2ab433e3e13b5e8fbecbd9d8a9d6ba20c64f942c73)

rand-guest run عوامی حصے کو بار بار دیے گئے --public فلیگز کی صورت میں لیتا ہے۔ شیل میں:

PUB=$(for w in $(cat auction-public.txt); do printf -- "--public %s " "$w"; done)
BIDS="--input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514
      --input 250 --input 461845907 --input 3432918353 --input 3864292196 --input 2246822507
      --input 900 --input 3266489909 --input 668265263 --input 374761393 --input 3550635116
      --input 300 --input 4252018373 --input 3042594569 --input 1759714724 --input 625341585"
rand-guest run auction.bin $PUB $BIDS --tier 10
out[0] = 1
out[1] = 2
out[2] = 400
out[3] = 4
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 641
tier 10
tier 10: fits (cycles 721 of 1023, Poseidon2 permutations 88 of 128)

فروخت ہو گیا (1): بولی دہندہ 2 کو 400 پر، جو دوسری سب سے بڑی بولی ہے، اور چار بولی دہندگان شمار ہوئے۔ دو ایسے رن جنہیں ناکام ہونا چاہیے:

تبدیلیآؤٹ پٹ 0–3مطلب
بولی دہندہ 1 کی بولی 250 کی جگہ 2603 1 0 0بولی دہندہ 1 پر BAD_OPENING؛ اس کے سوا کچھ شائع نہیں ہوتا
محفوظ قیمت 100 کے بجائے 1,000 (مختلف عوامی حصہ، لہٰذا مختلف program id)2 0 0 4NO_SALE: کوئی بولی محفوظ قیمت تک نہیں پہنچی

5. یہی پروگرام C میں

C guest کی ڈائریکٹری میں .c فائلیں اور آپ کے اپنے ہیڈرز ہوتے ہیں۔ rand-guest build --lang c، guest.h، _start اور ایک مختصر رن ٹائم فراہم کرتا ہے (RISC-V پروگرام deploy کریں)۔ یہ ڈائریکٹری کی ہر .c فائل کو کمپائل کرتا ہے، اس لیے ہوسٹ کی ٹیسٹ فائلیں کہیں اور رکھیں۔

my-guests/auction-c/auction.h:

#ifndef AUCTION_H
#define AUCTION_H
#include <stdint.h>

/* Outcome of a second-price auction. sold is 0 when no bid reached the reserve. */
struct outcome {
    uint32_t sold;
    uint32_t winner;
    uint32_t price;
};

struct outcome settle(uint32_t reserve, const uint32_t *bids, uint32_t n);
#endif

my-guests/auction-c/settle.c:

#include "auction.h"

/* A second-price (Vickrey) auction. Ties go to the lower index. */
struct outcome settle(uint32_t reserve, const uint32_t *bids, uint32_t n) {
    struct outcome o = {0, 0, 0};
    if (n == 0) {
        return o;
    }
    uint32_t winner = 0, top = bids[0], second = 0;
    for (uint32_t i = 1; i < n; i++) {
        if (bids[i] > top) {
            second = top;
            top = bids[i];
            winner = i;
        } else if (bids[i] > second) {
            second = bids[i];
        }
    }
    if (top >= reserve) {
        o.sold = 1;
        o.winner = winner;
        o.price = second > reserve ? second : reserve;
    }
    return o;
}

my-guests/auction-c/main.c:

#include "guest.h"
#include "auction.h"

#define MAX_BIDDERS 64
#define TAG 0x42494431u /* "BID1", the same tag the commit guest uses */

enum { SOLD = 1, NO_SALE = 2, BAD_OPENING = 3, TOO_MANY = 4 };

static uint32_t bids[MAX_BIDDERS];

static void finish(uint32_t status, uint32_t winner, uint32_t price, uint32_t n) {
    rand_write_output(0, status);
    rand_write_output(1, winner);
    rand_write_output(2, price);
    rand_write_output(3, n);
    rand_halt();
}

/* Public (fixed at deploy): reserve, n, then n 8-word commitments.
 * Private (per call): for each bidder, the bid and four salt words.
 * Outputs: [status, winner, price, n, 0, 0, 0, 0]. */
void main(void) {
    uint32_t reserve = rand_read_public(0);
    uint32_t n = rand_read_public(1);
    if (n > MAX_BIDDERS) {
        finish(TOO_MANY, 0, 0, n);
    }
    for (uint32_t i = 0; i < n; i++) {
        uint32_t base = 5 * i;
        uint32_t buf[8] = {TAG, i, rand_read_input(base), rand_read_input(base + 1),
                           rand_read_input(base + 2), rand_read_input(base + 3),
                           rand_read_input(base + 4), 0};
        bids[i] = buf[2];
        rand_poseidon2(buf, 7);
        for (uint32_t w = 0; w < 8; w++) {
            if (buf[w] != rand_read_public(2 + 8 * i + w)) {
                finish(BAD_OPENING, i, 0, n);
            }
        }
    }
    struct outcome o = settle(reserve, bids, n);
    if (o.sold) {
        finish(SOLD, o.winner, o.price, n);
    }
    finish(NO_SALE, 0, 0, n);
}

bids صفر سے ابتدا کیا گیا static ہے، اس لیے یہ .bss میں جاتا ہے۔ اس پر امیج کا کوئی ورڈ خرچ نہیں ہوتا، کیونکہ RAM پہلے ہی صفر پڑھتی ہے۔

قاعدے کو ہوسٹ پر ٹیسٹ کریں

settle.c کوئی syscall استعمال نہیں کرتا، اس لیے آپ کا عام کمپائلر اسے بلڈ کر سکتا ہے۔ my-guests/auction-c-test/test_settle.c:

#include <assert.h>
#include <stdio.h>
#include "../auction-c/auction.h"

int main(void) {
    uint32_t bids[] = {400, 250, 900, 300};
    struct outcome o = settle(100, bids, 4);
    assert(o.sold && o.winner == 2 && o.price == 400);
    o = settle(500, bids, 4);
    assert(o.sold && o.price == 500);
    o = settle(1000, bids, 4);
    assert(!o.sold);
    uint32_t tie[] = {700, 700};
    o = settle(0, tie, 2);
    assert(o.winner == 0 && o.price == 700);
    puts("settle: 4 checks passed");
    return 0;
}
$ cc -Wall -o test_settle my-guests/auction-c-test/test_settle.c my-guests/auction-c/settle.c && ./test_settle
settle: 4 checks passed

بلڈ کریں اور چلائیں

$ rand-guest build --lang c my-guests/auction-c --out auction-c.bin
200 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote auction-c.bin and its .sha256 (200 words, hc 68605aa8d28a863a4a52a25de07ba159327a0fd0145a9dcd5e5aa6e79ab6bea0, program id 5667c343b8848805a908c9009d426164f39d2f927b2c780529a054f0d66aafc1)

$ rand-guest run auction-c.bin $PUB $BIDS --tier 10
out[0] = 1
out[1] = 2
out[2] = 400
out[3] = 4
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 576
tier 10
tier 10: fits (cycles 641 of 1023, Poseidon2 permutations 73 of 128)

آؤٹ پٹ Rust guest کے آؤٹ پٹ سے مطابقت رکھتے ہیں۔ چار بولی دہندگان کے ساتھ C امیج 58 ورڈ چھوٹا اور 65 سائیکل تیز ہے؛ حصہ 7 دکھاتا ہے کہ 64 بولی دہندگان پر توازن پلٹ جاتا ہے۔ یہ دونوں الگ پروگرام ہیں جن کے hc اور program id مختلف ہیں۔ ان میں سے وہی deploy کریں جس کا سورس آپ شائع کرتے ہیں۔

6. زبان سے متعلق نکات

Rust

کریٹ۔ #![no_std]، #![no_main]، اور ایک #[no_mangle] pub extern "C" fn main() -> ! جو halt() پر ختم ہو۔ ریلیز پروفائل panic = "abort" مقرر کرتا ہے۔ ایک خالی [workspace] ٹیبل کریٹ کو کسی بھی بالائی workspace سے باہر رکھتا ہے۔ .cargo/config.toml اور اپنے کمپائلر فلیگز شامل نہ کریں: rand-guest build پہلی چیز کو مسترد کرتا ہے اور دوسری تمام خود مقرر کرتا ہے (Rust پروگرام deploy کریں)۔

پینک۔ guest-sdk کا پینک ہینڈلر سلاٹ 7 میں 0xdeadbeef لکھ کر رک جاتا ہے۔ یہ رن پھر بھی ایک درست اور قابلِ ثبوت رن ہے۔ یہ پروگرام سلاٹ 0 اور 1 لکھنے کے بعد چھ عناصر والے Vec کا اشاریہ 999 پڑھتا ہے:

$ rand-guest run heap.bin --input 6 --input 1 --input 2 --input 3 --input 4 --input 5 --input 6
out[0] = 4
out[1] = 1080033280
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 3735928559
cycles 1326
tier 12

سلاٹ 0 اور 1 میں حقیقی قدریں ہیں، اور سلاٹ 7 بتاتا ہے کہ رن پینک ہوا۔ جو کالر ایسا رن جمع کراتا ہے اسے چین پر رسید مل جاتی ہے۔ رسیدیں پڑھنے والے کو لازماً سلاٹ 7 = 3,735,928,559 کو مسترد کرنا ہوگا، یا پروگرام کو کبھی پینک نہیں ہونا چاہیے۔ ہر راستے پر get()، جانچ شدہ حساب اور واضح اسٹیٹس کوڈز کو ترجیح دیں۔

ہیپ۔ بطورِ طے شدہ کوئی ایلوکیٹر نہیں ہوتا۔ جب آپ ایک فراہم کر دیں تو alloc کام کرتا ہے۔ ایک مقررہ arena پر bump ایلوکیٹر اس پروگرام کے لیے موزوں ہے جو ایک بار چل کر ختم ہو جاتا ہے:

extern crate alloc;

use alloc::vec::Vec;
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;

/// A bump allocator over a fixed arena: allocation moves a pointer forward, free does nothing.
/// One run is one short-lived process, so nothing needs to be reclaimed.
struct Bump {
    arena: UnsafeCell<[u8; ARENA]>,
    next: UnsafeCell<usize>,
}
const ARENA: usize = 256 * 1024;
unsafe impl Sync for Bump {}

unsafe impl GlobalAlloc for Bump {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let next = &mut *self.next.get();
        let start = (*next + layout.align() - 1) & !(layout.align() - 1);
        let end = start + layout.size();
        if end > ARENA {
            return core::ptr::null_mut();
        }
        *next = end;
        (self.arena.get() as *mut u8).add(start)
    }
    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}

#[global_allocator]
static HEAP: Bump = Bump { arena: UnsafeCell::new([0; ARENA]), next: UnsafeCell::new(0) };

arena صفر شدہ ہے، اس لیے یہ .bss میں رہتا ہے اور امیج میں کوئی ورڈ نہیں بڑھاتا۔ طے شدہ لنکر اسکرپٹ 1 MiB RAM دیتا ہے، اور arena، آپ کے statics اور 64 KiB کا اسٹیک سب اسی میں سمانے چاہییں۔ اس ایلوکیٹر کے ساتھ Vec، collect() اور iter().sum() سب بلڈ ہوتے ہیں اور check سے گزر جاتے ہیں۔ البتہ alloc اور اس کے انحصارات کوڈ بڑھاتے ہیں: یہ ٹیسٹ پروگرام 1,341 ورڈ کا ہے، جبکہ نیلامی کا پروگرام 258 کا۔ حصہ 7 وضاحت کرتا ہے کہ پروگرام کا حجم ثبوت کے بجٹ پر کیوں بھاری پڑتا ہے۔

فلوٹنگ پوائنٹ۔ ٹارگٹ میں فلوٹ ہدایات نہیں ہیں، اور Rust، f32/f64 کو سافٹ ویئر روٹینز میں بدل دیتا ہے۔ یہ کام کرتی ہیں: 9، 2، 7، 4 اور 5 کا اوسط f64 میں نکالا گیا تو نتیجہ 5.4f32 کے بٹس کی صورت میں آیا۔ ان میں سے ہر ایک پر بہت سے سائیکل خرچ ہوتے ہیں۔ جہاں ممکن ہو، صحیح اعداد یا fixed point استعمال کریں۔

کریٹس۔ crates.io کے no_std کریٹس کسی بھی انحصار کی طرح بلڈ ہوتے ہیں۔ crc کریٹ نے ایمولیٹر پر hello, rand کا CRC-32 چلا کر 2124296908 چھاپا، وہی قدر جو Python کا zlib.crc32 دیتا ہے۔ جن کریٹس کو std درکار ہے وہ اس ٹارگٹ کے لیے بلڈ نہیں ہوتے۔ rand-guest build، --locked نہیں دیتا، اس لیے اپنی Cargo.lock کمٹ کریں: اس کے بغیر مہینوں بعد کا ری بلڈ کوئی نیا کریٹ ورژن لے کر مختلف hc پیدا کر سکتا ہے۔

unimp کا پھندا: لائبریری کوڈ سے آنے والی Csr نشاندہی۔ core کا کچھ کوڈ اس راستے پر جسے LLVM ناقابلِ رسائی سمجھتا ہے، ایک unimp ورڈ (0xc0001073، csrrw x0, cycle, x0) میں کمپائل ہوتا ہے۔ مشین میں کوئی CSR نہیں، اس لیے check امیج کو مسترد کر دیتا ہے:

0x00002250  0xc0001073  Csr: CSR instruction: the machine has no CSRs
2613 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED
wrote heap.bin (the loader refuses it: no hc)

یہاں قصوروار ایک ایسے Vec پر slice::sort_unstable تھا جس کی لمبائی صرف رن ٹائم پر معلوم ہوتی ہے۔ مستحکم sort() نے بھی یہی کیا، جبکہ 16 عناصر والی مقررہ array پر sort_unstable گزر گیا۔ ایسی نشاندہی کا ماخذ ڈھونڈنے کے لیے guest کی ELF کو ڈس اسمبل کریں اور check کے چھاپے ہوئے پتے کے آس پاس کا فنکشن دیکھیں:

llvm-objdump -d -C my-guests/heap/target/riscv32im-unknown-none-elf/release/heap | grep -B12 unimp

پھر اس کال کی جگہ اپنا کوڈ لکھیں (اس پروگرام کے لیے ایک مختصر insertion sort)۔ اسی وجہ سے guest-sdk کا اپنا halt() بھی noreturn وعدے کے بجائے loop {} پر ختم ہوتا ہے۔

C

  • داخلے کا نقطہ void main(void) ہے، اور یہ rand_halt() پر ختم ہوتا ہے۔ ساتھ آنے والا _start اسٹیک مقرر کر کے اسے کال کرتا ہے۔
  • کوئی libc نہیں ہے۔ نہ printf، نہ malloc، نہ <string.h>۔ رن ٹائم memcpy، memmove، memset اور memcmp فراہم کرتا ہے (clang، struct کی نقل اور بڑی ابتدائی قدروں کے لیے انہیں کال کرتا ہے) اور 64-bit تقسیم کے چار معاون فنکشن بھی۔ یہ سب بائٹ لوپس ہیں، اس لیے کثرت سے چلنے والے کوڈ میں نقل اور تقسیم ورڈز میں کریں۔ ہیپ کے لیے، Rust کی طرح، ایک static array پر bump ایلوکیٹر لکھیں۔
  • غیر متعین رویہ پکڑا نہیں جاتا۔ کوئی پینک ہینڈلر نہیں ہے۔ حدود سے باہر لکھائی خاموشی سے میموری خراب کر دیتی ہے، اور غیر ہم آہنگ load یا store ٹریپ کرتا ہے، جس کا مطلب ہے کہ رن ثابت نہیں کیا جا سکتا۔ اشاریوں کو اپنی حدود سے جانچیں، جیسے main.c، n کو MAX_BIDDERS سے جانچتا ہے۔
  • ڈائریکٹری کے قواعد۔ اپنی guest.h یا start.S شامل نہ کریں؛ build --lang c دونوں کو مسترد کرتا ہے۔ ٹیسٹ فائلیں guest ڈائریکٹری سے باہر رکھیں، کیونکہ اس میں موجود ہر .c فائل امیج میں کمپائل ہوتی ہے۔
  • مقررہ clang۔ rand-guest، 23.1.1 کے سوا کسی بھی clang کو مسترد کرتا ہے۔ RAND_GUEST_CLANG_UNPINNED=1 اسے رد کر دیتا ہے، اس انتباہ کے ساتھ کہ آپ کا hc کسی اور کے بلڈ سے مطابقت نہیں رکھے گا۔

7. کیا یہ ثابت ہو سکے گا؟ بجٹ

ثبوت ساز ہر رن کو ایک درجے t تک بھرتا ہے۔ ہر درجے کے دو آزاد بجٹ ہیں، اور rand-guest run --tier t دونوں رپورٹ کرتا ہے:

  • قطاریں: زیادہ سے زیادہ 2ᵗ − 1، جن میں چلائے گئے سائیکل اور ڈائجسٹ کی قطاریں شمار ہوتی ہیں؛
  • Poseidon2 permutations: زیادہ سے زیادہ 2ᵗ⁻³۔
درجہقطاریںPoseidon2 permutationsہیش ٹیبل کے بغیر گیس کی بالائی حدchain 20 پر کال
101,0231281,279قبول
124,0955125,119قبول
1416,3832,04820,479قبول
1665,5358,19281,919v0.5.6 سے مسترد
18، 202¹⁸ − 1، 2²⁰ − 12¹⁵، 2¹⁷327,679، 1,310,719v0.5.6 سے مسترد

فیس گیس اور بائٹس کے تابع ہے، درجے کے نہیں۔ chain 15 پر ایک کال یکساں 0.001 + call_fee(tier) ادا کرتی تھی: درجہ 10 پر 0.002 RAND، درجہ 12 پر 0.0021 اور درجہ 14 پر 0.0022۔ chain 18 سے ہر رن کی پیمائش ہوتی ہے۔ ایک عام قطار 1 گیس، Poseidon2 absorb کی قطار 3، Keccak permutation 192 اور SHA-256 compression 64 گیس کی ہے۔ ثبوت ایک اعلان کردہ گیس کی حد شائع کرتا ہے، سرکٹ یقینی بناتا ہے کہ رن کی گیس اس کے برابر یا اس سے کم ہو، اور اصل گیس نجی رہتی ہے۔ ہیش ٹیبل کے بغیر کوئی درجہ جس بالائی حد کا اعلان کر سکتا ہے وہ (2ᵗ − 1) + 2ᵗ⁻² ہے، یعنی اوپر والا کالم۔ فیس کی کم از کم حد یہ ہے

floor = BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉

جہاں BUNDLE_BASE 0.001 RAND ہے، بائٹس سے مراد کال کا ثبوت اور ان پٹ envelope ہیں، اور قیمتیں 100 اکائی فی گیس اور 800 اکائی فی KiB سے شروع ہوئیں (ایک اکائی 10⁻⁹ RAND ہے) اور ہر بلاک میں تھوڑی سی بدلتی ہیں۔ chain 20 پر یہ 10,000 اور 80,000 اکائیوں سے اوپر نہیں جا سکتیں، اور صرف کالز کے ثبوت اور envelope کی بائٹس ہی بائٹ کی قیمت کو حرکت دیتی ہیں؛ chains 18 اور 19 پر کوئی بالائی حد نہیں تھی۔ حصہ 9 دکھاتا ہے کہ والیٹ بطورِ طے شدہ کیا ادا کرتا ہے، اور مثالی پروگرام میں chain 18 پر چار ناپی گئی کالز درج ہیں، جن میں سے ہر ایک 0.0057 سے 0.0075 RAND کی تھی۔

درجہ 14 وہ سب سے اونچا درجہ ہے جو کوئی کال استعمال کر سکتی ہے۔ fullnode v0.5.6 (2026-09-25) سے، نوڈ درجہ 14 سے اوپر کے کسی بھی کال ثبوت کو CallTierTooHigh کے ساتھ مسترد کر دیتا ہے، اور یہ ویریفائر کلید بنانے سے پہلے ہوتا ہے۔ اسی اصول کے تحت ایسی کال پر مشتمل بلاک غیر معتبر ہو جاتا ہے۔ یہ حد 2–4 GB میموری والے ویلیڈیٹرز کو تحفظ دیتی ہے، کیونکہ درجہ 20 کی ویریفائر کلید بنانے میں کئی منٹ اور کئی گیگابائٹ لگتے ہیں۔ rand call ایسی کال کو ثبوت بنانے سے پہلے مسترد نہیں کرتا، البتہ chain 20 پر وہ پہلے درجہ ظاہر کر دیتا ہے۔ کال کرنے سے پہلے rand-guest run … --tier 14 چلائیں، اور اگر رن اس میں نہ سمائے تو پروگرام کی ساخت بدلیں۔ ڈیپلائی سے پہلے rand program deploy --input <word> … (یا بغیر ان پٹ والی کال کے لیے --check-call) آپ کی مطلوبہ کال کو ایمولیٹر میں چلاتا ہے، اور اگر وہ درجہ 14 یا اس سے نیچے نہ آئے تو ڈیپلائی سے انکار کر دیتا ہے۔

بجٹ کس چیز پر خرچ ہوتا ہے

غیر متوقع خرچ ڈائجسٹ کی قطاریں اور پرمیوٹیشنز ہیں۔ آپ کا کوڈ چلنے سے پہلے مشین پروگرام، نجی ان پٹس اور عوامی سیگمنٹ کو ہیش کر کے hc، H_IN اور H_PUB بناتی ہے۔ ان میں سے ہر ایک پر تقریباً ہر چار الفاظ کے لیے ایک پرمیوٹیشن اور ایک قطار خرچ ہوتی ہے۔ اس کے علاوہ آپ کی ہر POSEIDON2 کال پر ہیش کیے گئے ہر چار الفاظ کے لیے تقریباً ایک پرمیوٹیشن لگتی ہے۔ درجہ 10 پر نیلامی کے لیے:

مدالفاظپرمیوٹیشنز
پروگرام (hc)258تقریباً 65
نجی ان پٹس (H_IN)20تقریباً 5
عوامی سیگمنٹ (H_PUB)34تقریباً 9
چار کمٹمنٹ جانچیں، ہر ایک 7 الفاظ4 × 78
کل، جیسا کہ run بتاتا ہے128 میں سے 88

قطاریں: 641 سائیکل اور ڈائجسٹ کی قطاریں مل کر 1,023 میں سے 721 بنیں۔

chain 20 پر، chain 19 کی طرح، والیٹ کی اپنی گنتی rand-guest run کی گنتی سے کچھ زیادہ ہوتی ہے۔ وہاں کال ثبوت اپنی ٹرانزیکشن سے بندھا ہوتا ہے (chain 20 پر اس بندش میں genesis ہیش بھی شامل ہے، اور الفاظ اتنے ہی آٹھ رہتے ہیں)، اور یہ بندش عوامی سیگمنٹ کے آٹھ مزید الفاظ کی صورت میں ساتھ جاتی ہے: عوامی سیگمنٹ والے پروگرام کے لیے ڈائجسٹ کی دو مزید قطاریں اور دو مزید پرمیوٹیشنز، اور عوامی سیگمنٹ کے بغیر پروگرام کے لیے ہر ایک کی ایک مزید۔

اس سے پروگرام کا حجم بجٹ کا حصہ بن جاتا ہے۔ ہیپ کا آزمائشی پروگرام صرف 1,183 سائیکل چلا، مگر اس کے 1,341 الفاظ نے 340 پرمیوٹیشنز لیں اور اسے درجہ 12 تک پہنچا دیا۔ درجہ 14 کی 2,048 پرمیوٹیشنز کے ساتھ، تقریباً 8,000 الفاظ سے خاصا بڑا پروگرام کال ہی نہیں کیا جا سکتا، خواہ وہ کچھ بھی کرتا ہو۔ chain 20 پر عین حد ایسے پروگرام کے لیے 8,180 الفاظ ہے جس کا کوئی عوامی سیگمنٹ نہ ہو اور جس کی کال میں کوئی ان پٹ نہ ہو، اور ان دونوں میں سے کسی ایک کے ہونے پر حد کم ہو جاتی ہے۔ chain 20 پر پروگرام کی حد 65,535 الفاظ ہے، جیسی chain 15 پر تھی، مگر chains 19 اور 20 اس حد سے بڑے پروگرام کو ڈیپلائی کرنے سے انکار کر دیتی ہیں (ProgramUncallable)، جبکہ chain 15 ایسے پروگرام کی ڈیپلائی فیس لے لیتی تھی جسے کوئی کال نہیں کر سکتا تھا۔ یہی وجہ ہے کہ chain 13 کا ترجمہ شدہ ERC-20 (11,686 الفاظ، درجہ 16 پر کال کیا گیا) chain 20 پر نہ ڈیپلائی ہو سکتا ہے نہ کال۔ پروگرام چھوٹے رکھیں: Rust میں opt-level = "s"، C میں -Os (جو پہلے سے طے ہے)، کم سے کم انحصارات، اور جب تک ضرورت نہ ہو alloc نہیں۔

نیلامی خطی طور پر بڑھتی ہے: ہر اضافی بولی دہندہ 5 ان پٹ الفاظ، 8 عوامی الفاظ، جانچ کے لیے 2 پرمیوٹیشنز اور تقریباً سو سائیکل بڑھاتا ہے۔ 64 بولی دہندگان (بے ترتیب بولیاں، 320 ان پٹ الفاظ، 514 عوامی الفاظ) کے ساتھ دونوں بلڈز ایک ہی فاتح اور قیمت منتخب کرتی ہیں، اور دونوں کو درجہ 14 درکار ہے:

auction.bin    tier 14: fits (cycles 6557 of 16383, Poseidon2 permutations 403 of 2048)
auction-c.bin  tier 14: fits (cycles 8571 of 16383, Poseidon2 permutations 388 of 2048)

پرمیوٹیشنز درجہ 12 کی 512 میں سما جاتیں، مگر قطاریں اس کی 4,095 میں نہیں سماتیں۔ یہاں درجہ ہیشنگ نہیں بلکہ سائیکل طے کرتے ہیں۔ 64 بولی دہندگان پر Rust بلڈ زیادہ تیز ہے۔ جب لوپ غالب آ جائے تو اس کا opt-level = 3، C بلڈ کے مقررہ -Os سے آگے نکل جاتا ہے۔

Keccak اور SHA-256

keccak اور sha256_compress میں سے ہر ایک پر ایک قطار خرچ ہوتی ہے، مگر جو ثبوت ان میں سے کسی کو بھی استعمال کرے اس کے ساتھ ایک اضافی جدول ہوتی ہے۔ aeacf31 پر دونوں ایک array ریفرنس لیتے ہیں: [u32; 50] پر keccak(&mut state) اور [u32; 24] پر sha256_compress(&mut buf)، جبکہ 7ef3220 پر یہ خام پوائنٹر لیتے تھے۔ v0.5.6 سے ایک کال زیادہ سے زیادہ 128 Keccak-f پرمیوٹیشنز اور زیادہ سے زیادہ 128 SHA-256 کمپریشنز استعمال کر سکتی ہے۔ chain 20 پر ایک Keccak پرمیوٹیشن پر 192 گیس اور ایک SHA-256 کمپریشن پر 64 گیس بھی لگتی ہے۔

یہ جدول ثبوت کو بڑا کر دیتی ہے، اور chain 20 کال ثبوت کی حد 4,194,304 بائٹ رکھتی ہے (4 MiB، جیسا کہ chains 17 تا 19 پر؛ chains 13 تا 16 پر یہ حد 8 MiB تھی)۔ chain 18 پر Keccak جدول والی درجہ 12 کی کال 3.35 MB کی ناپی گئی، جو حد کا بیشتر حصہ ہے، اور SHA-256 جدول والی درجہ 14 کی کال 1.91 MB کی، جبکہ دونوں کے بغیر 1.50 MB تھی (مثالی پروگرام)۔ ہر KiB پر فی بائٹ قیمت وصول کی جاتی ہے۔ chain 15 پر صرف مفت 2 MiB + 18,432 سے زائد بائٹس پر، 1,000 یونٹ فی KiB کے حساب سے، فیس لی جاتی تھی (فیس)۔ جب تک کسی بیرونی ہیش سے مطابقت ضروری نہ ہو، Poseidon2 استعمال کریں۔

ثبوت بنانے کے لیے میموری اور وقت

ثبوت بنانے کا وقت اور میموری درجے کے ساتھ تیزی سے بڑھتے ہیں۔ درجہ 10 کی کال کا ثبوت لیپ ٹاپ پر چند سیکنڈ میں بن جاتا ہے۔ chain 20 پر ہر ڈیپلائی اور ہر کال کے ساتھ بنڈل کی جانب کے دو ثبوت بھی ہوتے ہیں: درجہ 10 کی خرچ اجازت (چند سیکنڈ) اور درجہ 14 کا بنڈل جو فیس ادا کرتا ہے (لیپ ٹاپ پر تقریباً دو منٹ)۔ chain 18 پر ناپی گئی چار کالوں میں سے ہر ایک نے تین سے ساڑھے پانچ منٹ لیے اور ان کی بلند ترین میموری 6.1 سے 6.6 GB رہی (مثالی پروگرام)۔ chain 15 پر بنڈل درجہ 14 کا ایک ہی ثبوت تھا جو تقریباً 100 s اور تقریباً 5.7 GB لیتا تھا۔ ناپے گئے اعداد و شمار ویلیڈیٹر اور ایگریگیٹر کا ہارڈویئر میں درج ہیں۔

8. ڈیپلائی

testnet، chain 20 ہے۔ ڈیپلائی کے لیے fullnode v0.6.8 یا بعد کا rand والیٹ، ایک کلید اور کچھ RAND درکار ہیں (تعارف؛ chain 20 کا فاسٹ، chain 19 کی طرح، صرف ان 16 والیٹس کو منٹ کرتا ہے جنہیں اس کا genesis اجازت فہرست میں رکھتا ہے)۔ v0.6.7 والیٹ chain 20 پر نہیں بھیج سکتا: chain 20 کا genesis binding_domain: 1 طے کرتا ہے، اس لیے ہر ثبوت کی بندش اور ہر دستخط شدہ پیغام میں genesis ہیش شامل ہوتا ہے، جو v0.6.7 والیٹ شامل نہیں کرتا۔ والیٹ کو sync کرنے، ڈیپلائی کرنے اور کال کرنے کے لیے عوامی endpoint https://rpc.randprotocol.org کافی ہے (سیٹ اپ)۔ نیلامی اپنے عوامی سیگمنٹ کے ساتھ ڈیپلائی ہوتی ہے:

rand fee deploy 258 --public-words 34                 # 0.001 + 0.0001 × (258 + 34) = 0.0302 RAND
rand program deploy auction.bin --public auction-public.txt

program deploy پروگرام id، hc، الفاظ کی تعداد، اور عوامی ان پٹ کے الفاظ کی تعداد اور ڈائجسٹ ظاہر کرتا ہے۔ کوئی بھی ثبوت بنانے سے پہلے وہ پروگرام کو چین کی حد کے مقابل اور عوامی ان پٹ کو max_program_public_words (chain 20 پر 32,768) کے مقابل جانچتا ہے۔ پھر وہ صفر قدر کی خود منتقلی کے ایک بنڈل کا ثبوت بناتا ہے جو فیس ادا کرتا ہے، اسے جمع کرتا ہے، اور commit کا انتظار کرتا ہے۔ chain 20 پر، chain 17 سے اب تک کی طرح، یہ دو ثبوت ہیں جن میں سے ہر ایک کی آؤٹ پٹ کی اپنی سطر ہوتی ہے: درجہ 10 کی خرچ اجازت (چند سیکنڈ، تقریباً 1.37 MB) اور درجہ 14 کا بنڈل (تقریباً دو منٹ، تقریباً 1.5 MB)۔ chain 15 پر یہ درجہ 14 کا ایک ثبوت تھا جو تقریباً 100 s لیتا اور تقریباً 1.42 MB کا ہوتا تھا۔ ڈیپلائی فیس میں گیس کا کوئی جز نہیں۔ نیلامی chains 18 تا 20 پر ڈیپلائی نہیں کی گئی؛ ایک گمنام ووٹ chain 18 پر عوامی سیگمنٹ کے ساتھ ایک ڈیپلائی کو اسی طرح دکھاتا ہے جیسے والیٹ نے اسے ظاہر کیا۔

--public کے ساتھ پروگرام id بدل جاتی ہے۔ rand-guest نے صرف کوڈ کے لیے 148ab0c4…2c73 ظاہر کیا۔ عوامی سیگمنٹ والا ڈیپلائی blake3("rand-program-2", base_pc ‖ len ‖ words ‖ len ‖ public) کے تحت محفوظ ہوتا ہے، جو ریزرو اور چاروں کمٹمنٹس کو بھی باندھ دیتا ہے۔ وہی id استعمال کریں جو program deploy ظاہر کرے۔ اسی کوڈ اور مختلف کمٹمنٹس والی دوسری نیلامی کی پروگرام id مختلف ہوتی ہے۔

rand program show <auction id>        # base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest

code_hash وہی hc ہے جو rand-guest نے ظاہر کیا، بس مختلف انداز میں لکھا گیا ہے۔ rand-guest ڈائجسٹ کے آٹھوں الفاظ میں سے ہر ایک کو بگ-اینڈین ترتیب میں ظاہر کرتا ہے، جبکہ والیٹ، rand program show اور rand_getProgram ہر لفظ کے بائٹس لٹل-اینڈین ترتیب میں ظاہر کرتے ہیں، اس لیے آٹھ hex ہندسوں کے ہر گروہ کے بائٹس الٹ جاتے ہیں۔ اوپر کی بلڈز کے لیے aa04e4c4…050d (Rust) بطور c4e404aa…4f16 نظر آتا ہے، اور 68605aa8…bea0 (C) بطور a85a6068…b69a۔

9. کال

فروخت کنندہ تمام 20 نجی الفاظ کے ساتھ کال کرتا ہے:

rand call <auction id> --expect-public auction-public.txt \
  --input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514 \
  --input 250 --input 461845907  --input 3432918353 --input 3864292196 --input 2246822507 \
  --input 900 --input 3266489909 --input 668265263  --input 374761393  --input 3550635116 \
  --input 300 --input 4252018373 --input 3042594569 --input 1759714724 --input 625341585

والیٹ پروگرام کا کوڈ اور عوامی سیگمنٹ حاصل کرتا ہے اور جانچتا ہے کہ ان کا ہیش id کے برابر ہے۔ اگر عوامی سیگمنٹ اس فائل کے الفاظ نہ ہوں تو --expect-public بھی ثبوت بنانے سے پہلے انکار کر دیتا ہے۔ پھر والیٹ کال کو ایمولیٹر میں چلاتا ہے اور عین گیس، وہ حد جس کا وہ اعلان کرے گا، درجہ (سب سے چھوٹا درجہ جس میں رن سما جائے، یہاں درجہ 10) اور فیس ظاہر کرتا ہے:

gas: <exact> (declaring <limit>, tier <t>)
fee <amount> RAND (incl. two price steps of headroom)

وہ 20 ان پٹ الفاظ کو ایک ایسے لفافے میں بند کرتا ہے جسے صرف کال کنندہ کھول سکتا ہے، رن کا ثبوت مقامی طور پر بناتا ہے، فیس ایک بنڈل کے ذریعے ادا کرتا ہے، جمع کرتا ہے، انتظار کرتا ہے اور رسید ظاہر کرتا ہے۔ chain 20 پر یہ تین ثبوت ہیں: کال، درجہ 10 کی خرچ اجازت اور درجہ 14 کا بنڈل۔

کال کی لاگت۔ chain 15 پر اس کال نے 0.001 + call_fee(10) = 0.002 RAND ادا کیے۔ chain 20 پر والیٹ حصہ 7 والی کم از کم فیس ادا کرتا ہے، جو ثبوت بننے سے پہلے ہی تین طے شدہ اقدار کے ساتھ حساب کر لی جاتی ہے:

  • اعلان کردہ حد درست گیس کو 2^(tier − 2) کے مضرب تک اوپر کی طرف پورا کر کے حاصل ہوتی ہے: درجہ 10 پر 256، درجہ 12 پر 1,024، درجہ 14 پر 4,096۔ --gas-limit max درجے کی بالائی حد کا اعلان کرتا ہے اور --gas-limit N ایک متعین عدد کا؛
  • ثبوت کی قیمت اس کے حقیقی حجم پر نہیں بلکہ چین کی 4,194,304 بائٹ کی حد پر لگائی جاتی ہے، کیونکہ چین 20 پر کال کا ثبوت اپنی ٹرانزیکشن سے بندھا ہوتا ہے اور فیس پہلے طے ہونی چاہیے؛
  • قیمت کے دو درجوں کی گنجائش، یعنی 1.125² کا ضربی عامل، کیونکہ کال قیمت لگنے کے دو یا تین بلاک بعد شامل ہوتی ہے۔

--fee طے شدہ قدر کو بدل دیتا ہے، اور rand fee call <tier> --gas <limit> --bytes <B> خود نوڈ سے کم از کم فیس دریافت کرتا ہے۔ نیلامی کی کال چین 18 سے 20 پر نہیں کی گئی، اس لیے اس کی گیس کی سطر اور فیس یہاں نہیں دکھائی گئی۔ ایک گمنام ووٹ میں چین 18 پر ایک مکمل کال اسی طرح درج ہے جیسے والیٹ نے اسے چھاپا، 0.0062 RAND پر۔

رسید میں درجہ، H_IN، H_PUB اور آؤٹ پٹس [1, 2, 400, 4, 0, 0, 0, 0] درج ہوتے ہیں: وہی الفاظ جو ایمولیٹر نے چھاپے تھے، کیونکہ ثبوت اسی عمل درآمد کا ہے۔ چاروں بولیاں اور سالٹ چین پر نہیں ہیں۔ --auditor <rand1…> ان پٹس کو ایک نامزد آڈیٹر کے لیے بھی سربمہر کرتا ہے، اور --no-envelope کوئی ریکارڈ شائع نہیں کرتا، جسے بعد میں واپس نہیں لیا جا سکتا (پروگرام)۔

10. نتیجے کی جانچ

بولی دہندہ، یا کوئی اور، فروخت کنندہ پر اعتماد کیے بغیر نیلامی کی جانچ کر سکتا ہے:

  1. کوڈ۔ شائع شدہ سورس کو اسی circuits کمٹ پر، Rust 1.98.1 (یا clang 23.1.1) کے ساتھ، rand-guest build سے دوبارہ بنائیں۔ hc کا موازنہ rand program show یا rand_getProgram سے حاصل code_hash سے کریں، اور بائٹ الٹی ترتیب والی املا کو ملحوظ رکھیں (حصہ 8)۔ بلڈ دوبارہ قابلِ تخلیق ہے، اس لیے دونوں کسی بھی مشین پر یکساں نکلتے ہیں۔
  2. عوامی حصہ۔ اسے rand_getProgramPublic سے حاصل کریں، یا rand call --expect-public چلائیں۔ تصدیق کریں کہ آپ کا اپنا کمٹمنٹ آپ کے اشاریے پر موجود ہے۔
  3. رسید۔ rand receipt <tx hash> یا rand_getReceipt: حیثیت 1، فاتح اور قیمت، سلاٹ 7 کی قدر 0xdeadbeef نہ ہو، اور program نیلامی کی شناخت کے برابر ہو۔
  4. ان پٹس، اگر آپ ان کے حق دار ہیں۔ فروخت کنندہ کا rand open-call <tx hash> (یا آڈیٹر کا --as-auditor) سربمہر ان پٹس کھولتا ہے، انہیں H_IN کے مقابل جانچتا ہے، پروگرام دوبارہ چلاتا ہے، اور کسی بھی عدم مطابقت پر غیر صفر کوڈ کے ساتھ ختم ہوتا ہے۔ v0.6.8 والیٹ استعمال کریں: v0.6.8 سے دوبارہ چلانے کا عمل پروگرام کے عوامی حصے اور کال کی بندش پر ہوتا ہے، اس لیے یہ نیلامی کے لیے کام کرتا ہے (fullnode issue #116)۔ v0.6.7 میں open-call عوامی حصے کے ساتھ ڈیپلائے کیے گئے کسی بھی پروگرام کے لیے re-running the program on these inputs failed: PublicIndex(0) پر رک جاتا تھا۔

ثبوت اس بات کی ضمانت دیتا ہے کہ قاعدہ کمٹ شدہ بولیوں کے انکشافات پر لاگو کیا گیا۔ یہ رقم منتقل نہیں کرتا۔ فاتح فروخت کنندہ کو ایک عام rand send سے ادائیگی کرتا ہے، اور رسید وہ دستاویز ہے جس کا دونوں فریق حوالہ دیتے ہیں۔

مسائل کا حل

علامتسبب اور حل
کسی پرانے کمٹ کے لیے لکھی گئی poseidon2(…)، keccak(…) یا sha256_compress(…) کال پر بلڈ ناکام ہو جاتا ہےaeacf31 پر poseidon2 ایک unsafe fn ہے اور باقی دونوں &mut [u32; 50] اور &mut [u32; 24] لیتے ہیں (حصے 3 اور 7)
check میں a7 = 7 کے ساتھ Syscall رپورٹ ہوتا ہےگیسٹ poseidon2_len کو کال کرتا ہے؛ aeacf31 پر rand-guest check سسٹم کال 7 کو فہرست میں شامل نہیں کرتا (zkp-circuits issue #1)
check میں 0xc0001073 پر Csr رپورٹ ہوتا ہےلائبریری کوڈ unimp میں کمپائل ہوا ہے؛ llvm-objdump سے فنکشن تلاش کریں اور کال بدل دیں (حصہ 6)
run بتاتا ہے کہ عمل درآمد کسی درجے میں نہیں سماتا، یا درجہ 14 سے زیادہ درکار ہےبہت زیادہ قطاریں یا Poseidon2 پرمیوٹیشنز؛ پروگرام مختصر کریں، کم ہیش کریں، یا کام کو کئی کالوں میں تقسیم کریں اور ان کے درمیان ایک ڈائجسٹ منتقل کریں
سلاٹ 7 = 3735928559Rust گیسٹ panic کا شکار ہوا؛ اس سے پہلے کے آؤٹ پٹس ناقابلِ اعتماد ہیں
trap: DoubleWrite(n)آؤٹ پٹ سلاٹ n دو بار لکھا گیا؛ ہر اخراج کو ایک ہی فنکشن سے گزاریں
trap: InputIndex(i)پروگرام نے ایسا نجی ان پٹ پڑھا جو کال کنندہ نے فراہم نہیں کیا؛ پہلے تعداد پڑھیں اور جانچیں
لوڈ یا اسٹور پر trap: …غیر مرتب یا حد سے باہر پتہ؛ C میں، اپنی حد سے آگے کا ارے اشاریہ
invalid value '0x…' for '--input'کمانڈ لائن پر ان پٹ الفاظ اعشاری ہوتے ہیں
کال CallTierTooHigh کے ساتھ مسترد ہو جاتی ہےثبوت درجہ 14 سے اوپر ہے؛ کال سے پہلے rand-guest run --tier 14 سے جانچ لیں
دوبارہ بنایا گیا hc ڈیپلائے شدہ سے مختلف ہےمختلف circuits کمٹ، ٹول چین، یا انحصاری ورژن؛ تینوں کو مقرر کریں اور Cargo.lock کمٹ کریں۔ اگر صرف املا مختلف ہے تو آٹھ ہیکس ہندسوں کا ہر گروہ بائٹ الٹی ترتیب میں ہے (حصہ 8)
rand open-call PublicIndex(0) پر رک جاتا ہےپروگرام کا ایک عوامی حصہ ہے اور والیٹ v0.6.7 ہے، جس کا دوبارہ چلانے کا عمل کوئی عوامی حصہ نہیں دیتا (fullnode issue #116)؛ v0.6.8 استعمال کریں
… is not inside a circuits checkoutگیسٹ ڈائریکٹری کو checkout کے اندر منتقل کریں (Rust پروگرام ڈیپلائے کریں)

آخری بار 2026-10-12 کو fullnode کمٹ c0878c3f (چین 21)، اور circuits `7ef3220` (`guest-sdk/src/lib.rs`, `rand-guest/guest.h`, `rand-guest/rt.c`, `rand-guest/README.md`, `research/docs/01-isa.md`, `research/src/machine.rs`); every `rand-guest` and `cargo test` output on this page was produced on 2026-09-25, fullnode `origin/main` `089bdd6` (`docs/cli.md`; `docs/confidential.md` and `executor.rs` for v0.5.6's call-proof caps, commit `1588be9`), circuits `aeacf31` (`guest-sdk/src/lib.rs`, `rand-guest/src/check.rs`, `rand-guest/guest.h`; `git diff 7ef3220 aeacf31 -- guest-sdk rand-guest`), read on 2026-09-30; no output on this page was re-run at that commit, fullnode `90e7716` (`docs/cli.md`, `docs/fees.md` §1.1, `docs/confidential.md`, `docs/guests.md` §7.1, `deploy/genesis-chain18.json`) and the wallet at tag v0.6.7 `86941a1` (`crates/randprotocol-client/src/main.rs`, `wallet.rs`; `gas.rs`, `ledger/mod.rs`, `executor.rs`), read on 2026-09-30; the auction was not deployed or called on chain 18, fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `crates/randprotocol-core/src/ledger/program_state.rs`, the client's `open-call` (#116)) and `rand_getLimits` on chain 20, read on 2026-10-01; the auction was not deployed or called on chain 20, fullnode tag v0.8.0 `c0878c3f` (`CHANGELOG.md`, `docs/fees.md` §1.3) and `rand_getLimits` on chain 21, read on 2026-10-12; the limits this page quotes are chain 21's where they differ from chain 20's; nothing on this page was deployed or called on chain 21 کے مقابل تصدیق کی گئی۔ جہاں کوڈ اور یہ صفحہ ایک دوسرے سے مختلف ہوں، وہاں کوڈ درست ہے؛ ہمیں مطلع کریں۔