वॉलेट
कुंजियाँ, पते और भेजना — साथ ही वह व्यावहारिक अनुबंध जिसे इस प्रोटोकॉल पर हर वॉलेट को पूरा करना है। हर नियम पैसा गँवाने का एक ऐसा तरीक़ा बताता है जिसकी प्रोटोकॉल इजाज़त देता है, और जिसे इसीलिए वॉलेट को रोकना चाहिए।
कमांड#
zcd wallet new --out KEYFILE # create an encrypted key file
zcd wallet address --key KEYFILE # show an address
zcd wallet balance --key KEYFILE # ask a node for a balance
zcd wallet send --key KEYFILE --to ADDR --amount N
zcd wallet sweep --key KEYFILE --to ADDR --one-shot
zcd wallet retire --key KEYFILE [--addr ADDR]
zcd ui --key KEYFILE # the same wallet in a browser
ग्राफ़िकल वॉलेट कोई दूसरा कार्यान्वयन नहीं है। zcd wallet, zcd
ui और डेस्कटॉप एप्लिकेशन एक ही wallet/session पर तीन
इंटरफ़ेस हैं: हर एक अपना सर्टिफिकेट वहीं बनाता है, और नियमों की जाँच उसी कॉल के भीतर चलती है।
इसलिए ग्राफ़िकल वॉलेट संरचनात्मक रूप से CLI से ज़्यादा ढीला हो ही नहीं सकता — इसलिए नहीं कि उसे लिखने
वाला सावधान था, बल्कि इसलिए कि कोई दूसरा कोड-पथ है ही नहीं जिस पर वह ढीला हो सके।
पतों के दो प्रकार#
| संस्करण | प्रकार | किसके लिए | दोबारा इस्तेमाल? |
|---|---|---|---|
0x01 | One-shot | एक अपेक्षित भुगतान। हर प्राप्त भुगतान के लिए एक नया पता निकालें। | नहीं। एक डेबिट उसे हमेशा के लिए जला देता है। |
0x02 | Persistent | व्यापारी, दान पते, हॉट वॉलेट, माइनिंग भुगतान। | हाँ, हमेशा। कभी spent registry में नहीं जा सकता। |
one-shot बनाम persistent एक वॉलेट नीति है जो पते के एक बिट के रूप में दिखती है, दो
बहीखाते नहीं। वॉलेट डिफ़ॉल्ट रूप से प्रति प्राप्त भुगतान 0x01 लेते हैं, क्योंकि यही एक भुगतान को
अगले से अजुड़ा बनाता है; जिसे खाता चाहिए वह 0x02 इस्तेमाल करता है।
आठ नियम#
नीचे दिया गया हर नियम पैसा या शुल्क गँवाने का एक ऐसा तरीक़ा बताता है जिसकी प्रोटोकॉल इजाज़त देता है — जानबूझकर, क्योंकि विकल्प इससे बुरा था — और जिसे इसीलिए वॉलेट को रोकना चाहिए। संदर्भ वॉलेट इन्हें लागू करता है। वह इन्हें केवल दर्ज नहीं करता।
नियम 1 — पूरे cell साफ़ करें#
किसी 0x01 पते से खर्च करते समय पूरा शेष हटाएँ
— प्राप्तकर्ता को और अपने नियंत्रण वाले किसी change पते को। सर्टिफिकेट उस पते का
MARK_SPENT ढोता है, और उसके लागू होने के बाद उस पते के तहत हर पठन और लेखन हमेशा के लिए
विफल हो जाता है। कोई दूसरा ट्रांज़ैक्शन नहीं होता।
चेन अब इसे कुछ नरम कर देती है: जला दिए गए पते में जो कुछ बचा हो वह कमिट के समय सर्टिफिकेट के अपने
RefundTo cell में चला जाता है — पते के देशी शेष के लिए भी और हर उस cell के लिए भी जिसे
सर्टिफिकेट ख़ुद नाम लेकर बताता है। इसलिए अधूरी सफ़ाई अब बची हुई रक़म नष्ट नहीं करती; वह उसे आपके change पते
तक पहुँचा देती है।
एक सर्टिफिकेट में एक ही RefundTo होता है और वह कई हस्ताक्षरकर्ताओं के one-shot पते जला
सकता है, इसलिए आपके cell में बची रक़म उनके पते पर पहुँच सकती है।
ऐसे सर्टिफिकेट पर कभी सह-हस्ताक्षर न करें जो आपका one-shot पता जलाता हो और ऐसे पते पर रिफ़ंड करता हो
जो आपके नियंत्रण में नहीं है।
यह नियम किसे नहीं बचा सकता: ऐसी परिसंपत्ति में पड़ा शेष जिसका सर्टिफिकेट में ज़िक्र ही नहीं। किसी पते के तहत मौजूद परिसंपत्तियाँ किसी slot से निकाली नहीं जा सकतीं, इसलिए बिना नाम ली परिसंपत्ति तक पहुँचने का मतलब होता उसी इकलौते चरण में पूरी cell तालिका छानना जो समानांतर नहीं होता। पता जलाने वाले सर्टिफिकेट में हर परिसंपत्ति का नाम लें।
नियम 2 — RefundTo ऐसा पता हो जिसे आप अब भी इस्तेमाल कर सकें#
उसमें या तो आपके नियंत्रण वाला कोई persistent पता होना चाहिए या कोई नया
one-shot पता जिसे यह सर्टिफिकेट जलाता न हो। जले हुए cell में बसाने से बची रक़म फँस जाती है: fold उसे ऐसे
cell में लिखने के बजाय जिसे कोई पढ़ न सके, जला देता है, और उसे refunded के बजाय
refund_burned बताता है, ताकि शेष का मिलान करने वाला वॉलेट यह पहचान सके।
नियम 3 — एक पता, एक अपेक्षित भुगतान#
दो बाध्यताएँ, हर पक्ष पर एक:
- प्राप्त करना। हर अपेक्षित भुगतान के लिए एक नया
0x01पता निकालें। किसी को दो बार प्रकाशित न करें। पिछलेttl_maxब्लॉकों के भीतर उजागर किया गया कोई पता तब तक न साफ़ करें और न सेवामुक्त करें, जब तक आप यह न स्वीकार कर लें कि उस तक रास्ते में पड़ा भुगतान skip हो जाएगा और उसके भेजने वाले का बिल बनेगा। - भेजना। ऐसे one-shot पते पर भुगतान करने से मना करें जिसे आप पहले ही जमा या खर्च हुआ देख सकते हैं। अगर कोई प्राप्तकर्ता आपको दूसरी बार वही पता दे, तो उसे सुविधा नहीं, त्रुटि मानें।
जिस भी चीज़ का भुगतान एक से ज़्यादा बार होता हो — कोई व्यापारी, कोई दान पता, कोई माइनिंग
भुगतान — उसके लिए persistent (0x02) पता इस्तेमाल करें। यही वह
इकलौता नियम है जो जोखिम को कम करने के बजाय हटा देता है।
नियम 4 — निर्भर शृंखलाएँ: पुष्टि करें, या जोखिम स्वीकारें#
हर उस सर्टिफिकेट के लिए Seq बढ़ाएँ जो किसी पिछले पर निर्भर है। एक ही ब्लॉक के भीतर fold
किसी हस्ताक्षरकर्ता के सर्टिफिकेट Seq क्रम में कमिट करता है, इसलिए एक ही ब्लॉक में निर्भर
शृंखला सुरक्षित है। ब्लॉकों के आर-पार वह सुरक्षित नहीं है: Seq = n+1
तभी प्रसारित करें जब Seq = n पुष्ट हो चुका हो, वरना यह स्वीकार करें कि निर्भर वाला अकेले कमिट
होकर skip हो सकता है। यह प्रोटोकॉल की खामी नहीं है — हस्ताक्षर करके हस्ताक्षरकर्ता ने बासीपन का जोखिम
स्वीकार कर लिया।
नियम 5 — अधिकतम उदारता से रखें और प्राथमिकता ईमानदारी से#
हर बाज़ार दो दाम लेता है। अधिकतम एक शोधन-क्षमता सीमा है: जैसे ही आधार शुल्क उससे आगे निकलता है, सर्टिफिकेट शामिल-अयोग्य हो जाता है और उस पर दोबारा हस्ताक्षर करना पड़ता है। प्राथमिकता वह है जो माइनर को वास्तव में मिलती है।
- अधिकतम बढ़ाने से शुल्क में कुछ नहीं लगता — सुरक्षा का अंतर मुफ़्त है।
- पर जमा राशि
gas × maxआरक्षित कर लेती है, इसलिए अधिकतम यह तय करता है कि एक fold चरण के लिए कितना शेष बँधेगा। उसे वास्तव में उपलब्ध शेष के हिसाब से रखें। - लंबे TTL वाले सर्टिफिकेट को छोटे TTL वाले से ज़्यादा गुंजाइश चाहिए, क्योंकि आधार शुल्क को हिलने के लिए ज़्यादा ब्लॉक मिलते हैं।
छोटे शेष के लिए निकास का रास्ता यह है कि खिड़की छोटी की जाए, सुरक्षा नहीं: छोटे TTL के साथ हस्ताक्षर करें और समाप्ति पर दोबारा हस्ताक्षर करें, यानी बँधी रक़म के बदले विलंब लें। ग़लत प्रतिक्रिया यह होगी कि लंबा TTL रखा जाए और अधिकतम घटा दिया जाए, जिससे सर्टिफिकेट ठीक तभी फँसने लायक हो जाता है जब बाज़ार हिलता है — और वही स्थिति है जिसके लिए वह अंतर रखा गया था।
नियम 6 — चालों को कैनोनिकल क्रम में लगाएँ#
प्रोटोकॉल किसी हस्तांतरण की चालों पर कोई क्रम नहीं थोपता, इसलिए कैनोनिकल क्रम के बिना उसी तार्किक भुगतान
को दोबारा भेजने पर अलग id बनती है और वॉलेट "पहले ही भेज दिया" और "दो बार भेज दिया" में फ़र्क़ नहीं कर पाता।
wallet.Transfer परिसंपत्ति, स्रोत, गंतव्य, फिर राशि के हिसाब से क्रम लगाता है, इसलिए दोबारा
भेजने पर वही id बनती है।
दोबारा भेजने पर वही हस्ताक्षर बनना ज़रूरी नहीं है: id के preimage में हस्ताक्षर सूची शामिल नहीं है, इसलिए नए nonce पर दोबारा हस्ताक्षरित प्रयास वही id है और नेटवर्क उसे वही डुप्लिकेट मानकर मना कर देता है। अनन्यता एक ऐसा गुण है जो प्रोटोकॉल ख़ुद देता है, हर वॉलेट के लिए, केवल सावधान वॉलेट के लिए नहीं।
नियम 7 — seed कभी किसी को न दें#
seed ही कुंजी है। जो कोई उसे पढ़ लेता है वह उन दोनों पतों में रखी हर चीज़ का मालिक है — one-shot और persistent दोनों, जो चेन पर एक-दूसरे से असंबद्ध हैं पर एक ही कुंजी से निकले हैं।
कुंजी फ़ाइलें हमेशा एन्क्रिप्टेड होती हैं: passphrase पर Argon2id, seed पर AES-256-GCM, और दोनों के पैरामीटर फ़ाइल में ही रखे जाते हैं, ताकि इस बाइनरी से बाहर रह गया उपयोगकर्ता किसी भी भाषा की मानक लाइब्रेरी से रिकवर कर सके। बिना एन्क्रिप्शन के लिखने के लिए कोई फ़्लैग नहीं है। passphrase टर्मिनल से बिना echo पढ़े जाते हैं, कभी किसी फ़्लैग से नहीं — कमांड लाइन पर पड़ा passphrase shell के इतिहास में और प्रक्रिया तालिका में होता है।
लेखन एक साथ दो गुण थामता है, और दोनों एक ही वाक्य के बारे में हैं — एक भी खोना पैसा खोना है:
- कभी चुपचाप ऊपर से न लिखें। गंतव्य पर पहले से मौजूद कुंजी फ़ाइल को छुआ नहीं जाता और लेखन विफल हो जाता है।
- कभी अधूरी फ़ाइल न छोड़ें। seed उसी डायरेक्टरी में एक अस्थायी फ़ाइल में जाता है, वहीं fsync होता है, और उसके बाद ही एक अकेली फ़ाइल-सिस्टम क्रिया में अपने अंतिम नाम से प्रकाशित होता है।
दोनों हर उस फ़ाइल-सिस्टम पर टिकते हैं जिस पर CLI को मापा गया, FAT32 और exFAT समेत — यानी वे प्रारूप जो कोल्ड-स्टोरेज USB बैकअप में सबसे ज़्यादा इस्तेमाल होते हैं।
FAT32 और exFAT में Unix अनुमति बिट होते ही नहीं, इसलिए कुंजी फ़ाइल जिस 0600 के साथ बनाई
जाती है वह उन पर टिकता नहीं — तय माउंट विकल्प करते हैं। Linux पर डिफ़ॉल्ट fmask उसे
आमतौर पर सबके पढ़ने लायक छोड़ देता है; macOS ऐसे वॉल्यूम noowners से माउंट करता है, जो
0700 बताता है और लागू कुछ नहीं करता। passphrase इसी हिसाब से चुनें। उनमें कोई journal भी नहीं
होता, इसलिए अगर FAT पर प्रकाशन बीच में रुक जाए और विफलता बताए, तो दोबारा चलाने से पहले गंतव्य जाँच लें।
नियम 8 — वही मना करें जो नेटवर्क मना करेगा#
जो वॉलेट ऐसी चीज़ की सफलता बताता है जिसे नेटवर्क सिर्फ़ फेंक सकता है, उसने विफलता को वहाँ खिसका दिया है जहाँ उपयोगकर्ता कभी नहीं देखेगा। इसके दो रूप, और दोनों वास्तव में देखे गए हैं:
- ऐसे बाइट जिन्हें कोई पीयर डिकोड न कर सके। codec अपने आप में एक प्राधिकारी है, जिसके नियम validator दोबारा नहीं दोहराता। builder अब यह गुण सीधे सिद्ध करता है: वह जो भी निकाले, decoder उसे स्वीकार करे।
- ऐसा हस्तांतरण जिसे fold केवल skip कर सकता है। स्रोत के शेष से ऊपर का हस्तांतरण किसी नियम को नहीं तोड़ता, इसलिए हर नोड उसे भीतर आने देता है — और जो उत्पादक उसे फिर भी शामिल कर ले उसे कोई मना नहीं करता, और तब fold उसे
skip_feeपर निपटा देता है, जो जमा राशि से जलता है और किसी को नहीं मिलता। बुरी बात "कोई असर नहीं" नहीं है, बुरी बात यह है कि "शुल्क जल गया और मूल्य कभी हिला ही नहीं"।
zcd wallet send --force उसी एक इनकार को लाँघता है और किसी और को नहीं,
क्योंकि TTL खिड़की के भीतर पहुँचने की उम्मीद वाली जमा राशि उसी सर्टिफिकेट को लागू करा देती है और वॉलेट भविष्य
नहीं देख सकता। वह किसी अमान्य सर्टिफिकेट को valid नहीं बना सकता; वह केवल ऐसा valid सर्टिफिकेट जमा कर सकता है
जो skip हो सकता है — और skip मुफ़्त नहीं होता।
उस नोड पर भरोसा जिसे आप ख़ुद नहीं चलाते#
zcd और वॉलेट पूर्ण नोड नहीं हैं। वे वही मानते हैं जो कोई नोड उन्हें बताता है, और जो CLI
ऐसे नोड से RPC पर बात करती है जिसका सत्यापन वह ख़ुद नहीं करती उसे उस नोड के उत्तरों के बारे में कुछ
मानना ही पड़ता है — यह ठीक करने लायक बग नहीं है, वॉलेट होता ही यही है। तीन उपाय, हर एक किसी एक
ख़ास झूठ के लिए:
| उपाय | यह क्या पकड़ता है |
|---|---|
--devnet / --testnet / --params | ऑपरेटर यह घोषित करता है कि वह किस नेटवर्क के लिए हस्ताक्षर करना चाहता है, और पूछे गए हर नोड को उसी घोषणा के विरुद्ध जाँचा जाता है। नेटवर्क की पहचान कभी अकेले किसी नोड से नहीं ली जाती। यह balance पर भी लागू होता है, केवल हस्ताक्षर करने वाली कमांड पर नहीं। |
--confirm-rpc NODE | एक दूसरे, स्वतंत्र नोड का नाम देता है। आगे कुछ भी होने से पहले हर पते का शेष और spent फ़्लैग दोनों नोड एक जैसा बताएँ, यह ज़रूरी है। जिस नोड की chain_id असहमत हो वह अस्वीकृत है, और ऐसा --confirm-rpc जो वही एंडपॉइंट नाम दे जो --rpc पहले ही नाम दे चुका है, सिरे से अस्वीकृत है — ख़ुद से जाँच करता नोड ख़ुद से सहमत ही होता है। |
| टाइप की गई पुष्टि | जमा करने से पहले zcd wallet sweep ठीक-ठीक अंक छापता है और आपसे sweep टाइप कराता है। --yes केवल यही संकेत छोड़ता है, उससे ऊपर की जाँचें कभी नहीं। |
वॉलेट कार्यान्वयन के लिए जाँच-सूची#
अगर आप इस प्रोटोकॉल के लिए वॉलेट लिख रहे हैं, तो अनुबंध यह है:
0x01से खर्च पूरा शेष हटाता है, और जमा आरक्षण को भी उसी का हिस्सा गिनता है — उन प्रोग्रामों पर भी जिनमें कोई चाल है ही नहीं।RETIREऐसे लक्ष्य को मना कर देता है जिसमें अब भी कोई शेष पड़ा हो।- किसी शेष के बारे में नोड की रिपोर्ट को प्रश्न से परे नहीं माना जाता: नेटवर्क की पहचान ऑपरेटर घोषित करता है, दूसरा स्रोत मिलान के लिए उपलब्ध रहता है, और किसी अपरिवर्तनीय sweep से पहले ठीक-ठीक अंकों की पुष्टि होती है।
- हस्ताक्षर से पहले
RefundToको persistent-या-नया होने के लिए सत्यापित किया जाता है। - प्राप्ति हर अपेक्षित भुगतान के लिए नया पता निकालती है; भेजना ऐसे one-shot पते को मना करता है जिसमें पहले ही जमा हो चुका या जो खर्च हो चुका है।
- व्यापारी और भुगतान पते डिफ़ॉल्ट रूप से
0x02होते हैं। Seqप्रति निर्भर सर्टिफिकेट बढ़ता है, और वॉलेट अगला प्रसारित करने से पहले पुष्टि का इंतज़ार करता है — या खुलकर कहता है कि वह ऐसा नहीं कर रहा।- शुल्क की अधिकतम सीमाएँ उपलब्ध शेष और TTL से तय होती हैं, कोड में जड़ी हुई नहीं।
- चालें कैनोनिकल क्रम में लगाई जाती हैं, ताकि दोबारा भेजने पर वही सर्टिफिकेट id बने।
- कुंजी फ़ाइलें एन्क्रिप्टेड होती हैं, टिकाऊ ढंग से और बिना चुपचाप ऊपर लिखे बनाई जाती हैं, और passphrase कभी किसी फ़्लैग तक नहीं पहुँचते।
- ऐसी किसी चीज़ पर हस्ताक्षर नहीं होता जिसकी अपनी एन्कोडिंग से उसे वापस डिकोड न किया जा सके, और ऐसी कोई चीज़ जमा नहीं होती जिसे स्रोत का शेष सँभाल न सके — और कोई भी छूट नामित, सँकरी, और पूर्वावलोकन में बताई हुई होती है।