Program ini membolehkan seorang ahli daripada senarai tetap memberikan satu undi tanpa menunjukkan ahli yang mana satu. Senarai itu ialah pokok Merkle bagi cincangan Keccak-256, dibina seperti cara senarai airdrop Ethereum dibina, jadi perkakasan yang sama boleh menghasilkannya. Program ini dipasang pada chain 18, dan bahagian terakhir memanggilnya dengan ahli demo yang boleh anda gunakan sendiri.
Program contoh mengandungi persediaan yang dikongsi oleh keempat-empat halaman contoh: checkout circuits, dompet, dan tempat mendapatkan sumbernya.
1. Apa yang dibuktikan
Seorang penganjur mempunyai senarai ahli. Setiap ahli memegang satu rahsia 32 bait. Penganjur tidak pernah melihat rahsia itu: setiap ahli menyerahkan satu daun, keccak256(secret ‖ tag), dan penganjur membina pokok Merkle di atas daun-daun tersebut.
Satu undi ialah satu panggilan. Ia membuktikan, tanpa mendedahkan apa-apa yang lain:
- pemanggil mengetahui satu rahsia yang daunnya terdapat dalam pokok;
- pilihan yang diterbitkan ialah salah satu pilihan yang dibenarkan;
- nullifier yang diterbitkan ialah
keccak256(secret ‖ tag ‖ poll id).
Nullifier adalah sama bagi setiap undi yang dibuat oleh satu rahsia dalam satu undian, dan berbeza dalam setiap undian lain. Sesiapa yang mengira undi mengira setiap nullifier sekali sahaja. Ia tidak berkait dengan daun: untuk bergerak daripada nullifier kembali kepada daun, rahsia diperlukan.
Chain tidak menolak undi kedua. Program pada Rand tidak mempunyai storan, jadi ia tidak dapat mengingati nullifier (Program). Dua panggilan dengan rahsia yang sama kedua-duanya disahkan dan kedua-duanya meninggalkan resit. Peraturan “satu nullifier, satu undi” dikuatkuasakan oleh pengira, ke atas resit-resit itu. Sesiapa sahaja boleh menjalankan pengiraan yang sama, dan itulah yang menjadikan jumlah undi boleh disemak.
2. Antara muka
Pokok, undian dan bilangan pilihan adalah sama bagi setiap undi, jadi ia diletakkan dalam segmen awam, ditetapkan semasa pemasangan. ID program kemudian menamakan undian ini dan bukan yang lain.
| perkataan awam | maksud |
|---|---|
| 0 | kedalaman pokok d, 1 hingga 20 |
| 1 | ID undian |
| 2 | bilangan pilihan; satu pilihan ialah 0 .. choices − 1 |
| 3 hingga 10 | akar, 32 bait sebagai lapan perkataan little-endian |
| perkataan peribadi | maksud |
|---|---|
| 0 hingga 7 | rahsia |
| 8 | indeks daun ahli |
| 9 | pilihan |
| 10 dan seterusnya | laluan Merkle: d adik-beradik masing-masing 8 perkataan, dari daun ke atas |
| slot output | maksud |
|---|---|
| 0 | status: 1 sudah mengundi, 2 bukan ahli, 3 pilihan di luar julat, 4 persediaan tidak sah |
| 1 | pilihan |
| 2 hingga 6 | 20 bait pertama nullifier, sebagai lima perkataan little-endian |
| 7 | tidak digunakan; panic Rust menulis 0xdeadbeef di sini |
Cincangan 32 bait ialah lapan perkataan, dan lapan perkataan ialah semua output yang ada. Menerbitkan 160 bit meninggalkan ruang untuk status dan pilihan. Dua ahli akan berkongsi nullifier 160 bit hanya melalui perlanggaran cincangan, yang memerlukan kira-kira 2⁸⁰ percubaan untuk ditemui.
Susunan bait. Keccak beroperasi pada bait. Syscall KECCAK mesin menerima keadaan sebagai perkataan, dengan bait i keadaan berada dalam bait i mod 4 perkataan i / 4. Maka 32 bait menjadi lapan perkataan dengan membaca empat bait pada satu masa, little-endian, dan akar dalam segmen awam hanyalah bait-bait akar yang dibaca dengan cara itu.
3. Guest
my-guests/allowlist/src/main.rs, lengkap:
#![no_std]
#![no_main]
use guest_sdk::{halt, keccak, read_input, read_public, write_output};
const MAX_DEPTH: u32 = 20;
const OK: u32 = 1;
const NOT_MEMBER: u32 = 2;
const BAD_CHOICE: u32 = 3;
const BAD_SETUP: u32 = 4;
/// The 32 bytes hashed after a secret to make its leaf: the ASCII tag, zero-padded.
const LEAF_TAG: [u32; 8] = tag_words(b"rand-allowlist-leaf-v1");
/// The first 28 bytes hashed after a secret to make its nullifier; the poll id is the last 4.
const NULL_TAG: [u32; 8] = tag_words(b"rand-allowlist-null-v1");
/// An ASCII tag as eight words: byte `i` of the tag is byte `i % 4` of word `i / 4`.
const fn tag_words(tag: &[u8]) -> [u32; 8] {
let mut w = [0u32; 8];
let mut i = 0;
while i < tag.len() {
w[i / 4] |= (tag[i] as u32) << (8 * (i % 4));
i += 1;
}
w
}
/// Keccak-256 of a 64-byte message, the hash Ethereum calls `keccak256(abi.encodePacked(a, b))`.
/// The message is one rate block, so this is one permutation: the two halves go into the first
/// 16 words of the state, `0x01` follows them, and `0x80` closes the 136-byte rate.
///
/// `a_second` is 0 or 1. When it is 1 the halves trade places. The place is computed as an
/// offset, not chosen by a branch, so both orders execute the same instructions.
fn hash2(a: &[u32; 8], b: &[u32; 8], a_second: u32) -> [u32; 8] {
let at = 8 * (a_second & 1) as usize;
let mut st = [0u32; 50];
st[at..at + 8].copy_from_slice(a);
st[8 - at..16 - at].copy_from_slice(b);
st[16] = 0x01;
st[33] = 0x8000_0000;
keccak(&mut st);
let mut out = [0u32; 8];
out.copy_from_slice(&st[..8]);
out
}
fn read8(at: u32) -> [u32; 8] {
let mut w = [0u32; 8];
for (i, x) in w.iter_mut().enumerate() {
*x = read_input(at + i as u32);
}
w
}
/// Public segment, fixed at deploy: [depth, poll id, number of choices, root (8 words)].
/// Private input: [secret (8 words), leaf index, choice, siblings (8 words per level)].
/// Outputs: [status, choice, nullifier (5 words), 0].
#[no_mangle]
pub extern "C" fn main() -> ! {
let depth = read_public(0);
let poll = read_public(1);
let choices = read_public(2);
if depth == 0 || depth > MAX_DEPTH {
write_output(0, BAD_SETUP);
halt();
}
let secret = read8(0);
let index = read_input(8);
let choice = read_input(9);
// Walk from the leaf to the root. Bit `level` of the index says whether the running hash
// is the right child at that level.
let mut cur = hash2(&secret, &LEAF_TAG, 0);
let mut level = 0;
while level < depth {
let sib = read8(10 + 8 * level);
cur = hash2(&cur, &sib, index >> level);
level += 1;
}
let mut diff = 0u32;
for k in 0..8 {
diff |= cur[k] ^ read_public(3 + k as u32);
}
let mut ctx = NULL_TAG;
ctx[7] = poll;
let nullifier = hash2(&secret, &ctx, 0);
if diff != 0 {
write_output(0, NOT_MEMBER);
} else if choice >= choices {
write_output(0, BAD_CHOICE);
} else {
write_output(0, OK);
write_output(1, choice);
for k in 0..5 {
write_output(2 + k as u32, nullifier[k]);
}
}
halt();
}
Tiga perkara di dalamnya wajar disalin.
Satu permutasi bagi setiap cincangan. guest_sdk::keccak256 menerima sebarang rentetan bait, dan membayarnya dengan gelung bait. Setiap mesej di sini tepat 64 bait, kurang daripada kadar 136 bait, jadi pelapikan ialah dua perkataan malar dan cincangan ialah satu panggilan KECCAK pada keadaan yang diisi melalui salinan perkataan. Dalam istilah Ethereum, hash2(a, b, 0) ialah keccak256(abi.encodePacked(a, b)) bagi dua bytes32.
Daun dan nullifier tidak boleh dikelirukan dengan nod pokok. Satu nod mencincang dua cincangan anak 32 bait. Satu daun mencincang satu rahsia dan satu tag ASCII tetap, dan satu nullifier mencincang satu rahsia, satu tag lain dan ID undian. Supaya satu daun dapat diterima sebagai nod, salah satu anaknya mesti sama dengan tag, yang bermaksud mencari praimej Keccak.
Laluan dilalui tanpa percabangan pada indeks. Versi pertama memilih susunan dua separuh dengan pertukaran bertopeng, dan pengkompil menukar semula topeng itu menjadi cabang: larian mengambil 9 kitaran lebih bagi setiap bit 1 dalam indeks daun. Bilangan kitaran itu sendiri tidak diterbitkan, tetapi had gas yang diisytiharkan oleh satu panggilan dibundarkan daripadanya (bahagian 6), jadi larian yang bergantung pada indeks boleh membocorkan sedikit indeks tersebut. Menulis setiap separuh pada ofset yang dikira telah menyelesaikannya: setiap indeks daun kini berjalan dengan 3,208 kitaran yang sama.
4. Sebelah hos
Penganjur dan pengundi memerlukan Keccak yang sama seperti yang dikira oleh mesin. allowlist.py ialah skrip Python tanpa kebergantungan dengan Keccak-f[1600] sendiri (hashlib.sha3_256 Python ialah SHA-3, yang melapik secara berbeza dan memberikan cincangan yang berbeza). Ia melakukan dua tugas:
allowlist.py setup <depth> <poll id> <choices> <secrets file> writes public.txt, prints the root
allowlist.py witness <depth> <poll id> <secrets file> <member> <choice>
prints the private words and the nullifier
Keseluruhan pokok, dalam istilah skrip:
LEAF_TAG = b"rand-allowlist-leaf-v1".ljust(32, b"\0")
NULL_TAG = b"rand-allowlist-null-v1".ljust(28, b"\0")
EMPTY = bytes(32) # an unused leaf: no secret hashes to it
def levels(depth, secrets):
row = [keccak256(s + LEAF_TAG) for s in secrets]
row += [EMPTY] * ((1 << depth) - len(row))
out = [row]
while len(row) > 1:
row = [keccak256(row[i] + row[i + 1]) for i in range(0, len(row), 2)]
out.append(row)
return out
nullifier = keccak256(secret + NULL_TAG + poll.to_bytes(4, "little"))
Dalam pengeluaran, penganjur memegang daun, bukan rahsia, dan setiap pengundi membina saksinya sendiri daripada daun yang diterbitkan. Skrip ini menerima fail rahsia kerana demo memainkan kedua-dua peranan.
Undian demo mempunyai kedalaman 10 (1,024 daun), ID undian 1 dan tiga pilihan. Rahsia kelapan-lapan ahlinya terdapat dalam demo-secrets.txt, satu rahsia 64-hex setiap baris, dan ia sengaja dibuat awam supaya sesiapa sahaja boleh memanggil program ini:
$ python3 allowlist.py setup 10 1 3 demo-secrets.txt
root ce8d6ef78a2ccaab688afa50ff969b0f89b23e7f32d8c3746848b46f773f4750
wrote public.txt
$ cat public.txt
10 1 3 4151217614 2882153610 1358596712 261854975 2134815369 1958991922 1874085992 1346846583
Perkataan akar pertama, 4151217614, ialah 0xf76e8dce: empat bait pertama akar ce 8d 6e f7 dibaca little-endian.
5. Bina dan jalankan
$ rand-guest build my-guests/allowlist --max-words 65535
427 words against a cap of 65535 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/allowlist/image.bin and its .sha256 (427 words, hc 420876739f0987ae7f0fbd23a98312ed4452b7e77cf6153e424ed02aca7b5afe, program id 93c780859b60516408099dc45247a21aead612f0c65c667248253533dd932bb3)
Ahli 5 mengundi pilihan 2. Saksi ialah 90 perkataan: 8 untuk rahsia, indeks, pilihan dan 80 untuk laluan.
$ cd my-guests/allowlist
$ python3 allowlist.py witness 10 1 demo-secrets.txt 5 2 > witness.txt
nullifier b5516c17d7f51034998f837217777deb8bc90886
nullifier words 392974773 873526743 1921224601 3950868247 2248722827
$ rand-guest run image.bin --public $(cat public.txt) --input $(cat witness.txt) --tier 14
out[0] = 1
out[1] = 2
out[2] = 392974773
out[3] = 873526743
out[4] = 1921224601
out[5] = 3950868247
out[6] = 2248722827
out[7] = 0
cycles 3208
tier 12
tier 14: fits (cycles 3342 of 16383, Poseidon2 permutations 134 of 2048)
Lima perkataan nullifier itu ialah perkataan yang dikira oleh skrip di luar mesin. Kegagalan berhenti dengan bersih bersama satu status, jadi ia juga boleh dibuktikan. Saksi yang sama dengan perkataan pertama rahsia diubah memberikan out[0] = 2 dan tiada yang lain, dan seorang ahli yang meminta pilihan 3 daripada 3 memberikan out[0] = 3.
6. Belanjawan
| program | 427 perkataan |
| kitaran | 3,208 bagi pokok berkedalaman 10; 3,342 dengan baris cincangan |
| Permutasi Keccak | 12: satu daun, sepuluh nod, satu nullifier. Had bagi satu panggilan ialah 128 |
| Permutasi Poseidon2 | 134 daripada 2,048, kesemuanya ialah cincangan program, input dan awam |
| peringkat | 12 |
Setiap tambahan satu aras kedalaman menelan satu permutasi dan kira-kira 270 kitaran. Pokok berkedalaman 20 (sejuta ahli) diukur pada 5,866 kitaran dan 22 permutasi, iaitu peringkat 14, peringkat tertinggi yang boleh digunakan oleh satu panggilan.
Sejak chain 18 satu panggilan membayar gas, dan permutasi Keccak ialah arahan yang mahal: 192 gas berbanding 1 bagi kitaran biasa. Dompet mencetak gas tepat bagi satu larian dan had yang diisytiharkannya untuknya:
gas: 5636 (declaring 6144, tier 12)
Setiap baris larian berharga 1 gas, dan satu baris Keccak berharga 191 lagi: 3,344 baris campur 12 × 191 ialah 5,636, dan dua perlima daripadanya ialah dua belas cincangan itu. Dompet mengira dua baris lebih banyak daripada rand-guest run. Pada chain 18 bukti panggilan terikat kepada transaksinya, dan pengikatan itu dibawa sebagai lapan perkataan tambahan segmen awam, yang menambah dua baris cincangan. Had yang diisytiharkan, 6,144, ialah angka tepat yang dibundarkan ke atas kepada gandaan 1,024. Had itu awam dan angka tepat itu tidak.
Bukti yang membawa jadual Keccak adalah besar. Yang ini ialah 3,346,865 bait, berbanding had 4,194,304 (4 MiB) pada chain 18 hingga 20. Angka kedalaman 20 di atas ialah ukuran emulator, dan tiada bukti dibuat pada kedalaman itu untuk halaman ini.
7. Pasang
Pemasangan membawa segmen awam, jadi yuran mengira 11 perkataannya bersama 427 perkataan kod: 0.001 + 0.0001 × 438 = 0.0448 RAND.
$ rand fee deploy 427 --public-words 11
0.0448 RAND
$ rand program deploy image.bin --public public.txt
program id: ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 (427 words, hc 73760842ae87099f23bd0f7fed1283a9e7b752443e15f67c2ad04e42fe5a7bca, public input 11 words, digest 2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510)
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 22.9s: tier 10, 1370827 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 190.2s: tier 14, 1497544 bytes
submitted deploy 05f8bfcbf9fccedbd53c3ba41bcc32dc8070ed3433364fc591b4f00699438de6
0 RAND out, 59.9552 RAND change, fee 0.0448 RAND, anchored at height 97632, proved in 190.2s (bundle) + 22.9s (auth)
ID yang dicetak oleh deploy, ad2f719f…b970, bukan yang dicetak oleh rand-guest build. Dengan segmen awam, ID ialah blake3("rand-program-2", base_pc ‖ len ‖ words ‖ len ‖ public), jadi ia mengikat akar, ID undian dan bilangan pilihan. Undian kedua ke atas ahli yang sama ialah pemasangan kedua bagi kod yang sama dengan ID undian yang berbeza, dan mendapat ID program serta nullifier yang berbeza.
hc juga dieja secara berbeza: rand-guest mencetak setiap satu daripada lapan perkataannya secara big-endian dan dompet mencetak bait setiap perkataan secara little-endian, jadi 42087673 menjadi 73760842. Ia ialah cincangan yang sama.
$ rand program show ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970
{
"base_pc": 4096,
"code_hash": "73760842ae87099f23bd0f7fed1283a9e7b752443e15f67c2ad04e42fe5a7bca",
"deployed_at": 97821,
"id": "ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970",
"public_digest": "2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510",
"public_words_len": 11,
"words_len": 427
}
Sesiapa sahaja boleh membaca semula segmen awam. rand_getProgramPublic memulangkan perkataan sebagai bait little-endian, jadi akar kelihatan seperti ketika ia dicetak:
$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"rand_getProgramPublic","params":["ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970"]}'
{"id":1,"jsonrpc":"2.0","result":"0a0000000100000003000000ce8d6ef78a2ccaab688afa50ff969b0f89b23e7f32d8c3746848b46f773f4750"}
8. Mengundi
rand call mengambil satu --input bagi setiap perkataan peribadi, dalam bentuk perpuluhan. sed menukar fail saksi (witness) kepada bendera tersebut:
$ rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
proving the call locally (90 inputs stay private, over the program's 11-word public input)…
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
proved in 29.2s: tier 12, 3346865 bytes, outputs [1, 2, 392974773, 873526743, 1921224601, 3950868247, 2248722827, 0]
proving the spend authorisation on this machine (tier 10; seconds)…
authorisation proved in 8.2s: tier 10, 1367303 bytes
proving the bundle (tier 14; about a minute and a half on a laptop)…
proved in 117.2s: tier 14, 1504260 bytes
submitted call 8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666
0 RAND out, 59.716708563 RAND change, fee 0.006191437 RAND, anchored at height 98185, proved in 117.2s (bundle) + 8.2s (auth)
{
"h_in": "ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6",
"h_pub": "2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510",
"height": 98335,
"index": 0,
"outputs": [
1,
2,
392974773,
873526743,
1921224601,
3950868247,
2248722827,
0
],
"program": "ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970",
"tier": 12,
"tx": "8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"
}
input transcript published; open it with `rand open-call 8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666`
Resit ialah apa yang disimpan oleh rantaian: status 1, pilihan 2, dan lima perkataan nullifier, di bawah id program undian itu. h_pub ialah digest segmen awam undian itu, nilai yang sama yang dipaparkan oleh program show. h_in ialah komitmen bergaram bagi 90 perkataan peribadi, dan ia tidak membuka apa-apa tanpa garam.
Panggilan itu mengambil masa tiga minit pada komputer riba Apple Silicon dan mencecah puncak 6.1 GB: 29 s untuk bukti panggilan, 8 s untuk kebenaran perbelanjaan dan 117 s untuk bundle yang membayar yuran. Yuran, 0.0062 RAND, sebahagian besarnya ialah bait. Four programs on chain 20 mengandungi pengiraannya.
Baris transkrip. Secara lalai, dompet mengunci input peribadi dalam sampul surat yang hanya boleh dibuka oleh kunci tontonan (viewing key) pemanggil, lalu menerbitkannya bersama panggilan. Ini ialah rekod untuk pemanggil, bukan pendedahan: tiada orang lain dapat membukanya. Pengundi yang tidak mahu sebarang rekod menggunakan --no-envelope. Selepas itu, tiada sesiapa, termasuk pengundi itu sendiri, dapat menunjukkan apakah input tersebut.
9. Mengira undi
Pengira tidak memerlukan apa-apa selain rantaian:
- Undian. Dapatkan segmen awam program dan semak root berbanding senarai daun yang diterbitkan (bina semula pokok dengan
levelsdaripadaallowlist.py). Bina semula guest daripada sumber dan bandingkanhcdengancode_hashprogram. - Resit. Ambil setiap resit yang
program-nya ialah id undian dan output 0 ialah1. Resit dengan status lain, atau dengan3735928559pada slot 7, bukan satu undi. - Pengiraan. Kumpulkan mengikut output 2 hingga 6. Kira setiap nullifier sekali sahaja, iaitu resit pertamanya mengikut susunan rantaian, dan tambahkan output 1 kepada jumlah.
Tiga lagi panggilan dibuat supaya pengira mempunyai sesuatu untuk dikerjakan. Ahli 5 mengundi kali kedua, kali ini untuk pilihan 0. Ahli 2 mengundi pilihan 1. Ahli 3 mengundi pilihan 0, daripada dompet yang hanya menggunakan titik akhir RPC awam. Ini ialah undi kedua ahli 5:
$ python3 allowlist.py witness 10 1 demo-secrets.txt 5 0 > witness.txt
nullifier b5516c17d7f51034998f837217777deb8bc90886
nullifier words 392974773 873526743 1921224601 3950868247 2248722827
$ rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt)
…
proved in 31.0s: tier 12, 3345265 bytes, outputs [1, 0, 392974773, 873526743, 1921224601, 3950868247, 2248722827, 0]
…
submitted call 0f596b87f5137a165d6abc691dbe04f7533db619911d81449fea9ede602c40d0
Rantaian menerima undi kedua itu, seperti yang seharusnya: buktinya sah. Resitnya membawa lima perkataan nullifier yang sama dengan resit pertama.
tally.py menerapkan ketiga-tiga peraturan itu. Ia membaca panggilan program daripada peneroka dan setiap resit daripada RPC awam, maka ia tidak memerlukan dompet atau nod:
$ python3 tally.py ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970
8aa13d1f… height 98335 choice 2 nullifier b5516c17d7f51034… counted
0f596b87… height 99194 choice 0 nullifier b5516c17d7f51034… repeat, dropped
2db517df… height 99339 choice 1 nullifier 52f2455918bbc811… counted
fdbcc960… height 101833 choice 0 nullifier cd86c5454b809914… counted
tally: choice 0: 1, choice 1: 1, choice 2: 1
Perubahan fikiran ahli 5 tidak dikira, dan tiada apa-apa dalam keempat-empat resit itu menunjukkan bahawa dua daripadanya datang daripada ahli senarai yang sama kecuali nullifier itu sendiri. Undian ini terbuka: mengundilah sebagai mana-mana daripada lapan ahli demo dan jalankan semula pengiraan.
Apa yang tidak diberikan oleh bukti itu
- Satu undi bagi setiap rahsia, bukan satu undi bagi setiap orang. Penganjur menentukan siapa yang mendapat daun. Ahli yang mendaftarkan dua daun mempunyai dua undi.
- Rahsia pengundi hanya sebaik rawaknya. Rahsia yang diterbitkan daripada rentetan yang boleh diteka boleh ditemui dengan mencincang tekaan dan membandingkannya dengan daun yang diterbitkan. Gunakan 32 bait rawak.
- Pilihan itu awam. Resit menunjukkan pilihan yang diundi oleh sesuatu nullifier. Tiada sesiapa dapat mengetahui ahli mana nullifier itu, tetapi undian dengan sedikit pengundi dan kehadiran yang diterbitkan ialah statistik nombor kecil, bukan kriptografi.
- Masa dan yuran ialah metadata. Bundle yuran dilindungi (shielded) dan tidak menamakan sesiapa, tetapi pengundi yang mendanai dompetnya dan mengundi seminit kemudian pada waktu sunyi telah memberitahu pemerhati sesuatu.
- Pengiraan dilakukan di luar rantaian. Rantaian menyimpan resit. Mengira resit tersebut, dan menolak nullifier kedua, dilakukan oleh sesiapa yang membacanya.