Rand Protocol
مستندات
مفاهیم اصلی

نوت‌ها و درخت تعهد

مدل نوت که هر انتقال بر پایهٔ آن ساخته می‌شود - تعهدها، nullifierها، envelopeها، درخت با عمق ۳۲، anchorها و پنجره‌های anchor و زمان - و نحوهٔ اسکن کیف پول برای یافتن نوت‌های خودش.

از این صفحه برای ساخت یا ممیزی هر چیزی استفاده کنید که استخر را می‌خواند: کیف پولی که نوت‌های خود را اسکن می‌کند، نمایه‌سازی که تعهدها و نال‌ی‌فایرها را دنبال می‌کند، یا ابزاری که بررسی می‌کند یک تراکنش چه چیزی منتشر می‌کند. ارزش در این زنجیره مجموعه‌ای از نوت‌هاست؛ زنجیره تعهدهای آن‌ها را نگه می‌دارد، نه محتوایشان را.

یک نوت

نوت عبارت است از { pk, from, amount, asset, time, r }: ۲۸ کلمه‌ی ۳۲ بیتی، یعنی 112 بایت، با چینش pk(8) from(8) amount_lo amount_hi asset time r(8). pk مقدار عمومی نوتِ مالک است (برگرفته از کلید مشاهده)، from مقدار عمومی فرستنده، amount یک u64 بر حسب واحد (1 RAND = 10⁹ units)، asset یک u32 (برای RAND برابر 0، و در غیر این صورت اندیس فشرده‌ی دفتر ثبت توکن‌ها برای یک توکن RPL، پل‌شده یا بومی؛ نگاه کنید به توکن‌های RPL)، time ارتفاع بلوکی که فرستنده هدف گرفته، و r هشت کلمه‌ی تصادفی برای پوشانندگی (blinding) است.

متن اصلی هرگز روی زنجیره ظاهر نمی‌شود. آنچه ظاهر می‌شود این است:

  • تعهد آن، cm = H(CM, words)، یک هش Poseidon2 از ۲۸ کلمه با برچسب دامنه‌ی 4، که به‌عنوان یک برگ به درخت افزوده می‌شود؛
  • پاکت آن، متن اصلی که برای نشانی مالک مهروموم شده است تا فقط مالک، یا فرستنده از راه کلید مشاهده‌ی خروجی‌اش، بتواند آن را بخواند. از زنجیره‌ی 18 به بعد (زنجیره‌ی 20 هم شامل است) پاکت یک یادداشت رمزگذاری‌شده نیز دارد. چند نوتی که زنجیره از روی فیلدهای عمومی محاسبه می‌کند اصلاً پاکت ندارند (پایین‌تر).

خرج کردن نوت، نال‌ی‌فایر آن را منتشر می‌کند: H(NF, nk, cm)، یک هش Poseidon2 با برچسب دامنه‌ی 3 از کلید مشاهده‌ی مالک و تعهد. بدون nk نمی‌توان نال‌ی‌فایر را به تعهدش پیوند داد. مجموعه‌ی نال‌ی‌فایرها همان چیزی است که از خرج مضاعف جلوگیری می‌کند.

پاکت

Envelope { kem_ct, to_receiver, to_sender, body }
بخشبایتچیست
kem_ct1,088یک کپسوله‌سازی ML-KEM-768 به نشانی گیرنده
to_receiver60کلید هر تراکنش TxKey که زیر راز کپسوله‌شده بسته‌بندی شده است
to_sender60همان TxKey که زیر کلید مشاهده‌ی خروجی فرستنده ovk بسته‌بندی شده است
body652نوت 112 بایتی و یک فیلد یادداشت 512 بایتی زیر TxKey (140 بایت، فقط خود نوت، تا زنجیره‌ی 17)

هر بخش با ChaCha20-Poly1305 رمز می‌شود، با یک nonce تصادفی 12 بایتی در ابتدا و یک برچسب 16 بایتی، و داده‌ی وابسته (associated data) یک برچسب است (rand-envelope-receiver، rand-envelope-sender، rand-envelope-body) که تعهد نوت پس از آن می‌آید؛ بنابراین کلید نمی‌تواند پاکتی را که خودش مهر نکرده باز کند و نوتِ بازشده به برگی که زیر آن قرار دارد گره خورده است. این 1,860 بایت متن رمزشده است؛ پیشوندهای طول در bincode آن را روی شبکه به 1,892 می‌رساند. جنسیس زنجیره‌ی 20 مقدار envelope_bytes: 1860 را تعیین می‌کند و دفتر کل آن پاکت نوتی با هر طول دیگر را رد می‌کند (envelope is 1348 bytes, this chain requires exactly 1860)؛ چیز دیگری درباره‌ی آن بررسی نمی‌کند. تا زنجیره‌ی 17 پاکت فیلد یادداشت نداشت و 1,348 بایت بود (1,380 روی شبکه)، و دفتر کل فقط سقف 2,048 بایت برایش می‌گذاشت، که هنوز قاعده‌ی زنجیره‌ای است که جنسیس آن envelope_bytes ندارد.

فیلد یادداشت به صورت len (u16 LE) ‖ UTF-8 text ‖ zero padding و 512 بایت است، پس حداکثر 510 بایت متن. هر پاکت نوت در زنجیره‌ی 20 آن را دارد، چه یادداشتی وجود داشته باشد چه نه: خروجی باقی‌مانده، خروجی ساختگی و پرداخت بدون یادداشت به یک شکل پر می‌شوند، و هیچ چیز روی زنجیره یادداشت، طول آن یا نبودنش را متمایز نمی‌کند. یادداشت نه در نوت است، نه در تعهد و نه در هیچ اثباتی. دقیقاً هر کسی که از پیش می‌تواند نوت را بخواند می‌تواند آن را بخواند: مالک، فرستنده از راه ovk، یا هر کسی که TxKey آن خروجی به او داده شده باشد. فیلد یادداشتِ معیوب (طول نادرست، UTF-8 نامعتبر، پدینگ ناصفر) به‌صورت «بدون یادداشت» باز می‌شود و هرگز نوت را از مالک نمی‌گیرد. یادداشت متنِ هر کسی است، چون هر کسی می‌تواند یک نوت ریز (dust) به یک نشانی عمومی بپردازد: آن را نامعتبر و نامطمئن نشان دهید، با جایگزینی نویسه‌های کنترلی و قالب‌بندی یونیکد، همان‌طور که کیف پول rand می‌کند. rand send --memo یکی را در خروجی پرداخت مهر می‌کند؛ rand notes --memo و rand history --memo آن را نشان می‌دهند.

درخت، لنگرها و پنجره‌ها

تعهدها به یک درخت Poseidon2 با عمق 32 افزوده می‌شوند، قدیمی‌ترین اول، و هرگز حذف نمی‌شوند. برگ i نوتِ iام است که زنجیره تا کنون ساخته؛ نوت‌های سپرده‌ی جنسیس نخستین برگ‌ها هستند. هر بسته (bundle) چهار برگ و چهار نال‌ی‌فایر اضافه می‌کند، هر چه جابه‌جا کند، ساختگی‌ها هم شامل است. برگ خالی همه‌صفر است و هر گره H(NODE, left, right) است (برچسب دامنه‌ی 7). گره فقط مرز (frontier) را نگه می‌دارد، بنابراین وضعیت اجماع هرگز برگ‌ها را در خود ندارد؛ rand_getWitness درخت کامل را در صورت نیاز از برگ‌های ذخیره‌شده بازسازی می‌کند، و برای همین گران‌ترین خواندنی است که یک گره ارائه می‌دهد.

یک بسته عضویت ورودی‌هایش را در برابر یک لنگر اثبات می‌کند، یعنی ریشه‌ی درخت. دو قاعده لنگر را درست نگه می‌دارند:

ثابتمقدارقاعده
ANCHOR_WINDOW256 بلوک؛ در زنجیره‌ی 20 برابر 1,024لنگر باید یکی از آخرین ریشه‌های پایان بلوک به اندازه‌ی window باشد؛ ریشه‌ای که درخت در میانه‌ی بلوک از آن می‌گذرد هرگز لنگر نیست
TIME_WINDOW256 بلوک؛ در زنجیره‌ی 20 برابر 1,024time بسته باید در بازه‌ی [height − window, height] باشد

هر دو پنجره 256 بلوک‌اند، مگر آنکه جنسیس proof_window_blocks را تعیین کند (v0.6.8، از 256 تا 4,096)، که هر دو را با هم جایگزین می‌کند. زنجیره‌ی 20 مقدار 1,024 را تعیین می‌کند؛ زنجیره‌های 14 تا 19 این فیلد را نداشتند.

پنجره‌ها وجود دارند چون اثبات‌سازی زمان واقعی می‌برد: اثبات بسته‌ی tier-14 روی یک لپ‌تاپ حدود 100 ثانیه اندازه‌گیری می‌شود (و از زنجیره‌ی 17 کیف پول یک اثبات احراز دوم از tier-10 هم می‌سازد، حدود 7 ثانیه). 256 بلوک با بلوک‌های 1.17 ثانیه‌ای زنجیره‌ی 18 حدود پنج دقیقه بود، و یک اثبات واگذارشده روی یک اثبات‌کننده‌ی کند در 294 ثانیه رد شد؛ 1,024 بلوکِ زنجیره‌ی 20 با 1.34 ثانیه‌ای که explorer در آنجا گزارش داد حدود 23 دقیقه است. کیف پولی که در این رقابت می‌بازد پیام anchor is not one of the last 1024 roots را می‌گیرد و دوباره اثبات می‌سازد. rand_getAnchor بدون پارامتر ریشه‌ی پایان بلوکِ سر زنجیره را برمی‌گرداند، تنها لنگری که یک اثبات‌کننده باید بر پایه‌ی آن بسازد.

بسته

هر تراکنشی که ارزش جابه‌جا می‌کند دقیقاً یک بسته‌ی ثابت 4-ورودی-4-خروجی با دارایی پنهان دارد (از زنجیره‌ی 14؛ تا زنجیره‌ی 13 دو-ورودی-دو-خروجی بود)، که با گوست zkVM دارایی پنهان اثبات می‌شود و چکیده‌ی آن hc_bundle در فایل جنسیس ثابت شده است (60af094acfe65d85fdb18fb3d06cf9085dcf28c96e59e87f1ee527226e6e3fce، گوست بسته v3، در زنجیره‌های 17 تا 20؛ در زنجیره‌های 14 و 15 برابر 83d3a3704a1fcdb9bae7136c0a947ffa34bd53f055388395ed705fe8cacd0ef8 بود، گوست v1):

Bundle { anchor, nullifiers[4], commitments[4], fee, burn_a, burn_r, burn_asset, time, envelopes[4], proof,
         auth_commit, auth_proof }     // the last two since chain 17

شکاف‌های 0–1، هم ورودی و هم خروجی، یک دارایی خصوصی A را حمل می‌کنند؛ شکاف‌های 2–3 همیشه RAND حمل می‌کنند. در پرداخت فقط-RAND شکاف‌های 0–1 ساختگی‌اند و ارزش و کارمزد از شکاف‌های 2–3 جابه‌جا می‌شوند. در انتقال توکن، توکن از شکاف‌های 0–1 جابه‌جا می‌شود و کارمزد RAND از شکاف‌های 2–3 همان اثبات پرداخت می‌شود. A یک کلمه‌ی شاهد (witness) اثبات است و در هیچ فیلد عمومی نیست، بنابراین انتقال هر توکن RPL برابر Action::None با burn_a = burn_r = burn_asset = 0 است، فیلد به فیلد همان شکلِ یک پرداخت RAND.

اثبات بیان می‌کند که هر ورودی واقعی برگی زیر anchor است و دارایی شکاف خود را دارد، که چهار نال‌ی‌فایر همان‌هایی هستند که منتشر شده‌اند، که هر گروه متوازن است (in0 + in1 = out0 + out1 + burn_a در دارایی خصوصی، in2 + in3 = out2 + out3 + fee + burn_r در RAND)، و که خرج‌کننده کلیدها را دارد — بی‌آنکه فاش کند کدام برگ‌ها، چه مقادیری، کدام دارایی، یا چه کسی. سه فیلد سوزاندن تنها بیانیه‌ی عمومی بسته درباره‌ی ارزشی هستند که استخر را ترک می‌کند: burn_a و burn_asset فقط در TokenBurn یا BridgeBurn ناصفرند، که در آن‌ها با مقدار و دارایی اقدام برابرند؛ burn_r مقدار RAND سوزانده‌شده است و فقط در Bond (سهام گذاشته‌شده) و RegisterAggregator (وثیقه) ناصفر است. کارمزد همیشه RAND است.

از زنجیره‌ی 17 اختیار خرج کردن بین دو اثبات تقسیم شده است (احراز تفکیک‌شده). گوست بسته v3 به‌جای کلید خرج، کلید مشاهده‌ی nk و یک salt می‌گیرد: pk_self = H(PK, nk) را استخراج می‌کند، می‌خواهد که هر ورودی به آن تعلق داشته باشد، و c = H(AUTH, nk ‖ salt) را در چکیده‌ای که منتشر می‌کند می‌گنجاند. گوست دوم، گوست احراز (hc_auth جنسیس، 1e4e347f44cf86750b30a9a4bdf9ec9256efe353d4ff8017451eca7d195639c1)، کلید خرج sk و همان salt را می‌گیرد، nk = H(NK, sk) را استخراج می‌کند و همان c را منتشر می‌کند. بسته c را به‌عنوان auth_commit و اثبات tier-10 گوست احراز را به‌عنوان auth_proof حمل می‌کند، و دفتر کل می‌خواهد هر دو اثبات در برابر همان الزام‌بندی تراکنش تأیید شوند و دو مقدار c برابر باشند. بنابراین اثبات‌کننده‌ای که nk و salt را دارد می‌تواند اثبات بسته را بسازد اما اثبات احراز را نه، و اثبات‌سازیِ واگذارشده بر همین استوار است. salt به ازای هر بسته 256 بیت تصادفی تازه است، پس auth_commit عمومی است و چیزی را پیوند نمی‌دهد. کیف پولی که برای خودش اثبات می‌سازد هر دو اثبات را هم می‌سازد، بنابراین تراکنش خوداثبات‌شده و تراکنش واگذارشده روی زنجیره یکسان به نظر می‌رسند.

شکل تراکنش ثابت است، پس بسیاری از جایگاه‌ها ساختگی‌اند: ورودی ساختگی یادداشتی با مقدار صفر است که به خرج‌کننده تعلق دارد (ابطال‌گر آن همچنان منتشر می‌شود)، خروجی ساختگی یادداشتی با مقدار صفر است، و خروجی باقی‌مانده حتی وقتی باقی‌مانده صفر است نیز وجود دارد. تراکنشی که در نبود باقی‌مانده شکل دیگری داشت، فاش می‌کرد که باقی‌مانده‌ای وجود نداشته است. پیامد این موضوع برای کیف‌پول‌ها: هر یک از دو گروه یک بسته حداکثر دو یادداشت خرج می‌کند. پرداخت RAND، اوراق قرضه (bond)، استقرار (deploy) یا فراخوانی (call) حداکثر دو یادداشت RAND خرج می‌کند؛ انتقال یا سوزاندن توکن حداکثر دو یادداشت از آن توکن و حداکثر دو یادداشت RAND برای کارمزد خرج می‌کند. مقداری که در یک گروه روی سه یادداشت یا بیشتر پخش شده باشد، باید نخست با ارسال به نشانی خود فرد تجمیع شود.

از زنجیرهٔ ۱۴ به بعد، اثبات به تراکنش خود نیز بسته شده است: اثبات روی هش کل تراکنش با اثبات‌های تهی‌شده ساخته و در برابر همان هش راستی‌آزمایی می‌شود، بنابراین نمی‌توان آن را روی کنشی دیگر، پاکت‌هایی دیگر یا زنجیره‌ای دیگر کپی کرد؛ در زنجیرهٔ ۲۰ این هش هش genesis را هم در بر می‌گیرد، پس روی زنجیره‌ای که شناسهٔ زنجیرهٔ یکسانی دارد نیز کپی‌شدنی نیست (تراکنش‌ها).

چه چیزی عمومی است و چه چیزی پنهان

عمومی روی زنجیرهپنهان
یک انتقال، از RAND یا از هر توکن RPLلنگر، هر چهار ابطال‌گر، هر چهار تعهد، کارمزد، burn_a = burn_r = burn_asset = 0، time، چهار متن رمزشدهٔ پاکت، اثبات، auth_commit و اثبات احرازچه کسی فرستاده، به چه کسی پرداخت شده، مبلغ، یادداشت همراه (memo)، کدام دارایی جابه‌جا شده، باقی‌مانده، کدام برگ‌ها خرج شده‌اند، کدام جایگاه‌ها ساختگی بوده‌اند
سوزاندن توکن یا سوزاندن پلهمهٔ آنچه یک انتقال منتشر می‌کند، با burn_asset و burn_a برابر با دارایی و مبلغ کنش (و برای سوزاندن پل، کارمزد رلهٔ‌ (relayer)، توکن پشتوانه و مقصد)کدام یادداشت‌ها سوزانده شده‌اند و چه کسی آن‌ها را سوزانده است
ضرب (mint) از فاستتعهد، پاکت، مبلغ، اعتبارسنج ضرب‌کننده، و از زنجیرهٔ ۱۴ به بعد گشایش یادداشت (pk، time، r) که پذیرش تعهد را از روی آن دوباره محاسبه می‌کندهر خرج بعدیِ یادداشت
سپردهٔ genesis{ cm, envelope, amount, opening: { pk, time, r } } در فایل genesis، و برای یادداشتِ توکنی فهرست‌شده با asset در گشایش (genesis زنجیرهٔ ۲۰ دو یادداشت zUSD دارد): pk مالک، مبلغ و دارایی عمداً عمومی‌اندزمان و مقصد خرج بعدی آن
برداشت (withdraw)، سپردهٔ پل، ضرب توکن یا ضرب اولیهٔ یک ثبت‌ناممبلغ، گیرنده، time یادداشت و عامل کورکننده r (زنجیره خودش تعهد را محاسبه می‌کند)هر خرج بعدیِ یادداشت
پرداخت یا ضرب برنامهٔ یک invoke (زنجیرهٔ ۲۰)مبلغ، دارایی، گیرنده و r؛ time یادداشت همان time بسته استهر خرج بعدیِ یادداشت
یادداشت کارمزد پل (زنجیرهٔ ۲۰)مبلغ، دارایی و time، یک عامل کورکنندهٔ مشتق‌شده، خزانهٔ genesis به‌عنوان مالک، و بدون پاکتهر خرج بعدیِ یادداشت

ارزش با مبلغی عمومی وارد استخر می‌شود — ضرب از فاست، سپردهٔ genesis، برداشت یک اعتبارسنج، گواهی پل و یادداشت کارمزد آن، ضرب توکن، پرداخت صندوق یک برنامه — و به‌صورت کارمزد یا سوزاندنِ عمومی از آن خارج می‌شود. هر آنچه میان این دو است، یادداشت است.

جدول بالا همان چیزی است که اثبات‌های زنجیرهٔ ۲۰ پنهان می‌کنند، و همیشه چنین نبوده است. در زنجیره‌های ۱۴ و ۱۵ خودِ اثبات بسته بیشتر فاش می‌کرد (بازبینی zkVM در ۲۰۲۶-۰۹-۲۷، INT-2 / GV-1): جمع جاری LogUp هر جدول بدون کورسازی منتشر می‌شد، و مهمان (guest) نسخهٔ ۱ برای ورودی ساختگی بررسی‌های Merkle و دارایی را رد می‌کرد و روی تک‌تک بیت‌های مسیر Merkle شاخه می‌زد، پس هر کس می‌توانست از روی اثبات یک بسته بخواند کدام یک از چهار جایگاه ورودی واقعی بوده‌اند، و در نتیجه اینکه بسته یک توکن را جابه‌جا کرده یا فقط RAND را، و نیز تعداد بیت‌های ۱ در شاخص هر برگِ خرج‌شده را. دو تغییر این را بست. زنجیرهٔ ۱۶ به مجموعهٔ قیود ۷ رفت که هر پایانهٔ LogUp را کور می‌کند و هر جدول اعلام‌شده را روی ۲^۷ سطر کف‌گذاری می‌کند، و به مهمان بستهٔ بدون‌شاخهٔ v2 رفت که در آن هر جایگاه، ساختگی یا واقعی، بررسی کامل عضویت و دارایی را با همان دستورها اجرا می‌کند. مهمان v3 که زنجیره‌های ۱۷ تا ۲۰ اجرا می‌کنند، همان رابطهٔ v2 است با nk و نمک به‌جای کلید خرج، و آن نیز بدون شاخه است. بسته‌هایی که روی زنجیره‌های ۱۴ و ۱۵ ثبت شده‌اند همان نشتی را نگه می‌دارند. آنچه یک اثبات در زنجیرهٔ ۲۰ هنوز نشان می‌دهد سرایند آن است: برای یک بسته یک شکل ثابت است (رده ۱۴، حد گاز اعلام‌شدهٔ ۲۰,۴۷۹ روی هر بسته)، و برای Call یا Invoke رده، حد گاز اعلام‌شده و اینکه آیا و تقریباً چند بار فراخوانی از پیش‌کامپایل keccak یا sha256 استفاده کرده است (اثبات‌ها).

یادداشت‌های بدون پاکت

بیشتر یادداشت‌ها با گشودن پاکتشان پیدا می‌شوند. برخی را زنجیره از فیلدهای عمومی محاسبه می‌کند و برای آن‌ها پاکت یا وجود ندارد یا نباید به آن اعتماد کرد.

  • یادداشت‌های کارمزد پل (زنجیرهٔ ۲۰). زیر bridge.fees زنجیره در هر سپرده و هر سوزاندن یک یادداشت zUSD به خزانهٔ genesis اضافه می‌کند (توکن‌های RPL). این یادداشت پاکت ندارد: ذخیره‌سازی آن را با پاکتی تهی که برای هیچ‌کس مهر نشده نمایه می‌کند، و rand_getCompactBlocks و rand_getCommitments آن را به همین شکل نشان می‌دهند، برای سپرده پس از یادداشت سپرده و برای سوزاندن پس از چهار یادداشت بسته. هر واژهٔ یادداشت عمومی است: مبلغ از تراکنش و نرخ genesis، دارایی و time از تراکنش، from = 0، عامل کورکننده blake3("rand-bridge-fee-r-1" ‖ mu) برای سپرده یا blake3("rand-bridge-burn-fee-r-1" ‖ txid) برای سوزاندن، و مالک همان recipient در genesis.
  • یادداشت‌هایی که پاکتشان چیزی را متعهد نمی‌کند. سپردهٔ پل، ضرب توکن، ضرب اولیهٔ یک ثبت‌نام و، در زنجیرهٔ ۲۰، پرداخت‌ها و ضرب‌های برنامهٔ یک invoke پاکتی دارند که هر کس تراکنش را ارسال کرده مهر کرده است، اما زنجیره یادداشت را از فیلدهای عمومی کنش محاسبه می‌کند و هرگز پاکت را با آن تطبیق نمی‌دهد. یک ارسال‌کنندهٔ بی‌دقت یا بدخواه می‌تواند داده‌ای بی‌معنا مهر کند، پس گیرنده نباید به آن وابسته باشد.

از این رو کیف‌پول rand این یادداشت‌ها را خودش بازسازی می‌کند (wallet::rebuilt_notes_with)، در یک گذر عمومی کنار رمزگشایی آزمایشی: برای هر تراکنشی که pk خودش را نام برده، و برای یادداشت‌های کارمزد وقتی نشانی‌اش همان خزانه است، یادداشت را محاسبه می‌کند و آن را در برگی ثبت می‌کند که تعهدش به آن هش می‌شود. یک یادداشت بازسازی‌شده فقط یک نامزد است: گره‌ای که دربارهٔ یک تراکنش، نرخ کارمزد یا یک هش دروغ بگوید، باعث می‌شود یادداشتی پیدا نشود، هرگز باعث نمی‌شود یادداشتی اعتبار شود. از v0.6.8 سرایندهایی که rand_getBlocks برمی‌گرداند تراکنش‌های سپرده، ضرب و ثبت‌نام را در public_notes دارند، و در زنجیره‌ای با کارمزد پل، سوزاندن‌ها را هم، با اثبات‌های تهی‌شده و زیر شناسهٔ واقعی‌شان، پس این گذر فقط صفحه‌های سرایند را می‌خواند. در v0.6.8 invokeها در public_notes نیستند، پس در برابر گرهٔ v0.6.8 کیف‌پول پرداخت یک invoke را فقط از پاکتش پیدا می‌کند، که invoke‌کننده آن را مهر می‌کند و چیزی آن را بررسی نمی‌کند. شاخهٔ main گرهٔ کامل از d0bb73fb invokeها را نیز آنجا دارد، با اثبات‌های تهی‌شده؛ این تغییری فقط در گره است، هنوز روی ناوگان زنجیرهٔ ۲۰ پیاده نشده، و کیف‌پول برای آن به هیچ تغییری نیاز ندارد.

اسکن: کیف‌پول یادداشت‌هایش را چگونه پیدا می‌کند

یک گره به «این کل درخت است» پاسخ می‌دهد، هرگز به «این موجودی شماست». یک کیف‌پول:

  1. برگ‌ها را با rand_getCommitments(from_index, limit) (حداکثر ۱,۰۰۰ در هر فراخوانی) یا با جریان هر بلوک rand_getCompactBlocks(from_height, to_height) (حداکثر ۱۲۸ بلوک در هر فراخوانی) صفحه‌به‌صفحه می‌خواند؛ هر تراکنشِ حامل بسته در آنجا چهار یادداشت و چهار ابطال‌گر فهرست می‌کند و هیچ خواننده‌ای نمی‌تواند جایگاه ساختگی را از جایگاه واقعی تشخیص دهد؛
  2. هر پاکت را رمزگشایی آزمایشی می‌کند: به‌عنوان گیرنده از راه کلید رمزگشایی ML-KEM خود، به‌عنوان فرستنده از راه ovk؛ یادداشت‌هایی را نگه می‌دارد که pk آن‌ها از خودش است؛ و یادداشت‌های دارای مبلغ عمومی را از تراکنش‌هایشان بازسازی می‌کند (بالا);
  3. ابطال‌گر هر یادداشت نگه‌داشته‌شده را محاسبه می‌کند و آن‌هایی را که در rand_getNullifiers(from_height, limit) ظاهر می‌شوند علامت می‌زند؛
  4. بقیه را جمع می‌زند.

rand_getTreeInfo می‌گوید تا کجا باید اسکن کرد: { "next_index", "root", "nullifiers" }. برای یک خرج، کیف‌پول به مسیر Merkle ۳۲ سطحیِ هر ورودی واقعی نیاز دارد. کیف‌پول rand نسخهٔ خودش از درخت تعهدها را نگه می‌دارد، که در حین اسکن از همان صفحه‌های rand_getCommitments ساخته می‌شود، و هر مسیر را خودش محاسبه می‌کند، بنابراین تنها پرسش دربارهٔ درخت که یک ارسال از گره می‌پرسد rand_getAnchor است که نام هیچ برگی را نمی‌برد. rand_getWitness(index) برای کلاینت‌های دیگر روی گره می‌ماند. این متد به گره می‌گوید فراخواننده می‌خواهد کدام برگ را خرج کند، که پیش از درخت محلی بزرگ‌ترین نشتی استخر بود: کلاینتی که ناچار به استفاده از آن است باید گره خودش را اجرا کند، یا علاوه بر شاهدهای لازم، شاهدهایی را هم بخواهد که به آن‌ها نیاز ندارد.

rand sync        # scanned 41 leaves and 37 blocks; 1 notes, 1 unspent
rand notes       # index, asset, amount, height, spent, pending, memo
rand history     # notes this wallet created for others, opened through ovk

یک note در بازهٔ بین rand send --no-wait و نخستین sync بعدی که یا nullifier آن را ببیند یا بلوک‌هایی پس از time + window را ببیند، در وضعیت pending است (این مقدار روی chain 20 برابر 1,024 است و از rand_getLimits.proof_window_blocks خوانده می‌شود؛ بدون آن 256 است). پس از آن، بسته (bundle) هرگز پذیرفته نمی‌شود و note دوباره قابل خرج کردن است.

آخرین بار با کامیت c0878c3f از fullnode (زنجیرهٔ 21) در تاریخ 2026-10-12 بررسی شد. هرجا کد و این صفحه با هم اختلاف دارند، حق با کد است؛ به ما اطلاع دهید.