यह पृष्ठ Rand पर अपना स्वयं का प्रोग्राम लाने के लिए आवश्यक सब कुछ बताता है: कोई प्रोग्राम क्या हो सकता है, वह क्या पढ़ता है और क्या प्रकाशित करता है इसकी रूपरेखा कैसे बनाएँ, और एक संपूर्ण उदाहरण जो दो बार लिखा गया है, एक बार Rust में और एक बार C में। इसके बाद यह परीक्षण, प्रूविंग बजट, डिप्लॉय करने, कॉल करने और इस बात को समझाता है कि कोई भी परिणाम की जाँच कैसे कर सकता है। नीचे दिया गया हर rand-guest आउटपुट एक वास्तविक रन से लिया गया है और ठीक वैसे ही चिपकाया गया है जैसा प्रिंट हुआ था। ये रन 2026-09-25 को circuits कमिट 7ef3220 पर किए गए थे। testnet अब chain 20 है, जिसका टूलचेन, chains 18 और 19 की तरह, aeacf31 पर बना है (ब्रांच feat/v067-circuits; fullnode v0.6.8 भी इसी कमिट पर पिन है), और ये आउटपुट वहाँ दोबारा नहीं चलाए गए हैं। जहाँ SDK या chain में अंतर है, वहाँ पृष्ठ यह बताता है।
दो छोटे ट्यूटोरियल त्वरित मार्ग हैं: 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 है। उदाहरण प्रोग्राम यहाँ से आगे बड़े प्रोग्रामों के साथ बढ़ता है और बताता है कि chain 18 की gas मीटरिंग ने क्या बदला। यह पृष्ठ RPL-1 प्रोग्रामों को कवर करता है, जिन्हें एक Call स्टेटलेस रूप से चलाता है; chain 20 में RPL-2 प्रोग्राम स्टेट भी है, यानी cells और एक vault जिन्हें एक Invoke बदलता है (प्रोग्राम), और इसके संपूर्ण उदाहरण randprotocol/example-programs में हैं।
1. प्रोग्राम क्या हो सकता है
प्रोग्राम कोई भी ऐसी गणना है जो इस ढाँचे में आती हो:
- यह इनपुट के रूप में 32-बिट वर्ड पढ़ता है।
- यह आउटपुट के रूप में अधिकतम आठ 32-बिट वर्ड प्रकाशित करता है।
- यह नियतात्मक (deterministic) है: समान इनपुट से सदैव समान आउटपुट मिलते हैं।
- यह अपने टियर के बजट के भीतर रुक जाता है (खंड 7)।
इन सीमाओं के भीतर आप कुछ भी लिख सकते हैं। आपको लूप, रिकर्शन, स्ट्रक्ट, ऐरे, 64-बिट अंकगणित, क्रेट्स, और यदि आप एलोकेटर लाएँ तो हीप भी मिलता है। फिर भी, मशीन शुद्ध RV32IM है, और उसके नीचे कोई ऑपरेटिंग सिस्टम नहीं है:
| आपके पास है | आपके पास नहीं है |
|---|---|
RV32IM पूर्णांक निर्देश, MUL/DIV/REM सहित | फ़्लोटिंग-पॉइंट हार्डवेयर (Rust के सॉफ़्टवेयर फ़्लोट काम करते हैं, धीरे) |
डिफ़ॉल्ट लिंकर स्क्रिप्ट में 0x1000 से शुरू होकर 1 MiB तक RAM, 64 KiB स्टैक के साथ | हीप, जब तक आप एलोकेटर न दें (खंड 6) |
| syscalls के रूप में Poseidon2, Keccak-f और SHA-256 | फ़ाइलें, नेटवर्क, घड़ियाँ, रैंडमनेस, थ्रेड, std, libc |
no_std Rust क्रेट्स और स्व-निहित C | ऐसी स्टेट जो एक कॉल से अगली कॉल तक बनी रहे |
| आठ सार्वजनिक आउटपुट वर्ड | प्रोग्राम के भीतर से RAND या टोकन स्थानांतरित करने का कोई तरीका |
प्रोग्राम की रूपरेखा बनाते समय अंतिम पंक्ति सबसे अधिक मायने रखती है। आउटपुट डेटा हैं, प्रभाव नहीं। एक कॉल यह प्रमाणित करती है कि किसी दिए गए डाइजेस्ट वाला प्रोग्राम कुछ निजी इनपुट पर चला और उसने आठ वर्ड प्रकाशित किए। chain प्रमाण का सत्यापन करती है और आठ वर्ड को एक रसीद में संग्रहीत करती है। यह कोई बैलेंस नहीं बदलती और कुछ भी ट्रिगर नहीं करती। वर्ड का जो भी अर्थ हो, वह प्रोग्राम, उसके कॉलर और रसीद पढ़ने वाले के बीच तय होता है। प्रोग्राम उन बातों की सूची देता है जिन्हें chain अभी समर्थित नहीं करती।
2. पहले इंटरफ़ेस की रूपरेखा बनाएँ
एक प्रोग्राम के तीन चैनल होते हैं। कोई भी कोड लिखने से पहले तय करें कि प्रत्येक पर क्या जाएगा।
| चैनल | कब निर्धारित | किससे पढ़ा जाता है | कौन देखता है | chain 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 वर्ड |
निजी इनपुट अपने आप में दुनिया के बारे में कुछ प्रमाणित नहीं करते
एक निजी इनपुट सॉल्टेड प्रतिबद्धता (commitment) H_IN द्वारा प्रमाण से बँधा होता है, इसलिए प्रूविंग के बाद वह बदल नहीं सकता। लेकिन प्रोग्राम के पास यह जानने का कोई तरीका नहीं है कि इनपुट सत्य है। कॉलर कोई भी संख्या टाइप कर सकता है। किसी निजी इनपुट को अर्थपूर्ण बनाने के लिए, उसे प्रोग्राम के भीतर किसी सार्वजनिक चीज़ से बाँधें:
- पहले प्रकाशित की गई एक प्रतिबद्धता (सार्वजनिक खंड में, या सत्यापनकर्ता को अलग माध्यम से दी गई) जिसे निजी इनपुट को खोलना होगा, जैसा कि नीचे की नीलामी में है;
- एक Merkle root जिसके अंतर्गत किसी निजी लीफ़ को प्रमाणित करना होगा (इसके लिए syscall 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 guest स्लॉट 7 में
0xdeadbeef(3,735,928,559) लिखकर रुक जाता है। panic से पहले लिखे गए आउटपुट अपनी जगह बने रहते हैं, इसलिए पाठक को अन्य स्लॉट पर भरोसा करने से पहले स्लॉट 7 की जाँच करनी चाहिए (खंड 6)। - आपको दिए गए इनपुट से आगे पढ़ना एक ट्रैप है (एमुलेटर में
InputIndex(5))। पहले गिनती पढ़ें और उसकी जाँच करें।
3. संपूर्ण उदाहरण: सीलबंद-बोली नीलामी
एक विक्रेता हारने वाली बोलियों को प्रकाशित किए बिना second-price नीलामी चलाना चाहता है:
- प्रत्येक बोलीदाता एक बोली और एक यादृच्छिक 128-बिट सॉल्ट चुनता है, और विक्रेता को एक प्रतिबद्धता देता है:
("BID1", bidder index, bid, salt)पर Poseidon2। - विक्रेता रिज़र्व मूल्य और हर प्रतिबद्धता को सार्वजनिक खंड के रूप में रखकर नीलामी प्रोग्राम डिप्लॉय करता है। अब प्रोग्राम id इसी नीलामी को नामित करती है, किसी और को नहीं।
- प्रत्येक बोलीदाता अपनी बोली और सॉल्ट विक्रेता को निजी रूप से भेजता है।
- विक्रेता हर बोली और सॉल्ट को निजी इनपुट के रूप में देकर प्रोग्राम को कॉल करता है। प्रोग्राम प्रत्येक ओपनिंग को उसकी प्रतिबद्धता से जाँचता है, विजेता चुनता है, और
[status, winner, price, bidder count]प्रकाशित करता है।
chain को पता चलता है कि कौन जीता और किस मूल्य पर। वह हारने वाली बोलियाँ कभी नहीं देखती। जो विक्रेता कोई भी बोली बदलता है, उसे परिणाम के बजाय BAD_OPENING मिलता है, क्योंकि प्रतिबद्धताएँ प्रोग्राम id में स्थिर हैं।
सब कुछ circuits checkout के भीतर रहता है, क्योंकि rand-guest build guest की डायरेक्टरी से ऊपर की ओर खोजकर 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 चाहिए जैसा मशीन उसकी गणना करती है। सबसे सरल सही उपकरण एक छोटा guest है जो इसकी गणना करता है और एमुलेटर पर चलता है। एक डाइजेस्ट आठ वर्ड का होता है, जो ठीक आउटपुट स्लॉट की संख्या है।
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) है, जो लंबाई को बाँधता है (syscall 7), लेकिन उस कमिट पर rand-guest check syscall 7 को अस्वीकार करता है, इसलिए ऐसा guest अभी बिल्ड नहीं होता (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 प्रोग्राम डिप्लॉय करें)।
चार बोलीदाता, इन बोलियों और सॉल्ट के साथ:
| बोलीदाता | बोली | साल्ट (चार शब्द) |
|---|---|---|
| 0 | 400 | 2654435769 2135587861 4087136352 1559071514 |
| 1 | 250 | 461845907 3432918353 3864292196 2246822507 |
| 2 | 900 | 3266489909 668265263 374761393 3550635116 |
| 3 | 300 | 4252018373 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 crate में रखें। guest-sdk, riscv32 के अतिरिक्त किसी भी लक्ष्य के लिए बिल्ड होने से इनकार करता है, इसलिए कोई guest crate आपके लैपटॉप पर cargo test नहीं चला सकता। जिस लाइब्रेरी crate की 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 दोनों crates पर पथ (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 से होकर जाता है, जो प्रत्येक स्लॉट को एक बार लिखता है और रुक जाता है। लूप की सीमा सार्वजनिक खंड से आती है, जो डिप्लॉय के समय निश्चित हो जाता है। इसलिए कोई कॉलर प्रोग्राम से कमिट की गई बोलियों से कम बोलियाँ नहीं पढ़वा सकता।
बिल्ड करें और चलाएँ
$ 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 के स्थान पर 260 | 3 1 0 0 | बोलीदाता 1 पर BAD_OPENING; और कुछ प्रकाशित नहीं होता |
| आरक्षित मूल्य 100 के स्थान पर 1,000 (भिन्न सार्वजनिक खंड, इसलिए भिन्न program id) | 2 0 0 4 | NO_SALE: कोई भी बोली आरक्षित मूल्य तक नहीं पहुँची |
5. वही प्रोग्राम C में
C guest निर्देशिका में .c फ़ाइलें और आपके अपने हेडर होते हैं। rand-guest build --lang c, guest.h, _start और एक छोटा रनटाइम प्रदान करता है (RISC-V प्रोग्राम डिप्लॉय करें)। यह निर्देशिका की प्रत्येक .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 भिन्न हैं। जिसका स्रोत आप प्रकाशित करें, उसी को डिप्लॉय करें।
6. भाषा संबंधी टिप्पणियाँ
Rust
Crate। #![no_std], #![no_main], और एक #[no_mangle] pub extern "C" fn main() -> ! जो halt() पर समाप्त होता है। रिलीज़ प्रोफ़ाइल panic = "abort" सेट करती है। एक खाली [workspace] तालिका crate को किसी भी पैरेंट वर्कस्पेस से बाहर रखती है। .cargo/config.toml और अपने स्वयं के कंपाइलर फ़्लैग न जोड़ें: rand-guest build पहले को अस्वीकार करता है और दूसरे को पूरा स्वयं सेट करता है (Rust प्रोग्राम डिप्लॉय करें)।
पैनिक। 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 काम करने लगता है। एक निश्चित एरीना पर बम्प एलोकेटर उस प्रोग्राम के लिए उपयुक्त है जो एक बार चलकर समाप्त हो जाता है:
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) };
एरीना शून्यीकृत है, इसलिए यह .bss में रहता है और इमेज में कोई शब्द नहीं जोड़ता। डिफ़ॉल्ट लिंकर स्क्रिप्ट 1 MiB RAM देती है, और एरीना, आपके statics और 64 KiB स्टैक को इसके भीतर समाना होगा। इस एलोकेटर के साथ Vec, collect() और iter().sum() सभी बिल्ड होते हैं और check पास करते हैं। फिर भी alloc और उसकी निर्भरताएँ कोड जोड़ती हैं: यह परीक्षण प्रोग्राम 1,341 शब्दों का है, जबकि नीलामी 258 शब्दों की है। खंड 7 बताता है कि प्रोग्राम का आकार प्रूविंग बजट क्यों खर्च करता है।
फ़्लोटिंग पॉइंट। लक्ष्य में कोई फ़्लोट निर्देश नहीं हैं, और Rust f32/f64 को सॉफ़्टवेयर रूटीनों में बदल देता है। वे काम करते हैं: 9, 2, 7, 4 और 5 का f64 में परिकलित माध्य 5.4f32 के बिट्स के रूप में लौटा। प्रत्येक पर कई साइकिल लगती हैं। जहाँ संभव हो, पूर्णांक या फ़िक्स्ड पॉइंट का उपयोग करें।
Crates। crates.io के no_std crates किसी भी अन्य निर्भरता की तरह बिल्ड होते हैं। crc crate द्वारा hello, rand का CRC-32, एमुलेटर पर चलाने पर, 2124296908 प्रिंट हुआ, जो वही मान है जो Python का zlib.crc32 देता है। जिन crates को std चाहिए, वे इस लक्ष्य के लिए बिल्ड नहीं होते। rand-guest build, --locked पास नहीं करता, इसलिए अपना Cargo.lock कमिट करें: इसके बिना, महीनों बाद का रीबिल्ड किसी नए crate को चुन सकता है और भिन्न 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)
यहाँ दोषी slice::sort_unstable था, एक ऐसे Vec पर जिसकी लंबाई केवल रन टाइम पर ज्ञात होती है। स्थिर sort() ने भी यही किया, जबकि 16 तत्वों वाली निश्चित ऐरे पर sort_unstable पास हो गया। ऐसे निष्कर्ष का स्रोत खोजने के लिए guest के ELF को डिसअसेंबल करें और check द्वारा प्रिंट किए गए पते के आसपास के फ़ंक्शन को देखें:
llvm-objdump -d -C my-guests/heap/target/riscv32im-unknown-none-elf/release/heap | grep -B12 unimp
फिर उस कॉल को अपने स्वयं के कोड से बदल दें (इस प्रोग्राम के लिए, एक छोटा इंसर्शन सॉर्ट)। इसी कारण guest-sdk का अपना halt() किसी noreturn वादे के बजाय loop {} पर समाप्त होता है।
C
- प्रवेश बिंदु
void main(void)है, और यहrand_halt()पर समाप्त होता है। साथ दिया गया_startस्टैक सेट करता है और इसे कॉल करता है। - कोई libc नहीं है। न
printf, नmalloc, न<string.h>। रनटाइमmemcpy,memmove,memsetऔरmemcmp(clang struct प्रतियों और बड़े प्रारंभकों के लिए इन्हें कॉल करता है) तथा चार 64-बिट विभाजन सहायक प्रदान करता है। ये सभी बाइट लूप हैं, इसलिए हॉट कोड में शब्दों के स्तर पर कॉपी और विभाजन करें। हीप के लिए, Rust की तरह, किसीstaticऐरे पर एक बम्प एलोकेटर लिखें। - अपरिभाषित व्यवहार पकड़ा नहीं जाता। कोई पैनिक हैंडलर नहीं है। सीमा से बाहर का लेखन चुपचाप मेमोरी को दूषित कर देता है, और असंरेखित लोड या स्टोर ट्रैप करता है, जिसका अर्थ है कि रन सिद्ध नहीं किया जा सकता। सूचकांकों को अपनी सीमाओं के विरुद्ध जाँचें, जैसे
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 क्रमचय: अधिकतम
2ᵗ⁻³।
| टियर | पंक्तियाँ | Poseidon2 क्रमचय | हैश तालिका के बिना गैस सीमा | chain 20 पर कॉल |
|---|---|---|---|---|
| 10 | 1,023 | 128 | 1,279 | स्वीकृत |
| 12 | 4,095 | 512 | 5,119 | स्वीकृत |
| 14 | 16,383 | 2,048 | 20,479 | स्वीकृत |
| 16 | 65,535 | 8,192 | 81,919 | v0.5.6 से अस्वीकृत |
| 18, 20 | 2¹⁸ − 1, 2²⁰ − 1 | 2¹⁵, 2¹⁷ | 327,679, 1,310,719 | v0.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 क्रमचय 192 का और एक SHA-256 संपीड़न 64 का। प्रूफ़ एक घोषित गैस सीमा प्रकाशित करता है, सर्किट यह सुनिश्चित करता है कि रन की गैस उसके बराबर या उससे कम हो, और सटीक गैस निजी रहती है। हैश तालिका के बिना कोई टियर जो अधिकतम सीमा घोषित कर सकता है, वह (2ᵗ − 1) + 2ᵗ⁻² है, जो ऊपर का स्तंभ है। न्यूनतम शुल्क है
floor = BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉
जहाँ BUNDLE_BASE 0.001 RAND है, बाइट्स कॉल प्रूफ़ और इनपुट एनवेलप के हैं, और कीमतें 100 इकाई प्रति गैस और 800 इकाई प्रति KiB से शुरू हुईं (एक इकाई 10⁻⁹ RAND है) और प्रत्येक ब्लॉक में थोड़ा बदलती हैं। chain 20 पर वे 10,000 और 80,000 इकाइयों से ऊपर नहीं जा सकतीं, और केवल कॉलों के प्रूफ़ और एनवेलप बाइट्स ही बाइट कीमत को बदलते हैं; chain 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 × 7 | 8 |
कुल, जैसा run रिपोर्ट करता है | 128 में से 88 |
पंक्तियाँ: 641 चक्र और डाइजेस्ट की पंक्तियाँ मिलाकर 1,023 में से 721 हुईं।
chain 20 पर, chain 19 की तरह, वॉलेट की अपनी गणना rand-guest run की गणना से थोड़ी अधिक होती है। वहाँ कॉल प्रूफ़ अपने ट्रांज़ैक्शन से बँधा होता है (chain 20 पर इस बंधन में genesis हैश भी शामिल है, उन्हीं आठ शब्दों में), और यह बंधन सार्वजनिक खंड के आठ अतिरिक्त शब्दों के रूप में जाता है: सार्वजनिक खंड वाले प्रोग्राम के लिए दो अतिरिक्त डाइजेस्ट पंक्तियाँ और दो अतिरिक्त क्रमचय, और सार्वजनिक खंड रहित प्रोग्राम के लिए प्रत्येक का एक अतिरिक्त।
इससे प्रोग्राम का आकार बजट का हिस्सा बन जाता है। heap परीक्षण प्रोग्राम केवल 1,183 चक्र चला, फिर भी उसके 1,341 शब्दों ने 340 क्रमचय लिए और उसे टियर 12 पर पहुँचा दिया। टियर 14 के 2,048 क्रमचयों पर, लगभग 8,000 शब्दों से काफ़ी बड़े प्रोग्राम को कॉल किया ही नहीं जा सकता, चाहे वह कुछ भी करता हो। chain 20 पर सटीक सीमा सार्वजनिक खंड रहित प्रोग्राम और बिना इनपुट वाली कॉल के लिए 8,180 शब्द है, और इनमें से किसी के भी होने पर कम। chain 20 पर प्रोग्राम की अधिकतम सीमा 65,535 शब्द है, जैसी chain 15 पर थी, परंतु chain 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 reference लेते हैं, [u32; 50] पर keccak(&mut state) और [u32; 24] पर sha256_compress(&mut buf), जबकि 7ef3220 एक raw pointer लेता था। v0.5.6 से एक कॉल अधिकतम 128 Keccak-f क्रमचय और अधिकतम 128 SHA-256 संपीडन का उपयोग कर सकती है। chain 20 पर एक Keccak क्रमचय की लागत 192 गैस और एक SHA-256 संपीडन की 64 गैस भी है।
तालिका प्रूफ़ को बड़ा बनाती है, और chain 20 कॉल प्रूफ़ को 4,194,304 बाइट (4 MiB, जैसा chain 17 से 19 पर; chain 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 से ऊपर के बाइट पर शुल्क लगता था, प्रति KiB 1,000 इकाइयाँ (शुल्क)। जब तक किसी बाहरी हैश से मेल खाना अनिवार्य न हो, Poseidon2 का उपयोग करें।
प्रूफ़ बनाने के लिए मेमोरी और समय
प्रूफ़ बनाने का समय और मेमोरी टियर के साथ तेज़ी से बढ़ते हैं। टियर-10 की कॉल का प्रूफ़ लैपटॉप पर कुछ सेकंड में बन जाता है। chain 20 पर प्रत्येक डिप्लॉय और प्रत्येक कॉल बंडल-पक्ष के दो प्रूफ़ भी साथ ले जाती है: एक टियर-10 व्यय प्राधिकरण (कुछ सेकंड) और शुल्क चुकाने वाला टियर-14 बंडल (लैपटॉप पर लगभग दो मिनट)। chain 18 पर मापी गई चार कॉलों में से प्रत्येक में तीन से साढ़े पाँच मिनट लगे और उनकी अधिकतम मेमोरी 6.1 से 6.6 GB रही (उदाहरण प्रोग्राम)। chain 15 पर बंडल लगभग 100 s और लगभग 5.7 GB का एकल टियर-14 प्रूफ़ था। वैलिडेटर और एग्रीगेटर हार्डवेयर में मापे गए आँकड़े दिए गए हैं।
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 वॉलेट नहीं डालता। वॉलेट को सिंक करने, डिप्लॉय करने और कॉल करने के लिए सार्वजनिक 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) के विरुद्ध जाँचता है। फिर वह शुल्क चुकाने वाले शून्य-मूल्य स्व-हस्तांतरण बंडल का प्रूफ़ बनाता है, उसे सबमिट करता है और कमिट की प्रतीक्षा करता है। chain 20 पर, chain 17 से चली आ रही व्यवस्था की तरह, ये दो प्रूफ़ हैं, जिनमें से प्रत्येक के आउटपुट की अपनी पंक्ति होती है: एक टियर-10 व्यय प्राधिकरण (कुछ सेकंड, लगभग 1.37 MB) और टियर-14 बंडल (लगभग दो मिनट, लगभग 1.5 MB)। chain 15 पर यह लगभग 100 s और लगभग 1.42 MB का एक टियर-14 प्रूफ़ था। डिप्लॉय शुल्क में कोई गैस घटक नहीं है। नीलामी chain 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 डाइजेस्ट के आठों शब्दों में से प्रत्येक को big-endian में प्रिंट करता है, जबकि वॉलेट, rand program show और rand_getProgram प्रत्येक शब्द के बाइट little-endian में प्रिंट करते हैं, इसलिए आठ 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)के गुणज तक ऊपर पूर्णांकित करके प्राप्त होती है: tier 10 पर 256, tier 12 पर 1,024, tier 14 पर 4,096।--gas-limit maxउस tier की अधिकतम सीमा घोषित करता है और--gas-limit Nएक सटीक मान; - प्रूफ़ का मूल्य उसके वास्तविक आकार पर नहीं, बल्कि चेन की 4,194,304 बाइट की अधिकतम सीमा पर लगाया जाता है, क्योंकि chain 20 पर कॉल का प्रूफ़ अपने ट्रांज़ैक्शन से बंधा होता है और शुल्क पहले से तय होना आवश्यक है;
- मूल्य के दो चरणों का अतिरिक्त अंतर, अर्थात 1.125² का गुणक, क्योंकि कॉल का मूल्य तय होने के दो या तीन ब्लॉक बाद वह चेन पर दर्ज होती है।
--fee डिफ़ॉल्ट को बदल देता है, और rand fee call <tier> --gas <limit> --bytes <B> नोड से स्वयं न्यूनतम शुल्क पूछता है। chains 18 से 20 पर नीलामी की कॉल नहीं की गई है, इसलिए उसकी गैस पंक्ति और शुल्क यहाँ नहीं दिखाए गए हैं। एक गुमनाम मतदान में chain 18 पर की गई एक पूर्ण कॉल उसी रूप में दी गई है जैसे वॉलेट ने उसे प्रिंट किया था, 0.0062 RAND पर।
रसीद में tier, H_IN, H_PUB और आउटपुट [1, 2, 400, 4, 0, 0, 0, 0] होते हैं: वही शब्द जो एम्युलेटर ने प्रिंट किए थे, क्योंकि प्रूफ़ उसी निष्पादन का है। चारों बोलियाँ और साल्ट चेन पर नहीं हैं। --auditor <rand1…> इनपुट को एक नामित ऑडिटर के लिए भी सील करता है, और --no-envelope कोई ट्रांसक्रिप्ट प्रकाशित नहीं करता, जिसे बाद में पलटा नहीं जा सकता (Programs)।
10. परिणाम की जाँच करें
कोई बोलीदाता, या कोई और, विक्रेता पर भरोसा किए बिना नीलामी की जाँच कर सकता है:
- कोड। प्रकाशित स्रोत को उसी circuits कमिट पर, Rust 1.98.1 (या clang 23.1.1) के साथ
rand-guest buildसे दोबारा बिल्ड करें।hcकी तुलनाrand program showयाrand_getProgramसे मिलेcode_hashसे करें, बाइट-उलटी वर्तनी को ध्यान में रखते हुए (अनुभाग 8)। बिल्ड पुनरुत्पादनीय है, इसलिए दोनों किसी भी मशीन पर मेल खाते हैं। - सार्वजनिक सेगमेंट। इसे
rand_getProgramPublicसे प्राप्त करें, याrand call --expect-publicचलाएँ। जाँचें कि आपकी अपनी प्रतिबद्धता आपके इंडेक्स पर है। - रसीद।
rand receipt <tx hash>याrand_getReceipt: स्थिति1, विजेता और मूल्य, slot 7 का मान0xdeadbeefनहीं, औरprogramनीलामी की id के बराबर। - इनपुट, यदि आप उनके अधिकारी हैं। विक्रेता का
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 syscall 7 को सूचीबद्ध नहीं करता (zkp-circuits issue #1) |
check 0xc0001073 पर Csr रिपोर्ट करता है | लाइब्रेरी कोड unimp में कंपाइल हुआ; llvm-objdump से फ़ंक्शन खोजें और कॉल को बदलें (अनुभाग 6) |
run कहता है कि निष्पादन किसी tier में नहीं समाता, या tier 14 से अधिक की आवश्यकता है | बहुत अधिक पंक्तियाँ या Poseidon2 क्रमचय; प्रोग्राम छोटा करें, कम हैश करें, या कार्य को कई कॉलों में बाँटें और उनके बीच एक डाइजेस्ट आगे बढ़ाएँ |
| slot 7 = 3735928559 | Rust गेस्ट panic हुआ; उससे पहले के आउटपुट विश्वसनीय नहीं हैं |
trap: DoubleWrite(n) | आउटपुट slot n दो बार लिखा गया; हर निकास को एक ही फ़ंक्शन से होकर ले जाएँ |
trap: InputIndex(i) | प्रोग्राम ने ऐसा निजी इनपुट पढ़ा जो कॉलर ने नहीं दिया; पहले गिनती पढ़ें और जाँचें |
लोड या स्टोर पर trap: … | असंरेखित या सीमा से बाहर का पता; C में, सीमा से आगे का ऐरे इंडेक्स |
invalid value '0x…' for '--input' | कमांड लाइन पर इनपुट शब्द दशमलव में होते हैं |
कॉल CallTierTooHigh के साथ अस्वीकृत | प्रूफ़ tier 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 | गेस्ट डायरेक्टरी को चेकआउट के अंदर ले जाएँ (Rust प्रोग्राम डिप्लॉय करें) |