Gunakan halaman ini untuk membangun atau mengaudit apa pun yang membaca pool: dompet yang memindai catatan miliknya, indexer yang mengikuti komitmen dan nullifier, atau alat yang memeriksa apa yang dipublikasikan oleh sebuah transaksi. Nilai di chain ini adalah sekumpulan catatan; chain menyimpan komitmennya, tidak pernah isinya.
Sebuah catatan
Sebuah catatan adalah { pk, from, amount, asset, time, r }: 28 kata berukuran 32 bit, 112 byte, dengan susunan pk(8) from(8) amount_lo amount_hi asset time r(8). pk adalah nilai publik catatan milik pemilik (diturunkan dari viewing key), from milik pengirim, amount berupa u64 dalam satuan unit (1 RAND = 10⁹ unit), asset berupa u32 (0 untuk RAND, selain itu indeks padat registri token untuk token RPL, baik yang dibridge maupun native; lihat token RPL), time tinggi blok yang ditargetkan pengirim, dan r delapan kata acak untuk blinding.
Plaintext tidak pernah muncul di chain. Yang muncul adalah:
- komitmen-nya
cm = H(CM, words), hash Poseidon2 dari 28 kata di bawah domain tag 4, yang ditambahkan sebagai daun pada pohon; - amplop-nya, plaintext yang disegel ke alamat pemilik sehingga hanya pemilik, atau pengirim melalui outgoing viewing key miliknya, yang dapat membacanya. Sejak chain 18 (termasuk chain 20) amplop juga memuat memo terenkripsi. Beberapa catatan yang dihitung chain dari bidang publik sama sekali tidak memiliki amplop (di bawah).
Membelanjakan catatan memublikasikan nullifier-nya H(NF, nk, cm), hash Poseidon2 di bawah domain tag 3 atas viewing key pemilik dan komitmen. Tanpa nk, sebuah nullifier tidak dapat dikaitkan dengan komitmennya. Himpunan nullifier inilah yang mencegah pembelanjaan ganda.
Amplop
Envelope { kem_ct, to_receiver, to_sender, body }
| bagian | byte | keterangan |
|---|---|---|
kem_ct | 1.088 | enkapsulasi ML-KEM-768 ke alamat penerima |
to_receiver | 60 | kunci per transaksi TxKey yang dibungkus di bawah rahasia hasil enkapsulasi |
to_sender | 60 | TxKey yang sama, dibungkus di bawah outgoing viewing key pengirim ovk |
body | 652 | catatan 112 byte dan bidang memo 512 byte di bawah TxKey (140 byte, hanya catatannya, hingga chain 17) |
Setiap bagian menggunakan ChaCha20-Poly1305 dengan nonce acak 12 byte di depannya dan tag 16 byte, dan data terasosiasinya adalah sebuah tag (rand-envelope-receiver, rand-envelope-sender, rand-envelope-body) diikuti komitmen catatan, sehingga sebuah kunci tidak dapat membuka amplop yang tidak ia segel dan catatan yang telah dibuka terikat pada daun tempatnya berada. Itu berarti 1.860 byte ciphertext; dengan awalan panjang dari bincode menjadi 1.892 byte di jaringan. Genesis chain 20 menetapkan envelope_bytes: 1860, dan ledger-nya menolak amplop catatan dengan panjang lain (envelope is 1348 bytes, this chain requires exactly 1860); ledger tidak memeriksa hal lain tentangnya. Hingga chain 17 amplop tidak memiliki bidang memo dan berukuran 1.348 byte (1.380 di jaringan), dan ledger hanya membatasinya pada 2.048 byte, yang masih berlaku pada chain yang genesisnya tidak memiliki envelope_bytes.
Bidang memo adalah len (u16 LE) ‖ UTF-8 text ‖ zero padding, 512 byte, sehingga teksnya paling banyak 510 byte. Setiap amplop catatan di chain 20 memilikinya, baik ada memo maupun tidak: output kembalian, output dummy, dan pembayaran tanpa memo diberi padding dengan cara yang sama, dan tidak ada apa pun di chain yang membedakan memo, panjangnya, atau ketiadaannya. Memo tidak berada di dalam catatan, komitmen, ataupun bukti apa pun. Memo dapat dibaca persis oleh siapa pun yang sudah dapat membaca catatan: pemilik, pengirim melalui ovk, atau siapa pun yang diberi TxKey output tersebut. Bidang memo yang cacat (panjang yang salah, UTF-8 tidak valid, padding bukan nol) terbuka sebagai tanpa memo dan tidak pernah membuat pemilik kehilangan catatan. Memo adalah teks dari siapa saja, karena siapa pun dapat membayar catatan dust ke alamat publik: tampilkan sebagai tidak tepercaya, dengan karakter kontrol dan karakter format Unicode diganti, seperti yang dilakukan dompet rand. rand send --memo menyegel satu memo ke dalam output pembayaran; rand notes --memo dan rand history --memo menampilkannya.
Pohon, anchor, dan jendela
Komitmen ditambahkan ke satu pohon Poseidon2 berkedalaman 32, dari yang tertua, dan tidak pernah dihapus. Daun i adalah catatan ke-i yang pernah dibuat chain; catatan deposit genesis adalah daun-daun pertama. Sebuah bundle menambahkan empat daun dan empat nullifier apa pun yang dipindahkannya, termasuk dummy. Daun kosong seluruhnya nol dan sebuah node adalah H(NODE, left, right) (domain tag 7). Sebuah node hanya menyimpan frontier, sehingga state konsensus tidak pernah menyimpan daun; rand_getWitness membangun ulang seluruh pohon dari daun yang tersimpan sesuai permintaan, itulah sebabnya ia adalah pembacaan termahal yang dilayani sebuah node.
Sebuah bundle membuktikan keanggotaan input-inputnya terhadap sebuah anchor, yaitu root pohon. Dua aturan menjaga anchor tetap jujur:
| konstanta | nilai | aturan |
|---|---|---|
ANCHOR_WINDOW | 256 blok; 1.024 pada chain 20 | anchor harus berupa salah satu dari window root akhir blok terakhir; root yang dilalui pohon di tengah blok tidak pernah menjadi anchor |
TIME_WINDOW | 256 blok; 1.024 pada chain 20 | time milik bundle harus berada dalam [height − window, height] |
Kedua jendela berukuran 256 blok kecuali genesis menetapkan proof_window_blocks (v0.6.8, 256 hingga 4.096), yang menggantikan keduanya sekaligus. Chain 20 menetapkan 1.024; chain 14 hingga 19 tidak memiliki bidang itu.
Jendela ini ada karena pembuktian memerlukan waktu nyata: bukti bundle tier-14 berukuran sekitar 100 detik di laptop (dan sejak chain 17 dompet membuat bukti auth kedua, tier-10, sekitar 7 detik). 256 blok kira-kira lima menit pada blok 1,17 s milik chain 18, dan bukti terdelegasi pada prover yang lambat ditolak pada 294 s; 1.024 blok milik chain 20 kira-kira 23 menit pada 1,34 s yang dilaporkan explorer di sana. Dompet yang kalah dalam balapan ini diberi tahu anchor is not one of the last 1024 roots dan membuktikan ulang. rand_getAnchor tanpa parameter mengembalikan root akhir blok dari head, satu-satunya anchor yang boleh dijadikan dasar oleh prover.
Bundle
Setiap transaksi yang memindahkan nilai membawa tepat satu bundle hidden-asset 4-masuk-4-keluar yang tetap (sejak chain 14; sebelumnya 2-masuk-2-keluar hingga chain 13), dibuktikan oleh guest zkVM hidden-asset yang digest-nya hc_bundle dipatok di file genesis (60af094acfe65d85fdb18fb3d06cf9085dcf28c96e59e87f1ee527226e6e3fce, guest bundle v3, pada chain 17 hingga 20; sebelumnya 83d3a3704a1fcdb9bae7136c0a947ffa34bd53f055388395ed705fe8cacd0ef8, guest v1, pada chain 14 dan 15):
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
Slot 0–1, masuk maupun keluar, membawa aset privat A; slot 2–3 selalu membawa RAND. Pada pembayaran khusus RAND, slot 0–1 adalah dummy dan nilai serta fee berpindah melalui slot 2–3. Pada transfer token, token berpindah melalui slot 0–1 dan fee RAND dibayar dari slot 2–3 dalam bukti yang sama. A adalah kata saksi dari bukti dan tidak ada di bidang publik mana pun, sehingga transfer token RPL apa pun adalah Action::None dengan burn_a = burn_r = burn_asset = 0, bidang demi bidang berbentuk sama dengan pembayaran RAND.
Bukti menyatakan bahwa setiap input nyata adalah daun di bawah anchor dan membawa aset slotnya, bahwa keempat nullifier adalah yang dipublikasikan, bahwa setiap kelompok seimbang (in0 + in1 = out0 + out1 + burn_a pada aset privat, in2 + in3 = out2 + out3 + fee + burn_r pada RAND), dan bahwa pembelanja memegang kuncinya — tanpa mengungkapkan daun mana, jumlah berapa, aset apa, atau siapa. Ketiga bidang burn adalah satu-satunya pernyataan publik bundle tentang nilai yang keluar dari pool: burn_a dan burn_asset bukan nol hanya pada TokenBurn atau BridgeBurn, yang nilainya sama dengan jumlah dan aset aksi tersebut; burn_r adalah RAND yang dibakar, bukan nol hanya pada Bond (stake) dan RegisterAggregator (bond). Fee selalu dalam RAND.
Sejak chain 17, otoritas untuk membelanjakan dipecah menjadi dua bukti (otorisasi terpisah). Guest bundle v3 mengambil viewing key nk dan sebuah salt di tempat guest sebelumnya mengambil spend key: ia menurunkan pk_self = H(PK, nk), mensyaratkan setiap input dimiliki olehnya, dan melipat c = H(AUTH, nk ‖ salt) ke dalam digest yang dipublikasikannya. Guest kedua, guest auth (genesis hc_auth, 1e4e347f44cf86750b30a9a4bdf9ec9256efe353d4ff8017451eca7d195639c1), mengambil spend key sk dan salt yang sama, menurunkan nk = H(NK, sk) dan memublikasikan c yang sama. Bundle membawa c sebagai auth_commit dan bukti tier-10 guest auth sebagai auth_proof, dan ledger mensyaratkan kedua bukti terverifikasi terhadap binding transaksi yang sama serta kedua nilai c sama. Dengan demikian prover yang memegang nk dan salt dapat membuat bukti bundle tetapi tidak bukti auth, dan inilah dasar pembuktian terdelegasi. Salt berupa 256 bit acak baru untuk setiap bundle, sehingga auth_commit bersifat publik dan tidak mengaitkan apa pun. Dompet yang membuktikan sendiri juga membuat kedua bukti, sehingga transaksi yang dibuktikan sendiri dan yang terdelegasi tampak sama di chain.
Slot dalam bentuk yang tetap ini sering kali berupa slot semu: input semu adalah note bernilai nol milik pembelanja (nullifier-nya tetap dipublikasikan), output semu adalah note berjumlah nol, dan output kembalian selalu ada, bahkan ketika kembaliannya nol. Transaksi yang tampak berbeda saat tidak ada kembalian akan membocorkan bahwa kembalian itu tidak ada. Konsekuensinya bagi dompet: masing-masing dari dua kelompok dalam satu bundel membelanjakan paling banyak dua note. Pembayaran RAND, bond, deploy, atau call membelanjakan paling banyak dua note RAND; transfer atau burn token membelanjakan paling banyak dua note token tersebut dan paling banyak dua note RAND untuk biayanya. Nilai yang tersebar pada tiga note atau lebih dalam satu kelompok harus dikonsolidasikan terlebih dahulu, dengan mengirimkannya ke alamat sendiri.
Sejak chain 14, bukti juga terikat pada transaksinya: bukti dibuat atas, dan diverifikasi terhadap, hash dari seluruh transaksi dengan bukti-buktinya dikosongkan, sehingga tidak dapat disalin ke aksi lain, amplop lain, atau chain lain. Pada chain 20, hash tersebut juga mencakup genesis hash, sehingga bukti tidak dapat dipindahkan pula ke chain yang memiliki chain id yang sama (Transaksi).
Apa yang publik dan apa yang tersembunyi
| publik di chain | tersembunyi | |
|---|---|---|
| transfer, baik RAND maupun token RPL apa pun | anchor, keempat nullifier, keempat komitmen, biaya, burn_a = burn_r = burn_asset = 0, time, empat ciphertext amplop, bukti, auth_commit, dan bukti auth | siapa pengirimnya, siapa yang dibayar, jumlahnya, memo, aset mana yang berpindah, kembaliannya, daun mana yang dibelanjakan, slot mana yang semu |
| burn token atau burn bridge | semua yang dipublikasikan oleh transfer, dengan burn_asset dan burn_a sama dengan aset dan jumlah aksi tersebut (dan, untuk burn bridge, biaya relayer, token penopang, dan tujuannya) | note mana yang di-burn, dan siapa yang mem-burn-nya |
| mint faucet | komitmen, amplop, jumlah, validator yang melakukan mint, dan sejak chain 14 pembukaan (opening) note tersebut (pk, time, r), yang menjadi dasar admisi menghitung ulang komitmennya | setiap pembelanjaan note tersebut sesudahnya |
| deposit genesis | { cm, envelope, amount, opening: { pk, time, r } } dalam berkas genesis, dengan asset di dalam opening untuk note dari token yang terdaftar (genesis chain 20 memuat dua note zUSD): pk pemilik, jumlah, dan aset bersifat publik secara desain | kapan dan untuk apa note tersebut kemudian dibelanjakan |
| withdraw, deposit bridge, mint token, atau mint awal sebuah registrasi | jumlah, penerima, time note, dan blinding r (chain menghitung sendiri komitmennya) | setiap pembelanjaan note tersebut sesudahnya |
| payout invoke atau mint program (chain 20) | jumlah, aset, penerima, dan r; time note adalah milik bundel | setiap pembelanjaan note tersebut sesudahnya |
| note biaya bridge (chain 20) | jumlah, aset, dan time, blinding turunan, treasury genesis sebagai pemilik, dan tanpa amplop | setiap pembelanjaan note tersebut sesudahnya |
Nilai masuk ke pool dengan jumlah yang publik — mint faucet, deposit genesis, withdraw validator, atestasi bridge beserta note biayanya, mint token, payout vault program — dan keluar sebagai biaya atau burn yang publik. Semua yang berada di antaranya adalah note.
Tabel tersebut menggambarkan apa yang disembunyikan oleh bukti chain 20, dan hal ini tidak selalu benar. Pada chain 14 dan 15, bukti bundel itu sendiri mengungkapkan lebih banyak (tinjauan zkVM 2026-09-27, INT-2 / GV-1): total berjalan LogUp setiap tabel dipublikasikan tanpa blinding, dan guest v1 melewati pemeriksaan Merkle dan aset untuk input semu serta bercabang pada setiap bit jalur Merkle, sehingga siapa pun dapat membaca dari bukti bundel slot input mana di antara keempatnya yang nyata, dan dengan demikian apakah bundel itu memindahkan token atau hanya RAND, serta jumlah bit 1 pada setiap indeks daun yang dibelanjakan. Dua perubahan menutup kebocoran itu. Chain 16 beralih ke constraint set 7, yang menyamarkan setiap terminal LogUp dan menetapkan batas bawah 2^7 baris untuk setiap tabel yang dideklarasikan, serta ke guest bundel v2 tanpa percabangan, yang pada setiap slot, semu maupun nyata, menjalankan pemeriksaan keanggotaan dan aset penuh melalui instruksi yang sama. Guest v3, yang dijalankan chain 17 hingga 20, adalah relasi v2 dengan nk dan salt menggantikan kunci belanja, dan juga tanpa percabangan. Bundel yang di-commit pada chain 14 dan 15 tetap menyimpan apa yang telah bocor. Yang masih ditunjukkan oleh bukti pada chain 20 adalah headernya: untuk bundel, header itu berbentuk tetap (tier 14, batas gas yang dideklarasikan sebesar 20,479 pada setiap bundel), dan untuk Call atau Invoke, header itu berisi tier, batas gas yang dideklarasikan, serta apakah dan kira-kira seberapa sering call tersebut memakai prakompilasi keccak atau sha256 (Bukti).
Note tanpa amplop
Sebagian besar note ditemukan dengan membuka amplopnya. Sebagian lain dihitung oleh chain dari field publik, dan untuk note semacam itu amplopnya tidak ada atau tidak boleh dipercaya.
- Note biaya bridge (chain 20). Di bawah
bridge.fees, chain menambahkan sebuah note zUSD ke treasury genesis pada setiap deposit dan setiap burn (Token RPL). Note ini tidak memiliki amplop: penyimpanan mengindeksnya dengan amplop kosong, yang disegel untuk siapa pun, danrand_getCompactBlockssertarand_getCommitmentsmenampilkannya demikian, untuk deposit setelah note deposit dan untuk burn setelah keempat note bundel. Setiap word dari note ini bersifat publik: jumlah berasal dari transaksi dan rate genesis, aset dantimeberasal dari transaksi,from = 0, blindingblake3("rand-bridge-fee-r-1" ‖ mu)untuk deposit ataublake3("rand-bridge-burn-fee-r-1" ‖ txid)untuk burn, dan pemiliknya adalahrecipientgenesis. - Note yang amplopnya tidak mengikat apa pun. Deposit bridge, mint token, mint awal sebuah registrasi, dan, pada chain 20, payout serta mint program dari sebuah invoke membawa amplop yang disegel oleh siapa pun yang mengirimkan transaksi, tetapi chain menghitung note dari field publik aksi dan tidak pernah memeriksa amplop terhadapnya. Pengirim yang ceroboh atau berniat jahat dapat menyegel data sampah, sehingga penerima tidak boleh bergantung padanya.
Oleh karena itu, dompet rand membangun ulang note-note ini sendiri (wallet::rebuilt_notes_with), dalam satu lintasan publik di samping dekripsi percobaan: untuk setiap transaksi semacam itu yang menyebut pk-nya sendiri, dan untuk note biaya ketika alamatnya adalah treasury, dompet menghitung note tersebut dan mencatatnya pada daun yang komitmennya merupakan hasil hash note itu. Note yang dibangun ulang hanyalah kandidat: node yang berbohong tentang sebuah transaksi, rate biaya, atau sebuah hash hanya membuat sebuah note tidak ditemukan, tidak pernah membuat note kredit palsu. Sejak v0.6.8, header yang dikembalikan rand_getBlocks memuat transaksi deposit, mint, dan registrasi dalam public_notes, dan pada chain dengan biaya bridge, burn juga, dengan bukti dilucuti, di bawah id aslinya, sehingga lintasan tersebut hanya membaca halaman header. Invoke tidak ada dalam public_notes pada v0.6.8, sehingga terhadap node v0.6.8 dompet menemukan payout sebuah invoke hanya melalui amplopnya, yang disegel oleh invoker dan tidak diperiksa oleh siapa pun. main fullnode memuat invoke di sana juga, dengan bukti dilucuti, sejak d0bb73fb; ini adalah perubahan khusus node, belum diterapkan pada fleet chain 20, dan dompet tidak memerlukan perubahan apa pun untuknya.
Pemindaian: cara dompet menemukan note-nya
Sebuah node menjawab “ini seluruh pohonnya”, tidak pernah “ini saldo Anda”. Sebuah dompet:
- memuat daun per halaman dengan
rand_getCommitments(from_index, limit)(paling banyak 1,000 per panggilan) atau aliran per blokrand_getCompactBlocks(from_height, to_height)(paling banyak 128 blok per panggilan); setiap transaksi pembawa bundel mencantumkan empat note dan empat nullifier di sana, dan tidak ada pembaca yang dapat membedakan slot semu dari slot nyata; - mendekripsi-percobaan setiap amplop: sebagai penerima melalui kunci dekapsulasi ML-KEM miliknya, sebagai pengirim melalui
ovk; menyimpan note yangpk-nya adalah miliknya sendiri; dan membangun ulang note yang berjumlah publik dari transaksinya (di atas); - menghitung nullifier dari setiap note yang disimpan dan menandai note yang muncul dalam
rand_getNullifiers(from_height, limit); - menjumlahkan sisanya.
rand_getTreeInfo menyatakan seberapa jauh pemindaian harus dilakukan: { "next_index", "root", "nullifiers" }. Untuk sebuah pembelanjaan, dompet memerlukan jalur Merkle 32 level dari setiap input yang nyata. Dompet rand menyimpan salinan pohon komitmennya sendiri, dibangun selama pemindaian dari halaman rand_getCommitments yang sama, dan menghitung setiap jalur sendiri, sehingga satu-satunya pertanyaan tentang pohon yang diajukan sebuah pengiriman kepada node adalah rand_getAnchor, yang tidak menyebut daun mana pun. rand_getWitness(index) tetap tersedia di node untuk klien lain. Fungsi ini memberi tahu node daun mana yang akan dibelanjakan pemanggilnya, yang merupakan kebocoran terbesar pool sebelum adanya pohon lokal: klien yang harus menggunakannya sebaiknya menjalankan node sendiri, atau meminta witness yang tidak diperlukannya bersama dengan yang diperlukannya.
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
Sebuah note berstatus pending sejak rand send --no-wait hingga sync berikutnya yang melihat nullifier-nya atau blok yang melewati time + window (1.024 pada chain 20, dibaca dari rand_getLimits.proof_window_blocks; 256 jika tidak tersedia). Setelah itu bundle tidak akan pernah dapat diterima dan note tersebut dapat dibelanjakan kembali.