Rand Protocol
لاسوندونه
د بېلګې پروګرامونه

له زنځیر بهر لیجر

یو محرم پروګرام چې حالت هلته ساتي چېرې چې پروګرامونه حالت نه لري - د 256 حسابونو Merkle ونه چې له زنځیر بهر ژوند کوي، او د لیږدونو هره دسته د یوې ریښې څخه بلې ته د انتقال په توګه ثابتېږي او د رسیدونو له لارې سره تړل کېږي. انټرفېس، د Rust ګېسټ، د کوربه وسیله، د سایکلونو شمېر څنګه نیمایي شو، او هغه پروګرام چې په chain 18 کې ژوندی دی.

یو پروګرام په Rand کې ذخیره نه لري: یوه غوښتنه خپل ننوتي لولي، اته کلمې خپروي او یو رسید پرېږدي (پروګرامونه). دا پاڼه هغه معیاري لاره ښيي چې له دې سره سره حالت پرې وساتل شي. حالت له چېن بهر د Merkle ونې په بڼه ژوند کوي، پروګرام هر بدلون د یوې ریښې څخه بلې ته د یو ګام په توګه ثابتوي، او رسیدونه ګامونه یو له بل سره تړي.

بېلګه د 256 حسابونو یو لېجر دی. یوه غوښتنه تر څلورو پورې لېږدونه پلي کوي او ثابتوي چې هر لېږونکی د حساب خاوند و، پیسې یې درلودې، او هیڅ شی نه جوړ شوی او نه ورک شوی. چېن یو حالت، یو شمېر او یو هش ویني.

د بېلګې پروګرامونه هغه تنظیم لري چې ټولې څلور د بېلګې پاڼې یې سره شریکوي.

1. څه ثابتوي

لېجر د Poseidon2 پر بنسټ د 8 ژورتیا لرونکې Merkle ونه ده، هغه هش چې ماشین یې تر ټولو ارزانه ثابتوي. یوه پاڼه یو حساب دی:

owner = first 4 words of Poseidon2("OWNR", secret)
leaf  = Poseidon2("LEAF", owner, balance low, balance high, nonce)
node  = Poseidon2(left, right, "NODE")

راز څلور کلمې دي چې د حساب لرونکی یې ساتي.

یوه غوښتنه اوسنۍ ریښه او د لېږدونو یوه ډله د خصوصي ننوتي په توګه اخلي. د هر لېږد لپاره دا ثابتوي:

  1. د لېږونکي پاڼه د خپلې Merkle لارې له مخې په ونه کې ده؛
  2. غوښتونکی هغه راز پېژني چې د لېږونکي د مالک پېژند تر شا دی؛
  3. مقدار د لېږونکي له بیلانس څخه ډېر نه دی؛
  4. له کسر وروسته ونه د ترلاسه کوونکي پاڼه لري؛
  5. اعتبار له حد څخه نه اوړي.

بیا یو پیوند خپروي: Poseidon2(old root, new root, count, "LINK")، چې په 160 بټونو لنډ شوی.

ولې پیوند او نه دواړه ریښې. یوه ریښه اته کلمې ده او د وتلو اته ځایونه شته. د دواړو ریښو خپرول به دا معنا ولري چې هره یوه شاوخوا 96 بټونو ته لنډه شي، او د 96 بټونو یو ارزښت د شاوخوا 2⁴⁸ کار په لګولو سره ټکر کېدای شي، چې ثابتوونکي ته به اجازه ورکړي چې د یو رسید لاندې دوه بېلابېل لېجرونه وښيي. د دواړو ریښو یو 160 بټي هش دا ستونزه نه لري. بشپړې ریښې د لېجر له معلوماتو سره له چېن بهر لېږدول کېږي، او هر څوک چې یې ولري پیوند بیا حسابوي.

د لېجر تعقیبول. یو تعقیبوونکی اوسنی حالت ساتي. کله چې د پروګرام لپاره د 1 حالت سره یو رسید راښکاره شي، تعقیبوونکی نوې پاڼې د هغه چا څخه ترلاسه کوي چې غوښتنه یې کړې، ریښه بیا جوړوي، او پیوند د خپلې زړې ریښې، نوې ریښې او د رسید له شمېر څخه حسابوي. که دا د رسید له وتونو سره برابر وي، ګام سم دی او تعقیبوونکی نوی حالت مني. هغه رسید چې د تعقیبوونکي له اوسنۍ ریښې څخه پیوند نه لري، له پامه غورځول کېږي. همدا شی سیالي هم حل کوي: دوه غوښتونکي چې پر یوې ریښې جوړوي دواړه رسیدونه ترلاسه کوي، او هغه چې چېن یې لومړی ترتیبوي هماغه دی چې هر تعقیبوونکی یې مني.

څوک غوښتنه کولی شي. هر هغه څوک چې د یو لېږونکي راز او اوسنۍ ونه پېژني. په قاعده کې هیڅ چلوونکی نشته: یو لرونکی کولی شي خپل لېږد پر خپل ماشین ثابت کړي، نو راز هېڅکله له هغه نه وځي.

2. انٹرفېس

هیڅ عامه برخه نشته. د لېجر هویت د هغه پیدایښتي ریښه ده، چې له چېن بهر پرې موافقه شوې.

خصوصي کلمهمعنا
0د لېږدونو شمېر، تر ټولو ډېر 4
1 تر 8زړه ریښه
بیا، د هر لېږد لپاره، 146 کلمې
+0, +1د لېږونکي او د ترلاسه کوونکي د پاڼې شاخص، هر یو له 256 څخه کم
+2, +3مقدار، یو u64، لومړی ټیټه کلمه بیا لوړه
+4 تر +7د لېږونکي راز
+8 تر +10د لېږونکي بیلانس (ټیټ، لوړ) او نانس
+11 تر +74د لېږونکي Merkle لار: د 8 کلمو 8 خویندې، له پاڼې پورته
+75 تر +78د ترلاسه کوونکي د مالک پېژند
+79 تر +81د ترلاسه کوونکي بیلانس (ټیټ، لوړ) او نانس
+82 تر +145د ترلاسه کوونکي Merkle لار، په هغه ونه کې چې کسر یې شاته پرېږدي
د وتلو ځایمعنا
0حالت: 1 پلي شو، 2 د لېږونکي پاڼه په ونه کې نشته (یا راز غلط دی)، 3 د ترلاسه کوونکي پاڼه په ونه کې نشته، 4 ناکافي بیلانس، 5 اعتبار له حد څخه اوړي، 6 لېږونکی پخپله ترلاسه کوونکی دی، 7 له 4 څخه ډېر لېږدونه، 8 شاخص له حد څخه بهر
1پلي شوي لېږدونه: د بریالیتوب پر مهال شمېر، که نه نو د ناکام لېږد شاخص
2 تر 6د بریالیتوب پر مهال، د پیوند لومړۍ پنځه کلمې
7نه کارول کېږي؛ د Rust یو panic دلته 0xdeadbeef لیکي

لېږدونه په ترتیب پلي کېږي، هر یو پر هغې ونې چې مخکینی یې پرېښې ده. د نندارې په ډله کې، حساب 3 له صفر څخه پیلېږي، په لومړي لېږد کې 125 ترلاسه کوي او په درېیم کې یې 25 لګوي.

3. مېلمه پروګرام

my-guests/ledger/src/main.rs، بشپړ:

#![no_std]
#![no_main]

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

/// The ledger is a Merkle tree of 2^8 = 256 accounts.
const DEPTH: usize = 8;
const MAX_TRANSFERS: u32 = 4;
/// Private words per transfer: from, to, amount (2), secret (4), sender balance (2) and nonce,
/// sender path (64), receiver owner (4), balance (2) and nonce, receiver path (64).
const TRANSFER_WORDS: u32 = 146;

const OK: u32 = 1;
const BAD_SENDER: u32 = 2;
const BAD_RECEIVER: u32 = 3;
const INSUFFICIENT: u32 = 4;
const OVERFLOW: u32 = 5;
const SELF_TRANSFER: u32 = 6;
const TOO_MANY: u32 = 7;
const BAD_INDEX: u32 = 8;

const OWNER_TAG: u32 = 0x4f57_4e52; // "OWNR"
const LEAF_TAG: u32 = 0x4c45_4146; // "LEAF"
const NODE_TAG: u32 = 0x4e4f_4445; // "NODE"
const LINK_TAG: u32 = 0x4c49_4e4b; // "LINK"

type Digest = [u32; 8];

/// Poseidon2 over `buf[..n]`. The sponge does not pad, so every message this program hashes
/// has a fixed length and carries its own tag word.
fn hash(buf: &mut [u32], n: usize) -> Digest {
    assert!(n <= buf.len() && buf.len() >= 8);
    // SAFETY: the syscall reads `buf[..n]` and overwrites `buf[..8]`; the assert keeps both
    // inside `buf`, which `&mut` makes aligned, valid and unaliased for the call.
    unsafe { poseidon2(buf.as_mut_ptr(), n) };
    let mut d = [0u32; 8];
    d.copy_from_slice(&buf[..8]);
    d
}

/// An account's owner id: the first four words of Poseidon2("OWNR", secret).
fn owner_of(secret: &[u32; 4]) -> [u32; 4] {
    let mut buf = [OWNER_TAG, secret[0], secret[1], secret[2], secret[3], 0, 0, 0];
    let d = hash(&mut buf, 5);
    [d[0], d[1], d[2], d[3]]
}

fn leaf(owner: &[u32; 4], balance: u64, nonce: u32) -> Digest {
    let mut buf = [
        LEAF_TAG, owner[0], owner[1], owner[2], owner[3], balance as u32, (balance >> 32) as u32, nonce,
    ];
    hash(&mut buf, 8)
}

/// One level of a walk. `buf` holds a node's message, `left ‖ right ‖ "NODE"`, and arrives with
/// the running hash in words 0..8, where the last hash left its digest. `side` is 0 when the
/// running hash is the left child and 8 when it is the right one: it is moved to its half and
/// the sibling written to the other. The halves are picked by an offset, not a branch, so both
/// cases execute the same instructions (for a left child the move is a copy onto itself).
fn level(buf: &mut [u32; 17], sib: &Digest, side: usize) {
    let mut cur = [0u32; 8];
    cur.copy_from_slice(&buf[..8]);
    buf[side..side + 8].copy_from_slice(&cur);
    buf[8 - side..16 - side].copy_from_slice(sib);
    // SAFETY: the syscall reads the 17 words of `buf` and overwrites its first 8.
    unsafe { poseidon2(buf.as_mut_ptr(), 17) };
}

/// Walks two leaves up the same path at once and returns the root each one reaches. The path
/// is the eight siblings whose words start at private input `at`; bit `d` of `index` says
/// whether the running hash is the right child at level `d`. An update needs both walks (the
/// leaf before and the leaf after), and doing them together reads each sibling once.
fn roots_from(before: Digest, after: Digest, index: u32, at: u32) -> (Digest, Digest) {
    let (mut a, mut b) = ([0u32; 17], [0u32; 17]);
    a[..8].copy_from_slice(&before);
    b[..8].copy_from_slice(&after);
    a[16] = NODE_TAG;
    b[16] = NODE_TAG;
    for d in 0..DEPTH {
        let side = 8 * ((index >> d) & 1) as usize;
        let sib: Digest = core::array::from_fn(|k| read_input(at + (8 * d + k) as u32));
        level(&mut a, &sib, side);
        level(&mut b, &sib, side);
    }
    let (mut ra, mut rb) = ([0u32; 8], [0u32; 8]);
    ra.copy_from_slice(&a[..8]);
    rb.copy_from_slice(&b[..8]);
    (ra, rb)
}

fn read_u64(at: u32) -> u64 {
    read_input(at) as u64 | (read_input(at + 1) as u64) << 32
}

/// Applies the transfer whose words start at `at` to the tree with root `root`.
fn apply(root: Digest, at: u32) -> Result<Digest, u32> {
    let from = read_input(at);
    let to = read_input(at + 1);
    let amount = read_u64(at + 2);
    let secret = [read_input(at + 4), read_input(at + 5), read_input(at + 6), read_input(at + 7)];
    let sender_balance = read_u64(at + 8);
    let sender_nonce = read_input(at + 10);
    let receiver = [read_input(at + 75), read_input(at + 76), read_input(at + 77), read_input(at + 78)];
    let receiver_balance = read_u64(at + 79);
    let receiver_nonce = read_input(at + 81);

    if from >= 1 << DEPTH || to >= 1 << DEPTH {
        return Err(BAD_INDEX);
    }
    if from == to {
        return Err(SELF_TRANSFER);
    }

    // The sender's leaf is in the tree, and the caller knows the secret behind its owner id.
    // The second walk carries the debited leaf up the same path: the root after the debit.
    let sender = owner_of(&secret);
    let (seen, debited) = roots_from(
        leaf(&sender, sender_balance, sender_nonce),
        leaf(&sender, sender_balance.wrapping_sub(amount), sender_nonce.wrapping_add(1)),
        from,
        at + 11,
    );
    if seen != root {
        return Err(BAD_SENDER);
    }
    if amount > sender_balance {
        return Err(INSUFFICIENT);
    }

    // The receiver's leaf is in the tree the debit left behind; the second walk credits it.
    let (seen, credited) = roots_from(
        leaf(&receiver, receiver_balance, receiver_nonce),
        leaf(&receiver, receiver_balance.wrapping_add(amount), receiver_nonce),
        to,
        at + 82,
    );
    if seen != debited {
        return Err(BAD_RECEIVER);
    }
    if receiver_balance.checked_add(amount).is_none() {
        return Err(OVERFLOW);
    }
    Ok(credited)
}

/// Private input: [count, old root (8 words), then `count` transfers of 146 words each].
/// Outputs: [status, transfers applied, link (first 5 words), 0], where
/// link = Poseidon2(old root, new root, count, "LINK").
#[no_mangle]
pub extern "C" fn main() -> ! {
    let count = read_input(0);
    if count > MAX_TRANSFERS {
        write_output(0, TOO_MANY);
        halt();
    }
    let mut old = [0u32; 8];
    for (k, w) in old.iter_mut().enumerate() {
        *w = read_input(1 + k as u32);
    }

    let mut root = old;
    let mut done = 0;
    while done < count {
        match apply(root, 9 + TRANSFER_WORDS * done) {
            Ok(next) => root = next,
            Err(status) => {
                write_output(0, status);
                write_output(1, done);
                halt();
            }
        }
        done += 1;
    }

    let mut buf = [0u32; 18];
    buf[..8].copy_from_slice(&old);
    buf[8..16].copy_from_slice(&root);
    buf[16] = count;
    buf[17] = LINK_TAG;
    let link = hash(&mut buf, 18);

    write_output(0, OK);
    write_output(1, count);
    for k in 0..5 {
        write_output(2 + k as u32, link[k]);
    }
    halt();
}

زړه ریښه یو خصوصي ننوتی دی، او دا سم دی. هیڅ شی غوښتونکی نه مجبوروي چې له ریښتینې ریښې څخه پیل وکړي. هغه غوښتنه چې له یوې جوړې شوې ریښې پیلېږي، د داسې لېجر څخه یو سم ګام ثابتوي چې بل هیڅوک یې نه لري، او پیوند یې د هیڅ تعقیبوونکي له حالت سره سمون نه خوري. پیوند تړونکی دی: دا د دواړو ریښو یوه عامه ژمنه ده، او تعقیبوونکي یې له خپلو ریښو سره پرتله کوي.

ساتنه د جوړښت له مخې ده. کسر شوی مقدار او اعتبار شوی مقدار یو متغیر دی. د کتلو لپاره کومه جلا مجموعه نشته.

کسر او اعتبار په قصدي ډول د wrapping_ حساب کاروي. پروګرام د “وروسته” پاڼه مخکې له دې جوړوي چې پوه شي چې د “مخکې” پاڼه ریښتینې ده، ځکه چې دواړه یوځای پر لار ځي. که مقدار له بیلانس څخه ډېر وي، تاو شوې پاڼه حسابېږي او بیا هغه مهال غورځول کېږي چې دوه کرښې وروسته کتنه د 4 حالت راګرځوي.

نانس په هر کسر سره زیاتېږي، نو د لېږونکي پاڼه حتی هغه مهال هم بدلېږي چې بیلانس پخواني ارزښت ته راګرځي. تعقیبوونکي لیدلی شي چې یو حساب څو ځله لګښت کړی دی.

4. د وسیلو د زنځیر دوه حقیقتونه چې دا مېلمه پروګرام ورسره مخ شو

rand-guest check د اوږدوالي پورې تړلی Poseidon2 ردوي. SDK دوه Poseidon2 غوښتنې لري. poseidon2 (سیسکال 3) ډکول نه کوي: د تر څلورو کلمو یو پیغام هماغه هش لري لکه هماغه پیغام چې صفر کلمې ورپسې ور زیاتې شوې وي. poseidon2_len (سیسکال 7) اوږدوالی په سپنج کې کري او د متغیر اوږدوالي لرونکو معلوماتو لپاره خوندي اصلي انتخاب دی. ماشین دواړه پلي کوي، خو په circuits aeacf31 کې د کتونکي د سیسکالونو لست په 6 کې دریږي:

$ rand-guest build my-guests/ledger --max-words 65535
…
0x00001114  0x00000073  Syscall: ecall with a7 = 7, which is no syscall the machine implements
…
Error: the image is rejected by the checker (it was still written)

نو دا مېلمه پروګرام poseidon2 کاروي او هغه قاعده تعقیبوي چې نه ډک شوی سپنج خوندي کوي: هر پیغام یو ثابت اوږدوالی لري، او د پیغام هر ډول خپله نښه کلمه لري.

په یوه لوپ کې د بفر صفر کول تر ټولو ګرانه کرښه ده چې تاسو به یې پام ونه کړئ. د Merkle د تګ لومړۍ نسخه هغه څرګنده وه: یو فنکشن چې د هرې غوټې لپاره یې یو تازه [0u32; 17] جوړاوه، دواړه ماشومان یې پکې کاپي کول، هش یې کاوه، او ډایجسټ یې راګرځاوه. څلور لېږدونو 26,124 سایکلونه ونیول او په 14 پوړ کې نه ځایېدل، چې تر ټولو لوړ پوړ دی چې یوه غوښتنه یې کارولی شي. درې بدلونونه، چې یو هم هغه څه ته لاس نه وروړي چې ثابتېږي:

بدلوند څلورو لېږدونو لپاره سایکلونه
د هرې غوټې لپاره یو تازه صفر شوی بفر او د لارې یو اری26,124
د هر تګ لپاره یو بفر، یو ځل صفر شوی؛ د لارې صفر ډک اری نشته15,897
د مخکې او وروسته تګونه یوځای ترسره شوي، هره خور یو ځل لوستل شوې15,445
روان هش په هش بفر کې ساتل شوی، هلته چې سیسکال یې پرېږدي12,393

په یوه لوپ کې [0u32; 17] په هره تکرار کې په یوې memset غوښتنې کمپایل کېږي. سیسکال خپل ډایجسټ د خپل بفر پر لومړیو اتو کلمو لیکي، نو د پیغام د left ‖ right ‖ tag په بڼه ځای پر ځای کول دا معنا لري چې ډایجسټ دمخه هلته دی چېرې چې د راتلونکې کچې کیڼ ماشوم ځي. llvm-objdump -d د target/riscv32im-unknown-none-elf/release/ لاندې پر ELF ښيي چې سایکلونه چېرې دي.

5. د کوربه اړخ

د لارو جوړول له ماشین بهر Poseidon2 ته اړتیا لري. د circuits چک‌اوټ یې لري: rand_zkvm::hash::sponge_hash د هغه سپنج لپاره د کوربه اړخ فنکشن دی چې د POSEIDON2 سیسکال یې حسابوي. ledger-host یو کوچنی Rust باینري دی چې د لارې له مخې پرې تکیه کوي:

[dependencies]
rand_zkvm = { path = "../../research" }
ledger-host owner   <secret: 32 hex>                   the owner id of a secret
ledger-host genesis <accounts file> <state file>       build a tree, write its state
ledger-host root    <state file>                       the state's root
ledger-host step    <state file> <transfers file> <new state file>
                                                       stdout: the private words
                                                       stderr: old root, new root, link
ledger-host link    <old state file> <new state file> <count>
                                                       the link a receipt must show

د حالت فایل د owner balance nonce له 256 کرښو جوړ دی. د نندارې لېجر شپږ تمویل شوي حسابونه لري. د هغوی رازونه په demo-accounts.txt کې دي او، لکه په دې پاڼو کې د هرې ننداري په څېر، په قصدي ډول عامه دي. بیلانسونه د 10⁻⁸ په واحدونو کې دي.

$ cd my-guests/ledger-host && cargo +1.98.1 build --release && cd ../ledger
$ alias ledger-host=../ledger-host/target/release/ledger-host
$ cat demo-accounts.txt
de0f9e0173fe8358df1de251d81b4bec 50000000000
350826bc703c63ee3497f907373397f0 12000000000
8d5684dd764886843e4660d2c1ef12cc 7550000000
89442144b6673a3f93a5aa2359ddc6cf 0
99e9f82eede0b8b9feca124c0e1808be 100000000000
f29afdfbf0b821aa6c52f21cedc4c14a 4200000000
$ ledger-host genesis demo-accounts.txt state-0.txt
root bd1e27e17c0a0fe6e102f4c2197ed8721f22fccc99657860f035d1f5a4d1a568

د لېږدونو فایل په هره کرښه کې یو from to amount secret لري:

$ cat batch-1.txt
0 3 12500000000 de0f9e0173fe8358df1de251d81b4bec
4 1 99900000000 99e9f82eede0b8b9feca124c0e1808be
3 2 2500000000 89442144b6673a3f93a5aa2359ddc6cf
1 5 1500000000 350826bc703c63ee3497f907373397f0
$ ledger-host step state-0.txt batch-1.txt state-1.txt > witness-1.txt
old root bd1e27e17c0a0fe6e102f4c2197ed8721f22fccc99657860f035d1f5a4d1a568
new root c021ecd07d7ca8a04ee6dd7fce48052f2a88cf2b38040387066849b8e2e87e91
link bcf87def900bb2978b6c24bfaaf6c9d357d58ba9
link words 3170401775 2416685719 2339120319 2868300243 1473612713
$ head -6 state-1.txt
ff2a2fadae3eb422e7f8810b701d45c6 37500000000 1
d6f41168e6db1d63402588a7d50dda9c 110400000000 1
d240d9f11db1ed16b2902e4533c17015 10050000000 0
b357d759652ee04004d9c5c21a026d17 10000000000 1
7a179726cd16187bb760c0272c011cab 100000000 1
c393d03f75fcadfd1f1d7102040fc47b 5700000000 0

شپږ بیلانسونه لا هم 1,737.5 ته رسېږي. شاهد 593 کلمې دی: شمېر، زړه ریښه او د هر لېږد لپاره 146.

6. جوړول او چلول

$ rand-guest build my-guests/ledger --max-words 65535
773 words against a cap of 65535 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/ledger/image.bin and its .sha256 (773 words, hc 6bb2eccbe70a562774053da4631777d84f2bc68361f30f9be5c4aa16f0153814, program id c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab)
$ rand-guest run my-guests/ledger/image.bin --input $(cat witness-1.txt) --tier 14
out[0] = 1
out[1] = 4
out[2] = 3170401775
out[3] = 2416685719
out[4] = 2339120319
out[5] = 2868300243
out[6] = 1473612713
out[7] = 0
cycles 12394
tier 14
tier 14: fits (cycles 12739 of 16383, Poseidon2 permutations 1030 of 2048)

د پیوند پنځه کلمې د کوربه وسیلې له کلمو سره سمون خوري. ردونه، چې هر یو په امولېټر کې د یو لېږد لرونکې ډلې سره د state-0.txt پر وړاندې چلول شوی:

ډلهout[0], out[1]
حساب 2 له هغه څه ډېر لېږي چې لري یې4, 0
د حساب 2 لېږد د حساب 5 له راز سره2, 0
یو سم لېږد، بیا حساب 5 خپل ځان ته تادیه کوي6, 1
یو سم لېږد چې پر state-1.txt جوړ شوی او د زړې ریښې یوه کلمه یې بدله شوې2, 0
یو سم لېږد چې د ترلاسه کوونکي بیلانس پکې غلط ښودل شوی3, 0
د 5 شمېر7, 0

7. بودیجه

یو لېږدڅلور لېږدونه
خصوصي کلمې155593
سایکلونه3,22112,394
د Poseidon2 پرمیوټېشنونه، د ډایجسټونو په ګډونله 2,048 څخه 1,030
کچه1214

یو لېږد 37 هشونه دي: 32 غوټې (د اتو اتو غوټو دوه لارې، چې هره یوه د پاڼې لپاره له بدلون مخکې او وروسته وهل کېږي)، څلور پاڼې او د مالک یو پېژند. څلور لېږدونه د 14مې کچې د بودیجې شاوخوا 3,600 سایکلونه ناکارول شوي پرېږدي. یو بیلډ چې پکې سقف 5 ته لوړ شوی و، پنځه لېږدونه د 16,383 سایکلونو له جملې په 15,832 سایکلونو کې ترسره کړل. دا په بودیجه کې ځایېږي خو ډېره تشه نه پرېږدي، نو ځکه ځای پر ځای شوی پروګرام په 4 کې درېږي.

دلته Poseidon2 ارزانه هش دی. هر عادي سایکل 1 ګاز دی او د Poseidon2 هر جذب قطار 3، په داسې حال کې چې د SHA-256 یو کمپرېشن 64 او د Keccak یو پرمیوټېشن 192 ګاز دی. د دې غوښتنې 149 Poseidon2 هشونه په ګډه شاوخوا 2,500 ګاز زیاتوي، نږدې هغومره چې 13 Keccak پرمیوټېشنونه لګښت لري. بې‌نومه رایه 12 ترسره کوي:

gas: 14110 (declaring 16384, tier 14)

12,740 قطارونه، هر یو په 1 ګاز، جمع د 685 جذب قطارونو د هر یوه لپاره 2 نور، 14,110 کېږي.

8. ځای پر ځای کول

پروګرام عامه برخه نه لري، نو پېژند یې هماغه دی چې rand-guest build چاپ کړ.

$ rand fee deploy 773
0.0783 RAND
$ rand program deploy my-guests/ledger/image.bin
program id: c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab (773 words, hc cbecb26b27560ae7a43d0574d877176383c62b4f9b0ff36116aac4e5143815f0)
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 10.8s: tier 10, 1370188 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 113.1s: tier 14, 1494406 bytes
submitted deploy 40a8e53d7e916c90db77504cf6601c443d7da0756e04c52745f350969659588f
  0 RAND out, 59.7762 RAND change, fee 0.0783 RAND, anchored at height 97945, proved in 113.1s (bundle) + 10.8s (auth)

یو ځای پر ځای شوی پروګرام هر شمېر دفترونو ته خدمت کوي. دوه دفترونه د خپلې پیدایښتي رېښې له مخې سره توپیر لري، او رسیدونه یې هېڅکله یو له بل سره نه تړل کېږي.

9. غوښتنه

$ rand call c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab \
    $(sed 's/[0-9][0-9]*/--input &/g' witness-1.txt)
proving the call locally (593 inputs stay private)…
gas: 14110 (declaring 16384, tier 14)
fee 0.007489462 RAND (incl. two price steps of headroom)
proved in 114.8s: tier 14, 1498589 bytes, outputs [1, 4, 3170401775, 2416685719, 2339120319, 2868300243, 1473612713, 0]
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 19.9s: tier 10, 1374567 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 134.0s: tier 14, 1495974 bytes
submitted call 0ec66eed3bdeefc4a95741d26ad84f0a2d8198c4af3f7b1ec778e0525fa56a99
  0 RAND out, 59.701731664 RAND change, fee 0.007489462 RAND, anchored at height 98577, proved in 134.0s (bundle) + 19.9s (auth)
{
  "h_in": "68ea34ef767a30987c155a5a3cf0e69e832166b54611510ce0abc3430b8ff44c",
  "h_pub": null,
  "height": 98831,
  "index": 0,
  "outputs": [
    1,
    4,
    3170401775,
    2416685719,
    2339120319,
    2868300243,
    1473612713,
    0
  ],
  "program": "c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab",
  "tier": 14,
  "tx": "0ec66eed3bdeefc4a95741d26ad84f0a2d8198c4af3f7b1ec778e0525fa56a99"
}
input transcript published; open it with `rand open-call 0ec66eed3bdeefc4a95741d26ad84f0a2d8198c4af3f7b1ec778e0525fa56a99`

حالت 1، څلور لېږدونه، او په شپاړسیزه بڼه تړنه bcf87def 900bb297 8b6c24bf aaf6c9d3 57d58ba9. h_pub null دی، ځکه پروګرام عامه برخه نه لري. زنځیر نه کوم حساب ساتي، نه کومه اندازه او نه کومه رېښه.

ثبوت 1,498,589 بایټه دی، چې د هغه غوښتنې د ثبوت اندازه ده چې هش جدول نه لري: Poseidon2 له وړاندې په هر ثبوت کې شته، خو Keccak او SHA-256 هر یو خپل جدول ورسره راوړي. غوښتنه د Apple Silicon په یوه لپټاپ له پنځو دقیقو یو څه زیاته وخت ونیو (115 s د غوښتنې د ثبوت لپاره، 20 s د لګښت د اجازې لپاره، 134 s د بنډل لپاره) او د حافظې لوړ حد یې 6.1 GB ته ورسېد.

10. پخپله یې تعقیب کړئ

یو تعقیبوونکی چې state-0.txt لري او state-1.txt ورکړل شي، رسید په یوه کمانډ کې ګوري:

$ ledger-host link state-0.txt state-1.txt 4
link bcf87def900bb2978b6c24bfaaf6c9d357d58ba9
link words 3170401775 2416685719 2339120319 2868300243 1473612713

دا پنځه کلمې د رسید له 2 تر 6 پورې وتنې دي، او 4 یې لومړۍ وتنه ده. بل شمېر، یا د هر یوه حالت په یوه واحد بدلون، بله تړنه ورکوي.

دویم ګام. د دې پاڼې لپاره دفتر یو ځل بیا مخ ته لاړ. batch-2.txt د state-1.txt پر بنسټ دوه لېږدونه دي:

$ cat batch-2.txt
2 0 1050000000 8d5684dd764886843e4660d2c1ef12cc
5 4 700000000 f29afdfbf0b821aa6c52f21cedc4c14a
$ ledger-host step state-1.txt batch-2.txt state-2.txt > witness-2.txt
old root c021ecd07d7ca8a04ee6dd7fce48052f2a88cf2b38040387066849b8e2e87e91
new root a1f79db3379bc2a06c6da4326f122bb7bec04689329b79ba76f931cbb6ef7b47
link 2e2738c49dc6edcfd1349a6932145c12b23d366b
link words 774322372 2647059919 3509885545 840195090 2990356075
$ rand call c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab \
    $(sed 's/[0-9][0-9]*/--input &/g' witness-2.txt)
…
gas: 7240 (declaring 8192, tier 14)
fee 0.00645165 RAND (incl. two price steps of headroom)
proved in 100.9s: tier 14, 1516155 bytes, outputs [1, 2, 774322372, 2647059919, 3509885545, 840195090, 2990356075, 0]
…
submitted call 590f9814a9de00e6d667ac17587b3ea9742b745b6b43657876c42bc095ce8a99

د دې زړه رېښه د لومړي ګام نوې رېښه ده، c021ecd0…7e91. همدا د ساتنې لړۍ ده: په زنځیر 18 کې، رسید 0ec66eed…6a99 پیدایښتي حالت له state-1 سره وتاړه او رسید 590f9814…8a99 state-1 له state-2 سره وتاړه. په زنځیر 20 کې همدا دوه ګامونه، په همدې رېښو سره، 9c9998bb…66eb او dd756a89…ffc2 دي. د نمایشي دفتر اوسنی حالت هماغه دی چې دا دوه بستې یې له demo-accounts.txt څخه جوړوي، نو هر څوک یې بیا جوړولی او درېیم ګام اخیستلی شي.

د غوښتنې پرانیستل. والټ د هرې غوښتنې خصوصي ننوتې په داسې پاکټ کې مهر کړې چې یوازې د غوښتونکي د کتنې کیلي یې پرانیستلی شي. غوښتونکی یې وروسته پرانیستلی شي، او والټ پروګرام په هغه څه بیا چلوي چې پکې یې مومي:

$ rand open-call 0ec66eed3bdeefc4a95741d26ad84f0a2d8198c4af3f7b1ec778e0525fa56a99
opened with the caller's viewing key: 593 input word(s)
inputs: [4, 3172870113, 2081034214, 3775067330, 427743346, 522386636, 2573564000, 4030058997, 2765202792, 0, 3, …]
emulator outputs: [1, 4, 3170401775, 2416685719, 2339120319, 2868300243, 1473612713, 0]
receipt outputs:  [1,4,3170401775,2416685719,2339120319,2868300243,1473612713,0]
verdict: faithful — these are the words the proof was made over, and they reproduce its outputs

په دې ننوتو کې زړه رېښه او هره بدله شوې پاڼه شته، نو یو پاکټ چې د یوه پلټونکي لپاره مهر شوی وي (rand call --auditor <rand1…>) یوه لاره ده چې د یوه ګام معلومات داسې ځای ته ورسېږي چې یو ټاکلی اړخ یې تل ترلاسه کولی شي. د v0.6.7 سره، open-call د دې پروګرام لپاره کار کوي او په نورو درېیو کې ناکامېږي: د هغه پروګرام لپاره چې عامه برخه لري، ننوتې پرانیزي او بیا په re-running the program on these inputs failed: PublicIndex(0) درېږي، ځکه بیا چلول د پروګرام له عامه ننوتې پرته ترسره کېږي.

هغه څه چې ثبوت یې تاسو ته نه درکوي

  • شتون. زنځیر ثابتوي چې هر ګام سم دی. دفتر نه ساتي. هر څوک چې یو ګام جوړوي باید نوې پاڼې خپرې کړي، که نه تعقیبوونکي بل ګام نشي اخیستلی. هغه غوښتونکی چې یو ګام ثابت کړي او معلومات پټ وساتي، دفتر یې په همدې ځای کې د هر هغه چا لپاره کنګل کړی چې رسید مني. نو یو ریښتینی ځای پر ځای کول دې لپاره یو قاعدې ته اړتیا لري: یوه کمېټه چې د معلوماتو لپاره لاسلیک کوي، یا د بدلو شویو پاڼو په عامه ځای کې خپرول.
  • د ګډونوالو ترمنځ محرمیت. زنځیر له یوه شمېر او یوه هش پرته هېڅ نه ویني. خو ورکوونکی باید د ترلاسه کوونکي پاڼه وړاندې کړي، نو ورکوونکی د ترلاسه کوونکي بیلانس پېژني، او هر څوک چې د حالت فایل لري، ټول بیلانسونه لري. د Rand خپل shielded حوض له دې ستونزې د حسابونو پر ځای د نوټونو په کارولو سره ځان ساتي (نوټونه او د ژمنې ونه). دا بېلګه د کوچني پاتې کېدو لپاره حسابونه ساتي.
  • ترتیب. له یوې رېښې څخه دوه سم ګامونه سره ټکر کوي، او یوازې هغه حسابېږي چې د زنځیر په ترتیب کې لومړی راشي. دویم غوښتونکي د داسې رسید پیسې ورکړې چې هېڅوک یې نه مني، او باید په نوې رېښه بیا جوړ کړي.
  • له RAND یا له یوه ټوکن سره تړاو. بیلانسونه په یوه ونه کې شمېرې دي. هېڅ شی یې shielded حوض ته نه ننباسي او نه ترې باسي: د پروګرام وتنې معلومات دي، او زنځیر پر هېڅ یوه یې عمل نه کوي.
  • هغه لېږدونه چې د لرونکي لخوا لاسلیک او د بل چا لخوا ثابت شي. د حساب درلودل د یوه هش د مخکینۍ بڼې (preimage) پېژندل دي، او دا مخکینۍ بڼه یوه خصوصي ننوته ده، نو ثابتوونکی باید پخپله لرونکی وي. سپارنه به په پروګرام کې دننه د لاسلیک کتنې ته اړتیا ولري. د Dilithium یا ECDSA تایید د 14مې کچې په یوه غوښتنه کې نه ځایېږي.

وروستی ځل د 2026-09-30 په نېټه د fullnode د 48dc04b کمېټ (زنځیره 18) پر وړاندې تایید شوی، او circuits `aeacf31` (`guest-sdk/src/lib.rs`, `research/src/hash.rs`, `rand-guest`), the commit fullnode v0.6.7 vendors; every `rand-guest`, `ledger-host` and `rand` output on this page was produced on 2026-09-30, the deploy and the call on chain 18. چېرې چې کوډ او دا پاڼه سره توپیر ولري، کوډ سم دی؛ موږ ته خبر راکړئ.