Rand Protocol
دستاویزات
مثالی پروگرام

ایک گمنام ووٹ

ایک خفیہ پروگرام جو Keccak Merkle اجازت فہرست کی رکنیت ثابت کرتا ہے بغیر یہ بتائے کہ کون سا رکن ہے، اور ایک انتخاب اور ایک نلیفائر شائع کرتا ہے جو دوسرا ووٹ روک دیتا ہے - انٹرفیس، Rust گیسٹ، ہوسٹ اسکرپٹ، بجٹ، اور چین 18 پر فعال پروگرام۔

یہ پروگرام ایک مقررہ فہرست کے کسی رکن کو یہ ظاہر کیے بغیر کہ وہ کون سا رکن ہے، ایک ووٹ ڈالنے دیتا ہے۔ فہرست Keccak-256 ہیشوں کا ایک مرکل ٹری ہے، جو اسی طرح بنایا جاتا ہے جیسے Ethereum کی ایئر ڈراپ فہرست بنتی ہے، اس لیے وہی ٹولز اسے بناتے ہیں۔ یہ پروگرام chain 18 پر ڈیپلائے ہے، اور آخری حصہ اسے ایسے ڈیمو ارکان کے ساتھ کال کرتا ہے جنہیں آپ خود بھی استعمال کر سکتے ہیں۔

مثالی پروگرام میں وہ سیٹ اپ ہے جو چاروں مثالی صفحات میں مشترک ہے: circuits کا checkout، والیٹ، اور سورس کہاں سے حاصل کریں۔

1. یہ کیا ثابت کرتا ہے

ایک منتظم کے پاس ارکان کی فہرست ہے۔ ہر رکن کے پاس 32 بائٹ کا ایک راز ہے۔ منتظم راز کبھی نہیں دیکھتا: ہر رکن ایک لیف، keccak256(secret ‖ tag)، حوالے کرتا ہے، اور منتظم ان لیفس پر ایک مرکل ٹری بناتا ہے۔

ایک ووٹ ایک کال ہے۔ یہ کچھ اور ظاہر کیے بغیر ثابت کرتا ہے کہ:

  1. کال کرنے والا ایک ایسا راز جانتا ہے جس کا لیف ٹری میں موجود ہے؛
  2. شائع شدہ انتخاب اجازت یافتہ انتخابات میں سے ایک ہے؛
  3. شائع شدہ نلیفائر keccak256(secret ‖ tag ‖ poll id) ہے۔

ایک پول میں کسی راز سے ڈالے گئے ہر ووٹ کا نلیفائر یکساں ہوتا ہے، اور ہر دوسرے پول میں مختلف۔ ووٹ گننے والا ہر نلیفائر کو ایک بار گنتا ہے۔ یہ لیف سے منسلک نہیں ہوتا: نلیفائر سے واپس لیف تک پہنچنے کے لیے راز درکار ہے۔

چین دوسرے ووٹ کو مسترد نہیں کرتی۔ Rand پر کسی پروگرام کے پاس اسٹوریج نہیں ہوتا، اس لیے وہ کوئی نلیفائر یاد نہیں رکھ سکتا (پروگرام)۔ ایک ہی راز والی دو کالیں دونوں تصدیق پاتی ہیں اور دونوں ایک رسید چھوڑتی ہیں۔ “ایک نلیفائر، ایک ووٹ” کا اصول گننے والا رسیدوں پر لاگو کرتا ہے۔ کوئی بھی یہی گنتی دہرا سکتا ہے، اور اسی سے نتیجہ قابلِ جانچ بنتا ہے۔

2. انٹرفیس

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

عوامی لفظمطلب
0ٹری کی گہرائی d، 1 سے 20
1پول id
2انتخابات کی تعداد؛ انتخاب 0 .. choices − 1 ہے
3 سے 10روٹ، 32 بائٹ، آٹھ little-endian الفاظ کی صورت میں
نجی لفظمطلب
0 سے 7راز
8رکن کے لیف کا انڈیکس
9انتخاب
10 اور آگےمرکل پاتھ: d سبلنگ، ہر ایک 8 الفاظ کا، لیف سے اوپر کی طرف
آؤٹ پٹ سلاٹمطلب
0اسٹیٹس: 1 ووٹ ڈالا گیا، 2 رکن نہیں، 3 انتخاب حد سے باہر، 4 غلط سیٹ اپ
1انتخاب
2 سے 6نلیفائر کے پہلے 20 بائٹ، پانچ little-endian الفاظ کی صورت میں
7غیر مستعمل؛ Rust کا panic یہاں 0xdeadbeef لکھتا ہے

32 بائٹ کا ہیش آٹھ الفاظ ہے، اور کل آؤٹ پٹ ہی آٹھ الفاظ کا ہے۔ 160 بِٹ شائع کرنے سے اسٹیٹس اور انتخاب کے لیے جگہ بچتی ہے۔ دو ارکان کا 160 بِٹ نلیفائر صرف ہیش ٹکراؤ کی صورت میں ایک ہو سکتا ہے، جسے ڈھونڈنے میں تقریباً 2⁸⁰ کوششیں لگتی ہیں۔

بائٹ کی ترتیب۔ Keccak بائٹس پر کام کرتا ہے۔ مشین کا KECCAK سسکال اسٹیٹ کو الفاظ کی صورت میں لیتا ہے، جس میں اسٹیٹ کا بائٹ i لفظ i / 4 کے بائٹ i mod 4 میں ہوتا ہے۔ چنانچہ 32 بائٹ ایک وقت میں چار بائٹ little-endian پڑھ کر آٹھ الفاظ بنتے ہیں، اور عوامی سیگمنٹ میں روٹ محض روٹ کے بائٹس ہیں جو اسی طرح پڑھے گئے ہیں۔

3. گیسٹ

my-guests/allowlist/src/main.rs، مکمل:

#![no_std]
#![no_main]

use guest_sdk::{halt, keccak, read_input, read_public, write_output};

const MAX_DEPTH: u32 = 20;

const OK: u32 = 1;
const NOT_MEMBER: u32 = 2;
const BAD_CHOICE: u32 = 3;
const BAD_SETUP: u32 = 4;

/// The 32 bytes hashed after a secret to make its leaf: the ASCII tag, zero-padded.
const LEAF_TAG: [u32; 8] = tag_words(b"rand-allowlist-leaf-v1");
/// The first 28 bytes hashed after a secret to make its nullifier; the poll id is the last 4.
const NULL_TAG: [u32; 8] = tag_words(b"rand-allowlist-null-v1");

/// An ASCII tag as eight words: byte `i` of the tag is byte `i % 4` of word `i / 4`.
const fn tag_words(tag: &[u8]) -> [u32; 8] {
    let mut w = [0u32; 8];
    let mut i = 0;
    while i < tag.len() {
        w[i / 4] |= (tag[i] as u32) << (8 * (i % 4));
        i += 1;
    }
    w
}

/// Keccak-256 of a 64-byte message, the hash Ethereum calls `keccak256(abi.encodePacked(a, b))`.
/// The message is one rate block, so this is one permutation: the two halves go into the first
/// 16 words of the state, `0x01` follows them, and `0x80` closes the 136-byte rate.
///
/// `a_second` is 0 or 1. When it is 1 the halves trade places. The place is computed as an
/// offset, not chosen by a branch, so both orders execute the same instructions.
fn hash2(a: &[u32; 8], b: &[u32; 8], a_second: u32) -> [u32; 8] {
    let at = 8 * (a_second & 1) as usize;
    let mut st = [0u32; 50];
    st[at..at + 8].copy_from_slice(a);
    st[8 - at..16 - at].copy_from_slice(b);
    st[16] = 0x01;
    st[33] = 0x8000_0000;
    keccak(&mut st);
    let mut out = [0u32; 8];
    out.copy_from_slice(&st[..8]);
    out
}

fn read8(at: u32) -> [u32; 8] {
    let mut w = [0u32; 8];
    for (i, x) in w.iter_mut().enumerate() {
        *x = read_input(at + i as u32);
    }
    w
}

/// Public segment, fixed at deploy: [depth, poll id, number of choices, root (8 words)].
/// Private input: [secret (8 words), leaf index, choice, siblings (8 words per level)].
/// Outputs: [status, choice, nullifier (5 words), 0].
#[no_mangle]
pub extern "C" fn main() -> ! {
    let depth = read_public(0);
    let poll = read_public(1);
    let choices = read_public(2);
    if depth == 0 || depth > MAX_DEPTH {
        write_output(0, BAD_SETUP);
        halt();
    }

    let secret = read8(0);
    let index = read_input(8);
    let choice = read_input(9);

    // Walk from the leaf to the root. Bit `level` of the index says whether the running hash
    // is the right child at that level.
    let mut cur = hash2(&secret, &LEAF_TAG, 0);
    let mut level = 0;
    while level < depth {
        let sib = read8(10 + 8 * level);
        cur = hash2(&cur, &sib, index >> level);
        level += 1;
    }

    let mut diff = 0u32;
    for k in 0..8 {
        diff |= cur[k] ^ read_public(3 + k as u32);
    }

    let mut ctx = NULL_TAG;
    ctx[7] = poll;
    let nullifier = hash2(&secret, &ctx, 0);

    if diff != 0 {
        write_output(0, NOT_MEMBER);
    } else if choice >= choices {
        write_output(0, BAD_CHOICE);
    } else {
        write_output(0, OK);
        write_output(1, choice);
        for k in 0..5 {
            write_output(2 + k as u32, nullifier[k]);
        }
    }
    halt();
}

اس میں تین چیزیں نقل کرنے کے قابل ہیں۔

ہر ہیش کے لیے ایک پرمیوٹیشن۔ guest_sdk::keccak256 کوئی بھی بائٹ اسٹرنگ لیتا ہے، اور اس کی قیمت بائٹ لوپس کی صورت میں ادا کرتا ہے۔ یہاں ہر پیغام ٹھیک 64 بائٹ کا ہے، جو 136 بائٹ کے ریٹ سے کم ہے، اس لیے پیڈنگ دو مستقل الفاظ ہے اور ہیش ایک ایسی اسٹیٹ پر ایک KECCAK کال ہے جو الفاظ کی نقل سے بھری گئی ہو۔ Ethereum کی اصطلاح میں، دو bytes32 کے لیے hash2(a, b, 0) وہی ہے جو keccak256(abi.encodePacked(a, b)) ہے۔

لیف اور نلیفائر کو ٹری کے نوڈ سے خلط ملط نہیں کیا جا سکتا۔ نوڈ دو 32 بائٹ چائلڈ ہیشوں کو ہیش کرتا ہے۔ لیف ایک راز اور ایک مقررہ ASCII ٹیگ کو ہیش کرتا ہے، اور نلیفائر ایک راز، ایک اور ٹیگ اور پول id کو ہیش کرتا ہے۔ کسی لیف کے نوڈ کے طور پر چل جانے کے لیے اس کے کسی چائلڈ کا ٹیگ کے برابر ہونا ضروری ہوگا، جس کا مطلب Keccak کا preimage ڈھونڈنا ہے۔

پاتھ کو انڈیکس پر شاخ بنائے بغیر طے کیا جاتا ہے۔ پہلے ورژن نے دونوں نصفوں کی ترتیب ایک ماسک شدہ swap سے چنی، اور کمپائلر نے ماسک کو واپس ایک برانچ میں بدل دیا: لیف انڈیکس میں ہر 1 بِٹ پر رن نے 9 سائیکل زیادہ لیے۔ سائیکل کی گنتی خود شائع نہیں ہوتی، لیکن کال جو گیس کی حد ظاہر کرتی ہے وہ اسی سے راؤنڈ کی جاتی ہے (حصہ 6)، اس لیے انڈیکس پر منحصر رن انڈیکس کا تھوڑا سا حصہ افشا کر سکتا ہے۔ ہر نصف کو ایک محسوب آفسیٹ پر لکھنے سے یہ درست ہو گیا: اب ہر لیف انڈیکس یکساں 3,208 سائیکل چلتا ہے۔

4. ہوسٹ کی جانب

منتظم اور ووٹروں کو وہی Keccak درکار ہے جو مشین حساب کرتی ہے۔ allowlist.py ایک بغیر انحصار والا Python اسکرپٹ ہے جس کا اپنا Keccak-f[1600] ہے (Python کا hashlib.sha3_256 دراصل SHA-3 ہے، جو مختلف طریقے سے پیڈ کرتا ہے اور مختلف ڈائجسٹ دیتا ہے)۔ یہ دو کام کرتا ہے:

allowlist.py setup  <depth> <poll id> <choices> <secrets file>   writes public.txt, prints the root
allowlist.py witness <depth> <poll id> <secrets file> <member> <choice>
                                                   prints the private words and the nullifier

پورا ٹری، اسکرپٹ کی اصطلاح میں:

LEAF_TAG = b"rand-allowlist-leaf-v1".ljust(32, b"\0")
NULL_TAG = b"rand-allowlist-null-v1".ljust(28, b"\0")
EMPTY = bytes(32)  # an unused leaf: no secret hashes to it

def levels(depth, secrets):
    row = [keccak256(s + LEAF_TAG) for s in secrets]
    row += [EMPTY] * ((1 << depth) - len(row))
    out = [row]
    while len(row) > 1:
        row = [keccak256(row[i] + row[i + 1]) for i in range(0, len(row), 2)]
        out.append(row)
    return out

nullifier = keccak256(secret + NULL_TAG + poll.to_bytes(4, "little"))

پروڈکشن میں منتظم کے پاس لیفس ہوتے ہیں، راز نہیں، اور ہر ووٹر شائع شدہ لیفس سے اپنا وٹنس خود بناتا ہے۔ اسکرپٹ رازوں کی فائل اس لیے لیتا ہے کہ ڈیمو دونوں کردار ادا کرتا ہے۔

ڈیمو پول کی گہرائی 10 (1,024 لیف)، پول id 1 اور تین انتخابات ہیں۔ اس کے آٹھ ارکان کے راز demo-secrets.txt میں ہیں، ہر سطر پر ایک 64-hex راز، اور یہ جان بوجھ کر عوامی ہیں تاکہ کوئی بھی پروگرام کو کال کر سکے:

$ python3 allowlist.py setup 10 1 3 demo-secrets.txt
root ce8d6ef78a2ccaab688afa50ff969b0f89b23e7f32d8c3746848b46f773f4750
wrote public.txt
$ cat public.txt
10 1 3 4151217614 2882153610 1358596712 261854975 2134815369 1958991922 1874085992 1346846583

روٹ کا پہلا لفظ، 4151217614، 0xf76e8dce ہے: روٹ کے پہلے چار بائٹ ce 8d 6e f7 جو little-endian پڑھے گئے۔

5. بلڈ اور رن

$ rand-guest build my-guests/allowlist --max-words 65535
427 words against a cap of 65535 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/allowlist/image.bin and its .sha256 (427 words, hc 420876739f0987ae7f0fbd23a98312ed4452b7e77cf6153e424ed02aca7b5afe, program id 93c780859b60516408099dc45247a21aead612f0c65c667248253533dd932bb3)

رکن 5 انتخاب 2 کو ووٹ دیتا ہے۔ وٹنس 90 الفاظ کا ہے: 8 راز کے لیے، انڈیکس، انتخاب، اور 80 پاتھ کے لیے۔

$ cd my-guests/allowlist
$ python3 allowlist.py witness 10 1 demo-secrets.txt 5 2 > witness.txt
nullifier b5516c17d7f51034998f837217777deb8bc90886
nullifier words 392974773 873526743 1921224601 3950868247 2248722827
$ rand-guest run image.bin --public $(cat public.txt) --input $(cat witness.txt) --tier 14
out[0] = 1
out[1] = 2
out[2] = 392974773
out[3] = 873526743
out[4] = 1921224601
out[5] = 3950868247
out[6] = 2248722827
out[7] = 0
cycles 3208
tier 12
tier 14: fits (cycles 3342 of 16383, Poseidon2 permutations 134 of 2048)

نلیفائر کے پانچ الفاظ وہی ہیں جو اسکرپٹ نے مشین کے باہر حساب کیے۔ ناکامیاں صاف طور پر ایک اسٹیٹس کے ساتھ رکتی ہیں، اس لیے انہیں بھی ثابت کیا جا سکتا ہے۔ وہی وٹنس، راز کا پہلا لفظ بدل کر، out[0] = 2 اور کچھ نہیں دیتا، اور 3 میں سے انتخاب 3 مانگنے والا رکن out[0] = 3 دیتا ہے۔

6. بجٹ

پروگرام427 الفاظ
سائیکلگہرائی 10 کے ٹری کے لیے 3,208؛ ڈائجسٹ قطاروں سمیت 3,342
Keccak پرمیوٹیشنز12: ایک لیف، دس نوڈ، ایک نلیفائر۔ ایک کال کی حد 128 ہے
Poseidon2 پرمیوٹیشنز2,048 میں سے 134، سب کے سب پروگرام، ان پٹ اور عوامی ڈائجسٹ
درجہ12

گہرائی کی ہر اضافی سطح پر ایک پرمیوٹیشن اور تقریباً 270 سائیکل لگتے ہیں۔ گہرائی 20 کا ٹری (دس لاکھ ارکان) 5,866 سائیکل اور 22 پرمیوٹیشنز ناپتا ہے، جو درجہ 14 ہے، وہ سب سے اونچا درجہ جو کوئی کال استعمال کر سکتی ہے۔

chain 18 سے کال گیس ادا کرتی ہے، اور Keccak پرمیوٹیشن مہنگی ہدایت ہے: ایک عام سائیکل کے 1 کے مقابلے میں 192 گیس۔ والیٹ کسی رن کی ٹھیک گیس اور اس کے لیے ظاہر کی گئی حد پرنٹ کرتا ہے:

gas: 5636 (declaring 6144, tier 12)

رن کی ہر قطار پر 1 گیس لگتی ہے، اور Keccak کی قطار پر 191 مزید: 3,344 قطاریں جمع 12 × 191 برابر 5,636، اور اس کا دو بٹا پانچ حصہ بارہ ہیش ہیں۔ والیٹ rand-guest run سے دو قطاریں زیادہ گنتا ہے۔ chain 18 پر کال کا ثبوت اپنی ٹرانزیکشن سے بندھا ہوتا ہے، اور یہ بندھن عوامی سیگمنٹ کے آٹھ اضافی الفاظ کی صورت میں ساتھ جاتا ہے، جس سے دو ڈائجسٹ قطاریں بڑھ جاتی ہیں۔ ظاہر کی گئی حد، 6,144، ٹھیک عدد کو 1,024 کے مضرب تک اوپر راؤنڈ کر کے بنتی ہے۔ حد عوامی ہے اور ٹھیک عدد نہیں۔

Keccak ٹیبل ساتھ رکھنے والا ثبوت بڑا ہوتا ہے۔ یہ ثبوت 3,346,865 بائٹ کا ہے، جبکہ chain 18 سے 20 پر حد 4,194,304 (4 MiB) ہے۔ اوپر گہرائی 20 کے اعداد ایمولیٹر کی پیمائشیں ہیں، اور اس صفحے کے لیے اس گہرائی پر کوئی ثبوت نہیں بنایا گیا۔

7. ڈیپلائے

ڈیپلائے عوامی سیگمنٹ ساتھ لے جاتا ہے، اس لیے فیس اس کے 11 الفاظ کو 427 کوڈ الفاظ کے ساتھ گنتی ہے: 0.001 + 0.0001 × 438 = 0.0448 RAND۔

$ rand fee deploy 427 --public-words 11
0.0448 RAND
$ rand program deploy image.bin --public public.txt
program id: ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 (427 words, hc 73760842ae87099f23bd0f7fed1283a9e7b752443e15f67c2ad04e42fe5a7bca, public input 11 words, digest 2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510)
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 22.9s: tier 10, 1370827 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 190.2s: tier 14, 1497544 bytes
submitted deploy 05f8bfcbf9fccedbd53c3ba41bcc32dc8070ed3433364fc591b4f00699438de6
  0 RAND out, 59.9552 RAND change, fee 0.0448 RAND, anchored at height 97632, proved in 190.2s (bundle) + 22.9s (auth)

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

hc بھی مختلف طرح لکھا جاتا ہے: rand-guest اپنے آٹھوں الفاظ میں سے ہر ایک big-endian پرنٹ کرتا ہے اور والیٹ ہر لفظ کے بائٹ little-endian پرنٹ کرتا ہے، اس لیے 42087673 بدل کر 73760842 ہو گیا۔ یہ وہی ڈائجسٹ ہے۔

$ rand program show ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970
{
  "base_pc": 4096,
  "code_hash": "73760842ae87099f23bd0f7fed1283a9e7b752443e15f67c2ad04e42fe5a7bca",
  "deployed_at": 97821,
  "id": "ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970",
  "public_digest": "2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510",
  "public_words_len": 11,
  "words_len": 427
}

کوئی بھی عوامی سیگمنٹ واپس پڑھ سکتا ہے۔ rand_getProgramPublic الفاظ کو little-endian بائٹس کی صورت میں لوٹاتا ہے، اس لیے روٹ ویسا ہی نظر آتا ہے جیسا پرنٹ ہوا تھا:

$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"rand_getProgramPublic","params":["ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970"]}'
{"id":1,"jsonrpc":"2.0","result":"0a0000000100000003000000ce8d6ef78a2ccaab688afa50ff969b0f89b23e7f32d8c3746848b46f773f4750"}

8. ووٹ

rand call ہر نجی لفظ کے لیے ایک --input لیتا ہے، اعشاری شکل میں۔ sed وٹنس فائل کو انہی فلیگز میں بدل دیتا ہے:

$ rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
    $(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
proving the call locally (90 inputs stay private, over the program's 11-word public input)…
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
proved in 29.2s: tier 12, 3346865 bytes, outputs [1, 2, 392974773, 873526743, 1921224601, 3950868247, 2248722827, 0]
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 8.2s: tier 10, 1367303 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 117.2s: tier 14, 1504260 bytes
submitted call 8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666
  0 RAND out, 59.716708563 RAND change, fee 0.006191437 RAND, anchored at height 98185, proved in 117.2s (bundle) + 8.2s (auth)
{
  "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"
}
input transcript published; open it with `rand open-call 8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666`

چین جو چیز محفوظ رکھتی ہے وہ رسید ہے: پول کے program id کے تحت status 1، انتخاب 2 اور نلیفائر کے پانچ الفاظ۔ h_pub پول کے عوامی حصے کا ڈائجسٹ ہے، وہی قدر جو program show نے دکھائی تھی۔ h_in 90 نجی الفاظ پر ایک نمک دار (salted) کمٹمنٹ ہے، اور نمک کے بغیر اس سے کچھ ظاہر نہیں ہوتا۔

یہ کال Apple Silicon لیپ ٹاپ پر تین منٹ میں مکمل ہوئی اور میموری کا استعمال زیادہ سے زیادہ 6.1 GB تک پہنچا: کال کے ثبوت کے لیے 29 s، خرچ کی اجازت کے لیے 8 s اور فیس ادا کرنے والے بنڈل کے لیے 117 s۔ فیس، 0.0062 RAND، زیادہ تر بائٹس کی ہے۔ اس کا حساب chain 20 پر چار پروگرام میں دیا گیا ہے۔

ٹرانسکرپٹ والی سطر۔ طے شدہ طور پر والیٹ نجی ان پٹس کو ایک ایسے لفافے میں بند کرتا ہے جسے صرف کال کرنے والے کی ویونگ کی کھول سکتی ہے، اور اسے کال کے ساتھ شائع کر دیتا ہے۔ یہ کال کرنے والے کا اپنا ریکارڈ ہے، افشا نہیں: کوئی اور اسے نہیں کھول سکتا۔ جو ووٹر کوئی ریکارڈ بھی نہیں چاہتا وہ --no-envelope دیتا ہے۔ اس کے بعد کوئی بھی، خود ووٹر سمیت، کبھی یہ نہیں دکھا سکتا کہ ان پٹس کیا تھے۔

9. ووٹوں کی گنتی

گنتی کرنے والے کو چین کے سوا کسی چیز کی ضرورت نہیں:

  1. پول۔ پروگرام کا عوامی حصہ حاصل کریں اور جڑ کو پتّوں کی شائع شدہ فہرست سے ملائیں (allowlist.py کے levels سے درخت دوبارہ بنائیں)۔ گیسٹ کو سورس سے دوبارہ بنائیں اور hc کا پروگرام کے code_hash سے موازنہ کریں۔
  2. رسیدیں۔ ہر وہ رسید لیں جس کا program پول کا id ہو اور جس کا آؤٹ پٹ 0 1 ہو۔ کسی اور status والی رسید، یا وہ رسید جس کے خانہ 7 میں 3735928559 ہو، ووٹ نہیں ہے۔
  3. گنتی۔ آؤٹ پٹ 2 تا 6 کے لحاظ سے گروہ بنائیں۔ ہر نلیفائر کو ایک بار گنیں، یعنی چین کی ترتیب میں اس کی پہلی رسید، اور اس کا آؤٹ پٹ 1 میزان میں جمع کریں۔

گنتی کرنے والے کے لیے کچھ کام پیدا کرنے کی خاطر تین مزید کالیں کی گئیں۔ رکن 5 نے دوسری بار ووٹ دیا، اس بار انتخاب 0 کو۔ رکن 2 نے انتخاب 1 کو ووٹ دیا۔ رکن 3 نے انتخاب 0 کو ووٹ دیا، ایک ایسے والیٹ سے جس نے صرف عوامی RPC endpoint استعمال کیا۔ یہ رکن 5 کا دوسرا ووٹ ہے:

$ python3 allowlist.py witness 10 1 demo-secrets.txt 5 0 > witness.txt
nullifier b5516c17d7f51034998f837217777deb8bc90886
nullifier words 392974773 873526743 1921224601 3950868247 2248722827
$ rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
    $(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
…
proved in 31.0s: tier 12, 3345265 bytes, outputs [1, 0, 392974773, 873526743, 1921224601, 3950868247, 2248722827, 0]
…
submitted call 0f596b87f5137a165d6abc691dbe04f7533db619911d81449fea9ede602c40d0

چین نے دوسرا ووٹ قبول کر لیا، جیسا کہ اسے کرنا چاہیے: ثبوت درست ہے۔ اس کی رسید میں نلیفائر کے وہی پانچ الفاظ ہیں جو پہلی رسید میں تھے۔

tally.py یہ تینوں قواعد لاگو کرتا ہے۔ یہ پروگرام کی کالیں ایکسپلورر سے اور ہر رسید عوامی RPC سے پڑھتا ہے، اس لیے اسے نہ کسی والیٹ کی ضرورت ہے نہ کسی نوڈ کی:

$ python3 tally.py ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970
8aa13d1f…  height 98335  choice 2  nullifier b5516c17d7f51034…  counted
0f596b87…  height 99194  choice 0  nullifier b5516c17d7f51034…  repeat, dropped
2db517df…  height 99339  choice 1  nullifier 52f2455918bbc811…  counted
fdbcc960…  height 101833  choice 0  nullifier cd86c5454b809914…  counted
tally: choice 0: 1, choice 1: 1, choice 2: 1

رکن 5 کی رائے کی تبدیلی شمار نہیں ہوئی، اور چاروں رسیدوں میں خود نلیفائر کے سوا کوئی چیز یہ نہیں بتاتی کہ ان میں سے دو فہرست کے ایک ہی رکن کی ہیں۔ پول کھلا ہے: آٹھ ڈیمو اراکین میں سے کسی کے طور پر ووٹ دیں اور گنتی دوبارہ چلائیں۔

ثبوت آپ کو کیا نہیں دیتا

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

آخری بار 2026-09-30 کو fullnode کمٹ 48dc04b (چین 18)، اور circuits `aeacf31` (`guest-sdk/src/lib.rs`, `rand-guest`), the commit fullnode v0.6.7 vendors; every `rand-guest` and `rand` output on this page was produced on 2026-09-30, the deploy and the call on chain 18 کے مقابل تصدیق کی گئی۔ جہاں کوڈ اور یہ صفحہ ایک دوسرے سے مختلف ہوں، وہاں کوڈ درست ہے؛ ہمیں مطلع کریں۔