यह प्रोग्राम एक निश्चित सूची के किसी सदस्य को यह दिखाए बिना एक वोट डालने देता है कि वह कौन-सा सदस्य है। सूची Keccak-256 हैश का एक Merkle tree है, जो उसी तरह बनाया जाता है जैसे Ethereum एयरड्रॉप सूची बनाई जाती है, इसलिए वही टूलिंग इसे बनाती है। प्रोग्राम chain 18 पर डिप्लॉय है, और अंतिम खंड इसे डेमो सदस्यों के साथ कॉल करता है जिनका उपयोग आप स्वयं कर सकते हैं।
उदाहरण प्रोग्राम में वह सेटअप है जो चारों उदाहरण पृष्ठों में साझा है: circuits का checkout, वॉलेट, और स्रोत कहाँ से प्राप्त करें।
1. यह क्या सिद्ध करता है
एक आयोजक के पास सदस्यों की एक सूची है। प्रत्येक सदस्य के पास एक 32-बाइट का रहस्य (secret) है। आयोजक कभी रहस्य नहीं देखता: प्रत्येक सदस्य एक leaf, keccak256(secret ‖ tag), सौंपता है, और आयोजक leaves पर एक Merkle tree बनाता है।
एक वोट एक कॉल है। यह, और कुछ भी प्रकट किए बिना, सिद्ध करता है कि:
- कॉलर एक ऐसा रहस्य जानता है जिसका leaf tree में है;
- प्रकाशित विकल्प अनुमत विकल्पों में से एक है;
- प्रकाशित nullifier
keccak256(secret ‖ tag ‖ poll id)है।
एक poll में कोई रहस्य जितने भी वोट डाले, उन सबके लिए nullifier एक ही होता है, और हर दूसरे poll में भिन्न होता है। वोट गिनने वाला प्रत्येक nullifier को एक बार गिनता है। यह leaf से नहीं जुड़ता: किसी nullifier से वापस leaf तक पहुँचने के लिए रहस्य आवश्यक है।
चेन दूसरे वोट को अस्वीकार नहीं करती। Rand पर किसी प्रोग्राम के पास स्टोरेज नहीं होता, इसलिए वह किसी nullifier को याद नहीं रख सकता (प्रोग्राम)। एक ही रहस्य वाली दो कॉल दोनों सत्यापित होती हैं और दोनों एक रसीद छोड़ती हैं। “एक nullifier, एक वोट” का नियम गणक द्वारा, रसीदों पर, लागू किया जाता है। कोई भी वही गिनती चला सकता है, और यही मिलान को जाँचने योग्य बनाता है।
2. इंटरफ़ेस
tree, poll और विकल्पों की संख्या हर वोट के लिए समान हैं, इसलिए वे public segment में जाते हैं, जो डिप्लॉय के समय निश्चित होता है। तब program id इसी poll को नामित करता है, किसी अन्य को नहीं।
| public word | अर्थ |
|---|---|
| 0 | tree की गहराई d, 1 से 20 |
| 1 | poll id |
| 2 | विकल्पों की संख्या; एक विकल्प 0 .. choices − 1 है |
| 3 से 10 | root, 32 बाइट आठ little-endian words के रूप में |
| private word | अर्थ |
|---|---|
| 0 से 7 | रहस्य |
| 8 | सदस्य का leaf index |
| 9 | विकल्प |
| 10 से आगे | Merkle path: 8-8 words के d siblings, leaf से ऊपर की ओर |
| output slot | अर्थ |
|---|---|
| 0 | स्थिति: 1 वोट डाला गया, 2 सदस्य नहीं, 3 विकल्प सीमा से बाहर, 4 गलत सेटअप |
| 1 | विकल्प |
| 2 से 6 | nullifier के पहले 20 बाइट, पाँच little-endian words के रूप में |
| 7 | अप्रयुक्त; Rust panic यहाँ 0xdeadbeef लिखता है |
एक 32-बाइट हैश आठ words होता है, और आउटपुट कुल आठ words का ही है। 160 बिट प्रकाशित करने से स्थिति और विकल्प के लिए जगह बचती है। दो सदस्य एक 160-बिट nullifier केवल हैश टकराव (collision) से ही साझा करेंगे, जिसे खोजने में लगभग 2⁸⁰ प्रयास लगते हैं।
बाइट क्रम। Keccak बाइट्स पर काम करता है। मशीन का KECCAK syscall state को words के रूप में लेता है, जिसमें state का बाइट i word i / 4 के बाइट i mod 4 में होता है। इसलिए 32 बाइट एक बार में चार बाइट, little-endian, पढ़कर आठ words बनते हैं, और public segment में root बस इसी तरह पढ़े गए root के बाइट्स है।
3. गेस्ट
my-guests/allowlist/src/main.rs, पूर्ण:
#![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();
}
इसमें तीन बातें अपनाने योग्य हैं।
प्रति हैश एक permutation। guest_sdk::keccak256 कोई भी बाइट स्ट्रिंग लेता है, और इसकी कीमत बाइट लूप से चुकाता है। यहाँ प्रत्येक संदेश ठीक 64 बाइट का है, जो 136-बाइट rate से कम है, इसलिए padding दो स्थिर words है और हैश word प्रतियों से भरे state पर एक KECCAK कॉल है। Ethereum के शब्दों में, दो bytes32 के लिए hash2(a, b, 0) keccak256(abi.encodePacked(a, b)) है।
leaf और nullifier को tree node समझने की भूल नहीं हो सकती। एक node दो 32-बाइट child हैश को हैश करता है। एक leaf एक रहस्य और एक निश्चित ASCII tag को हैश करता है, और एक nullifier एक रहस्य, एक अन्य tag और poll id को हैश करता है। किसी leaf को node के रूप में पारित होने के लिए, उसके किसी child को tag के बराबर होना होगा, जिसका अर्थ है Keccak preimage खोजना।
path को index पर branching किए बिना तय किया जाता है। पहले संस्करण ने दोनों हिस्सों का क्रम एक masked swap से चुना था, और कंपाइलर ने mask को फिर से branch में बदल दिया: leaf index में प्रत्येक 1 बिट के लिए रन 9 अधिक cycles लेता था। cycle संख्या स्वयं प्रकाशित नहीं होती, लेकिन कोई कॉल जो gas limit घोषित करती है वह उसी से राउंड की जाती है (खंड 6), इसलिए index पर निर्भर रन index का थोड़ा अंश लीक कर सकता है। प्रत्येक हिस्से को एक गणना किए गए offset पर लिखने से यह ठीक हुआ: अब हर leaf index वही 3,208 cycles चलता है।
4. होस्ट पक्ष
आयोजक और मतदाताओं को वही Keccak चाहिए जो मशीन गणना करती है। allowlist.py एक dependency-मुक्त Python स्क्रिप्ट है जिसका अपना Keccak-f[1600] है (Python का hashlib.sha3_256 SHA-3 है, जो अलग तरह से padding करता है और भिन्न digest देता है)। यह दो काम करती है:
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
पूरा tree, स्क्रिप्ट के शब्दों में:
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"))
उत्पादन में आयोजक के पास leaves होते हैं, रहस्य नहीं, और प्रत्येक मतदाता प्रकाशित leaves से अपना witness स्वयं बनाता है। स्क्रिप्ट एक secrets फ़ाइल लेती है क्योंकि डेमो दोनों भूमिकाएँ निभाता है।
डेमो poll की गहराई 10 (1,024 leaves), poll id 1 और तीन विकल्प हैं। इसके आठ सदस्यों के रहस्य demo-secrets.txt में हैं, प्रति पंक्ति एक 64-hex रहस्य, और वे जानबूझकर सार्वजनिक हैं ताकि कोई भी प्रोग्राम को कॉल कर सके:
$ 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
पहला root word, 4151217614, 0xf76e8dce है: root के पहले चार बाइट ce 8d 6e f7 little-endian पढ़े गए।
5. बिल्ड और रन
$ 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)
सदस्य 5 विकल्प 2 के लिए वोट करता है। witness 90 words का है: रहस्य के लिए 8, index, विकल्प और path के लिए 80।
$ 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)
पाँच nullifier words वही हैं जो स्क्रिप्ट ने मशीन के बाहर गणना किए थे। विफलताएँ एक स्थिति के साथ साफ़ तौर पर रुकती हैं, इसलिए उन्हें भी सिद्ध किया जा सकता है। रहस्य का पहला word बदलकर वही witness out[0] = 2 देता है और कुछ नहीं, और 3 में से विकल्प 3 माँगने वाला सदस्य out[0] = 3 देता है।
6. बजट
| प्रोग्राम | 427 words |
| cycles | depth-10 tree के लिए 3,208; digest पंक्तियों सहित 3,342 |
| Keccak permutations | 12: एक leaf, दस nodes, एक nullifier। एक कॉल की सीमा 128 है |
| Poseidon2 permutations | 2,048 में से 134, सभी प्रोग्राम, इनपुट और public digests के |
| tier | 12 |
गहराई का प्रत्येक अतिरिक्त स्तर एक permutation और लगभग 270 cycles लेता है। depth-20 tree (दस लाख सदस्य) 5,866 cycles और 22 permutations मापता है, जो tier 14 है, सबसे ऊँचा tier जिसका कोई कॉल उपयोग कर सकती है।
chain 18 से एक कॉल gas का भुगतान करती है, और Keccak permutation महँगा instruction है: एक सामान्य cycle के 1 के मुकाबले 192 gas। वॉलेट किसी रन का सटीक gas और उसके लिए घोषित limit प्रिंट करता है:
gas: 5636 (declaring 6144, tier 12)
रन की प्रत्येक पंक्ति 1 gas लेती है, और एक Keccak पंक्ति 191 अधिक: 3,344 पंक्तियाँ और 12 × 191 मिलाकर 5,636 होता है, और इसका दो-पाँचवाँ भाग बारह हैश हैं। वॉलेट rand-guest run से दो पंक्तियाँ अधिक गिनता है। chain 18 पर एक call proof अपने transaction से बँधा होता है, और यह बंधन public segment के आठ अतिरिक्त words के रूप में जाता है, जो दो digest पंक्तियाँ जोड़ता है। घोषित limit, 6,144, सटीक आँकड़े को 1,024 के गुणज तक ऊपर राउंड करके बनती है। limit सार्वजनिक है और सटीक आँकड़ा नहीं।
Keccak तालिका ले जाने वाला proof बड़ा होता है। यह 3,346,865 बाइट का है, जबकि chain 18 से 20 पर सीमा 4,194,304 (4 MiB) है। ऊपर के depth-20 आँकड़े emulator माप हैं, और इस पृष्ठ के लिए उस गहराई पर कोई proof नहीं बनाया गया।
7. डिप्लॉय
डिप्लॉय public segment ले जाता है, इसलिए शुल्क 427 कोड words के साथ उसके 11 words भी गिनता है: 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)
deploy जो id प्रिंट करता है, ad2f719f…b970, वह नहीं है जो rand-guest build ने प्रिंट किया था। public segment के साथ id blake3("rand-program-2", base_pc ‖ len ‖ words ‖ len ‖ public) है, इसलिए यह root, poll id और विकल्पों की संख्या को बाँधता है। उन्हीं सदस्यों पर दूसरा poll उसी कोड का भिन्न poll id के साथ दूसरा डिप्लॉय है, और उसे भिन्न program id और भिन्न nullifiers मिलते हैं।
hc भी अलग तरह से लिखा जाता है: rand-guest अपने आठ words में से प्रत्येक को big-endian प्रिंट करता है और वॉलेट प्रत्येक word के बाइट्स little-endian प्रिंट करता है, इसलिए 42087673 73760842 बन गया। यह वही digest है।
$ 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
}
कोई भी public segment को वापस पढ़ सकता है। rand_getProgramPublic words को little-endian बाइट्स के रूप में लौटाता है, इसलिए root वैसा ही दिखता है जैसा प्रिंट हुआ था:
$ 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. वोट
rand call हर निजी शब्द के लिए एक --input लेता है, दशमलव में। sed विटनेस फ़ाइल को इन्हीं फ़्लैग में बदल देता है:
$ 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`
चेन रसीद को सहेजती है, पोल के प्रोग्राम id के अंतर्गत। उसमें स्थिति 1, विकल्प 2 और नलिफ़ायर के पाँच शब्द होते हैं। h_pub पोल के सार्वजनिक सेगमेंट का डाइजेस्ट है। यह वही मान है जो program show ने प्रिंट किया था। h_in 90 निजी शब्दों का सॉल्ट-युक्त कमिटमेंट है। सॉल्ट के बिना इससे कुछ भी नहीं खुलता।
Apple Silicon लैपटॉप पर कॉल में तीन मिनट लगे और मेमोरी का उपयोग अधिकतम 6.1 GB तक पहुँचा। कॉल प्रमाण में 29 s लगे, ख़र्च प्राधिकरण में 8 s और शुल्क चुकाने वाले बंडल में 117 s लगे। शुल्क 0.0062 RAND है और इसका अधिकांश भाग बाइट्स का है। इसकी गणना चेन 20 पर चार प्रोग्राम में दी गई है।
ट्रांसक्रिप्ट की पंक्ति। डिफ़ॉल्ट रूप से वॉलेट निजी इनपुट को एक लिफ़ाफ़े में सील करता है और उसे कॉल के साथ प्रकाशित करता है। यह लिफ़ाफ़ा केवल कॉल करने वाले की व्यूइंग की से खुलता है। यह कॉल करने वाले के लिए एक रिकॉर्ड है, प्रकटीकरण नहीं: कोई और इसे नहीं खोल सकता। जो मतदाता कोई भी रिकॉर्ड नहीं चाहता, वह --no-envelope देता है। तब कोई भी, स्वयं मतदाता भी, कभी नहीं दिखा सकता कि इनपुट क्या थे।
9. मतों की गणना
गणना करने वाले को चेन के अलावा किसी चीज़ की आवश्यकता नहीं होती:
- पोल। प्रोग्राम का सार्वजनिक सेगमेंट प्राप्त करें और रूट को लीफ़ की प्रकाशित सूची से मिलाएँ (
allowlist.pyकेlevelsसे ट्री को फिर से बनाएँ)। गेस्ट को सोर्स से फिर से बिल्ड करें औरhcकी तुलना प्रोग्राम केcode_hashसे करें। - रसीदें। हर वह रसीद लें जिसका
programपोल का id है और जिसका आउटपुट 01है। जिस रसीद की स्थिति कुछ और है, या जिसके स्लॉट 7 में3735928559है, वह मत नहीं है। - गणना। आउटपुट 2 से 6 के आधार पर समूह बनाएँ। हर नलिफ़ायर को एक ही बार गिनें, चेन के क्रम में उसकी पहली रसीद के आधार पर, और उसका आउटपुट 1 मिलान में जोड़ें।
गणना करने वाले के लिए कुछ काम हो, इसलिए तीन और कॉल किए गए। सदस्य 5 ने दूसरी बार मत दिया, इस बार विकल्प 0 के लिए। सदस्य 2 ने विकल्प 1 के लिए मत दिया। सदस्य 3 ने विकल्प 0 के लिए मत दिया। उसके वॉलेट ने केवल सार्वजनिक RPC एंडपॉइंट का उपयोग किया। यह सदस्य 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
चेन ने दूसरा मत स्वीकार कर लिया, और उसे स्वीकार करना ही था, क्योंकि प्रमाण वैध है। इसकी रसीद में वही पाँच नलिफ़ायर शब्द हैं जो पहली रसीद में थे।
tally.py तीनों नियम लागू करता है। यह प्रोग्राम के कॉल एक्सप्लोरर से और हर रसीद सार्वजनिक RPC से पढ़ता है। इसलिए इसे न वॉलेट की आवश्यकता है, न नोड की:
$ 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
सदस्य 5 का बदला हुआ मत नहीं गिना गया। चारों रसीदों में से दो एक ही सदस्य की हैं, यह केवल नलिफ़ायर बताता है। रसीदों में और कुछ इसका संकेत नहीं देता। पोल खुला है: आठ डेमो सदस्यों में से किसी के रूप में मत दें और गणना फिर से चलाएँ।
प्रमाण आपको क्या नहीं देता
- प्रति सीक्रेट एक मत, प्रति व्यक्ति एक मत नहीं। किसे लीफ़ मिलेगा, यह आयोजक तय करता है। जो सदस्य दो लीफ़ पंजीकृत करता है, उसके पास दो मत होते हैं।
- मतदाता का सीक्रेट उतना ही सुरक्षित है जितनी उसकी यादृच्छिकता। अगर सीक्रेट किसी अनुमान लगाने योग्य स्ट्रिंग से बना है, तो उसे खोजा जा सकता है: अनुमानों को हैश करें और प्रकाशित लीफ़ से मिलाएँ। 32 यादृच्छिक बाइट्स का उपयोग करें।
- विकल्प सार्वजनिक है। रसीद दिखाती है कि किसी नलिफ़ायर ने किस विकल्प के लिए मत दिया। नलिफ़ायर किस सदस्य का है, यह कोई नहीं बता सकता। पर कम मतदाताओं और प्रकाशित मतदान संख्या वाला पोल छोटी संख्याओं का सांख्यिकी प्रश्न है, क्रिप्टोग्राफ़ी नहीं।
- समय और शुल्क मेटाडेटा हैं। शुल्क बंडल शील्डेड है और किसी का नाम नहीं बताता। पर अगर कोई मतदाता शांत घंटे में अपने वॉलेट में धन डालता है और एक मिनट बाद मत देता है, तो वह देखने वाले को कुछ बता देता है।
- गणना ऑफ़-चेन होती है। चेन रसीदें संग्रहीत करती है। उन्हें गिनना और दूसरे नलिफ़ायर को अस्वीकार करना वह करता है जो उन्हें पढ़ता है।