முக்கிய உள்ளடக்கத்திற்குச் செல்லவும்

தனியுரிமையைப் பாதுகாக்கும் பயன்பாடு சார்ந்த பிளாஸ்மாவை (app-specific plasma) எழுதுங்கள்

பூஜ்ஜிய-அறிவு
சேவையகம்
ஆஃப்செயின்
தனியுரிமை
மேம்பட்ட
ஓரி பொமரன்ட்ஸ்
15 அக்டோபர், 2025
26 நிமிட வாசிப்பு

அறிமுகம்

ரோலப்களுக்கு மாறாக, பிளாஸ்மாக்கள் ஒருமைப்பாட்டிற்காக (integrity) Ethereum மெயின்நெட்டைப் பயன்படுத்துகின்றன, ஆனால் கிடைக்கும் தன்மைக்காக (availability) அல்ல. இந்தக் கட்டுரையில், பிளாஸ்மாவைப் போலச் செயல்படும் ஒரு பயன்பாட்டை எழுதுகிறோம், இதில் Ethereum ஒருமைப்பாட்டிற்கு உத்தரவாதம் அளிக்கிறது (அங்கீகரிக்கப்படாத மாற்றங்கள் இல்லை) ஆனால் கிடைக்கும் தன்மைக்கு அல்ல (ஒரு மையப்படுத்தப்பட்ட கூறு செயலிழந்து முழு அமைப்பையும் முடக்கலாம்).

நாம் இங்கு எழுதும் பயன்பாடு தனியுரிமையைப் பாதுகாக்கும் ஒரு வங்கியாகும். வெவ்வேறு முகவரிகள் இருப்புகளுடன் கணக்குகளைக் கொண்டுள்ளன, மேலும் அவை மற்ற கணக்குகளுக்குப் பணத்தை (ETH) அனுப்பலாம். வங்கி நிலை (கணக்குகள் மற்றும் அவற்றின் இருப்புகள்) மற்றும் பரிவர்த்தனைகளின் ஹாஷ்களை வெளியிடுகிறது, ஆனால் உண்மையான இருப்புகளை ஆஃப்செயினில் (offchain) தனிப்பட்டதாக வைத்திருக்கக்கூடிய இடத்தில் வைத்திருக்கிறது.

வடிவமைப்பு

இது உற்பத்திக்குத் தயாரான அமைப்பு அல்ல, மாறாக ஒரு கற்பித்தல் கருவியாகும். எனவே, இது பல எளிமைப்படுத்தப்பட்ட அனுமானங்களுடன் எழுதப்பட்டுள்ளது.

  • நிலையான கணக்குத் தொகுப்பு (Fixed account pool). ஒரு குறிப்பிட்ட எண்ணிக்கையிலான கணக்குகள் உள்ளன, மேலும் ஒவ்வொரு கணக்கும் முன்னரே தீர்மானிக்கப்பட்ட முகவரிக்குச் சொந்தமானது. பூஜ்ஜிய-அறிவுச் சான்றுகளில் மாறுபடும் அளவிலான தரவுக் கட்டமைப்புகளைக் கையாள்வது கடினம் என்பதால் இது மிகவும் எளிமையான அமைப்பை உருவாக்குகிறது. உற்பத்திக்குத் தயாரான அமைப்பிற்கு, நாம் Merkle root-ஐ நிலை ஹாஷாகப் பயன்படுத்தலாம் மற்றும் தேவையான இருப்புகளுக்கு Merkle சான்றுகளை வழங்கலாம்.

  • நினைவகச் சேமிப்பு (Memory storage). ஒரு உற்பத்தி அமைப்பில், மறுதொடக்கம் செய்யப்படும்போது அவற்றைப் பாதுகாக்க அனைத்துக் கணக்கு இருப்புகளையும் வட்டில் (disk) எழுத வேண்டும். இங்கே, தகவல் வெறுமனே தொலைந்துபோனால் பரவாயில்லை.

  • இடமாற்றங்கள் மட்டும் (Transfers only). ஒரு உற்பத்தி அமைப்பிற்கு வங்கியில் சொத்துகளை டெபாசிட் செய்வதற்கும் அவற்றைத் திரும்பப் பெறுவதற்கும் ஒரு வழி தேவைப்படும். ஆனால் இங்குள்ள நோக்கம் கருத்தை விளக்குவது மட்டுமே, எனவே இந்த வங்கி இடமாற்றங்களுக்கு மட்டுமே வரம்பிடப்பட்டுள்ளது.

பூஜ்ஜிய-அறிவுச் சான்றுகள்

அடிப்படை அளவில், ஒரு பூஜ்ஜிய-அறிவுச் சான்று, நிரூபிப்பவர் சில தரவுகளை அறிந்திருக்கிறார் என்பதைக் காட்டுகிறது, Dataprivate அதாவது சில பொதுத் தரவுகளான Datapublic மற்றும் Dataprivate ஆகியவற்றுக்கு இடையே ஒரு உறவு Relationship உள்ளது. சரிபார்ப்பவருக்கு Relationship மற்றும் Datapublic தெரியும்.

தனியுரிமையைப் பாதுகாக்க, நிலைகள் மற்றும் பரிவர்த்தனைகள் தனிப்பட்டதாக இருக்க வேண்டும். ஆனால் ஒருமைப்பாட்டை உறுதிப்படுத்த, நிலைகளின் கிரிப்டோகிராஃபிக் ஹாஷ் (opens in a new tab) பொதுவில் இருக்க வேண்டும். பரிவர்த்தனைகளைச் சமர்ப்பிக்கும் நபர்களுக்கு அந்தப் பரிவர்த்தனைகள் உண்மையில் நடந்தன என்பதை நிரூபிக்க, நாம் பரிவர்த்தனை ஹாஷ்களையும் வெளியிட வேண்டும்.

பெரும்பாலான சந்தர்ப்பங்களில், Dataprivate என்பது பூஜ்ஜிய-அறிவுச் சான்று நிரலுக்கான உள்ளீடாகும், மேலும் Datapublic என்பது வெளியீடாகும்.

Dataprivate-இல் உள்ள இந்தப் புலங்கள்:

  • Staten, பழைய நிலை
  • Staten+1, புதிய நிலை
  • Transaction, பழைய நிலையிலிருந்து புதிய நிலைக்கு மாறும் ஒரு பரிவர்த்தனை. இந்தப் பரிவர்த்தனையில் இந்தப் புலங்கள் இருக்க வேண்டும்:
    • இடமாற்றத்தைப் பெறும் இலக்கு முகவரி (Destination address)
    • மாற்றப்படும் தொகை (Amount)
    • ஒவ்வொரு பரிவர்த்தனையும் ஒரு முறை மட்டுமே செயல்படுத்தப்படுவதை உறுதிசெய்யும் நான்ஸ் (Nonce). மூல முகவரி பரிவர்த்தனையில் இருக்க வேண்டியதில்லை, ஏனெனில் அதைக் கையொப்பத்திலிருந்து மீட்டெடுக்க முடியும்.
  • Signature, பரிவர்த்தனையைச் செய்ய அங்கீகரிக்கப்பட்ட ஒரு கையொப்பம். நம்முடைய விஷயத்தில், பரிவர்த்தனையைச் செய்ய அங்கீகரிக்கப்பட்ட ஒரே முகவரி மூல முகவரி மட்டுமே. நமது பூஜ்ஜிய-அறிவு அமைப்பு செயல்படும் விதத்தின் காரணமாக, Ethereum கையொப்பத்துடன் கூடுதலாகக் கணக்கின் பொதுத் திறவுகோலும் (public key) நமக்குத் தேவை.

Datapublic-இல் உள்ள புலங்கள் இவை:

  • Hash(Staten) பழைய நிலையின் ஹாஷ்
  • Hash(Staten+1) புதிய நிலையின் ஹாஷ்
  • Hash(Transaction) நிலையை Staten-இலிருந்து Staten+1-க்கு மாற்றும் பரிவர்த்தனையின் ஹாஷ்.

உறவு பல நிபந்தனைகளைச் சரிபார்க்கிறது:

  • பொது ஹாஷ்கள் உண்மையில் தனிப்பட்ட புலங்களுக்கான சரியான ஹாஷ்களாகும்.
  • பரிவர்த்தனை, பழைய நிலையில் பயன்படுத்தப்படும்போது, புதிய நிலையை விளைவிக்கிறது.
  • கையொப்பம் பரிவர்த்தனையின் மூல முகவரியிலிருந்து வருகிறது.

கிரிப்டோகிராஃபிக் ஹாஷ் செயல்பாடுகளின் பண்புகள் காரணமாக, இந்த நிபந்தனைகளை நிரூபிப்பது ஒருமைப்பாட்டை உறுதிப்படுத்தப் போதுமானது.

தரவுக் கட்டமைப்புகள்

முதன்மைத் தரவுக் கட்டமைப்பு என்பது சேவையகத்தால் (server) வைத்திருக்கப்படும் நிலையாகும். ஒவ்வொரு கணக்கிற்கும், சேவையகம் கணக்கு இருப்பு மற்றும் மறுஇயக்கத் தாக்குதல்களைத் (replay attacks) (opens in a new tab) தடுக்கப் பயன்படுத்தப்படும் ஒரு நான்ஸைக் (nonce) (opens in a new tab) கண்காணிக்கிறது.

கூறுகள்

இந்த அமைப்பிற்கு இரண்டு கூறுகள் தேவை:

  • பரிவர்த்தனைகளைப் பெறும், அவற்றைச் செயல்படுத்தும் மற்றும் பூஜ்ஜிய-அறிவுச் சான்றுகளுடன் சங்கிலியில் ஹாஷ்களை வெளியிடும் சேவையகம் (server).
  • ஹாஷ்களைச் சேமித்து, நிலை மாற்றங்கள் முறையானவை என்பதை உறுதிப்படுத்தப் பூஜ்ஜிய-அறிவுச் சான்றுகளைச் சரிபார்க்கும் ஒரு ஸ்மார்ட் ஒப்பந்தம் (smart contract).

தரவு மற்றும் கட்டுப்பாட்டு ஓட்டம்

ஒரு கணக்கிலிருந்து மற்றொரு கணக்கிற்கு மாற்றுவதற்குப் பல்வேறு கூறுகள் தொடர்பு கொள்ளும் வழிகள் இவை.

  1. கையொப்பமிட்டவரின் கணக்கிலிருந்து வேறு கணக்கிற்கு மாற்றக் கோரும் கையொப்பமிடப்பட்ட பரிவர்த்தனையை இணைய உலாவி சமர்ப்பிக்கிறது.

  2. பரிவர்த்தனை செல்லுபடியாகும் என்பதைச் சேவையகம் சரிபார்க்கிறது:

    • கையொப்பமிட்டவர் வங்கியில் போதுமான இருப்புடன் ஒரு கணக்கைக் கொண்டுள்ளார்.
    • பெறுநருக்கு வங்கியில் கணக்கு உள்ளது.
  3. கையொப்பமிட்டவரின் இருப்பிலிருந்து மாற்றப்பட்ட தொகையைக் கழித்து, அதைப் பெறுநரின் இருப்பில் சேர்ப்பதன் மூலம் சேவையகம் புதிய நிலையைக் கணக்கிடுகிறது.

  4. நிலை மாற்றம் செல்லுபடியாகும் என்பதற்கான பூஜ்ஜிய-அறிவுச் சான்றைச் சேவையகம் கணக்கிடுகிறது.

  5. சேவையகம் Ethereum-க்கு ஒரு பரிவர்த்தனையைச் சமர்ப்பிக்கிறது, அதில் பின்வருவன அடங்கும்:

    • புதிய நிலை ஹாஷ்
    • பரிவர்த்தனை ஹாஷ் (எனவே பரிவர்த்தனை அனுப்புநர் அது செயல்படுத்தப்பட்டதை அறிய முடியும்)
    • புதிய நிலைக்கான மாற்றம் செல்லுபடியாகும் என்பதை நிரூபிக்கும் பூஜ்ஜிய-அறிவுச் சான்று
  6. ஸ்மார்ட் ஒப்பந்தம் பூஜ்ஜிய-அறிவுச் சான்றைச் சரிபார்க்கிறது.

  7. பூஜ்ஜிய-அறிவுச் சான்று சரிபார்க்கப்பட்டால், ஸ்மார்ட் ஒப்பந்தம் இந்தச் செயல்களைச் செய்கிறது:

    • தற்போதைய நிலை ஹாஷைப் புதிய நிலை ஹாஷாகப் புதுப்பிக்கிறது
    • புதிய நிலை ஹாஷ் மற்றும் பரிவர்த்தனை ஹாஷுடன் ஒரு பதிவு உள்ளீட்டை (log entry) வெளியிடுகிறது

கருவிகள்

கிளையன்ட் பக்கக் குறியீட்டிற்கு, நாம் Vite (opens in a new tab), React (opens in a new tab), Viem (opens in a new tab) மற்றும் Wagmi (opens in a new tab) ஆகியவற்றைப் பயன்படுத்தப் போகிறோம். இவை தொழில்துறை-தரமான கருவிகள்; உங்களுக்கு இவற்றைப் பரிச்சயம் இல்லை என்றால், நீங்கள் இந்த வழிகாட்டியைப் பயன்படுத்தலாம்.

சேவையகத்தின் பெரும்பகுதி Node (opens in a new tab)-ஐப் பயன்படுத்தி JavaScript-இல் எழுதப்பட்டுள்ளது. பூஜ்ஜிய-அறிவுப் பகுதி Noir (opens in a new tab)-இல் எழுதப்பட்டுள்ளது. நமக்கு 1.0.0-beta.10 பதிப்பு தேவை, எனவே நீங்கள் அறிவுறுத்தப்பட்டபடி Noir-ஐ நிறுவிய (opens in a new tab) பிறகு, இதை இயக்கவும்:

1noirup -v 1.0.0-beta.10

நாம் பயன்படுத்தும் பிளாக்செயின் anvil ஆகும், இது Foundry (opens in a new tab)-இன் ஒரு பகுதியாக இருக்கும் உள்ளூர்ச் சோதனை பிளாக்செயின் ஆகும்.

செயல்படுத்தல்

இது ஒரு சிக்கலான அமைப்பு என்பதால், இதை நாம் கட்டங்களாகச் செயல்படுத்துவோம்.

கட்டம் 1 - கைமுறைப் பூஜ்ஜிய அறிவு

முதல் கட்டத்திற்கு, நாம் உலாவியில் ஒரு பரிவர்த்தனையில் கையொப்பமிடுவோம், பின்னர் பூஜ்ஜிய-அறிவுச் சான்றுக்குத் தகவலைக் கைமுறையாக வழங்குவோம். பூஜ்ஜிய-அறிவுக் குறியீடு அந்தத் தகவலை server/noir/Prover.toml-இல் பெற எதிர்பார்க்கிறது (இங்கே (opens in a new tab) ஆவணப்படுத்தப்பட்டுள்ளது).

இது செயல்படுவதைப் பார்க்க:

  1. உங்களிடம் Node (opens in a new tab) மற்றும் Noir (opens in a new tab) நிறுவப்பட்டுள்ளதா என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள். முன்னுரிமையாக, அவற்றை macOS, Linux அல்லது WSL (opens in a new tab) போன்ற UNIX அமைப்பில் நிறுவவும்.

  2. கட்டம் 1 குறியீட்டைப் பதிவிறக்கி, கிளையன்ட் குறியீட்டை வழங்க இணையச் சேவையகத்தைத் தொடங்கவும்.

    1git clone https://github.com/qbzzt/250911-zk-bank.git -b 01-manual-zk
    2cd 250911-zk-bank
    3cd client
    4npm install
    5npm run dev

    சில வகையான மோசடிகளைத் தடுக்க, பல வாலட்கள் (MetaMask போன்றவை) வட்டிலிருந்து நேரடியாக வழங்கப்படும் கோப்புகளை ஏற்காது என்பதே இங்கு உங்களுக்கு இணையச் சேவையகம் தேவைப்படுவதற்குக் காரணமாகும்.

  3. வாலட்டுடன் ஒரு உலாவியைத் திறக்கவும்.

  4. வாலட்டில், புதிய கடவுச்சொற்றொடரை (passphrase) உள்ளிடவும். இது உங்களின் தற்போதைய கடவுச்சொற்றொடரை நீக்கிவிடும் என்பதை நினைவில் கொள்ளவும், எனவே உங்களிடம் காப்புப்பிரதி (backup) இருப்பதை உறுதிசெய்து கொள்ளவும்.

    கடவுச்சொற்றொடர் test test test test test test test test test test test junk ஆகும், இது anvil-க்கான இயல்புநிலைச் சோதனைக் கடவுச்சொற்றொடராகும்.

  5. கிளையன்ட் பக்கக் குறியீட்டிற்கு (opens in a new tab) உலாவவும்.

  6. வாலட்டுடன் இணைத்து, உங்கள் இலக்குக் கணக்கு மற்றும் தொகையைத் தேர்ந்தெடுக்கவும்.

  7. Sign என்பதைக் கிளிக் செய்து பரிவர்த்தனையில் கையொப்பமிடவும்.

  8. Prover.toml தலைப்பின் கீழ், நீங்கள் உரையைக் காண்பீர்கள். server/noir/Prover.toml-ஐ அந்த உரையுடன் மாற்றவும்.

  9. பூஜ்ஜிய-அறிவுச் சான்றைச் செயல்படுத்தவும்.

    1cd ../server/noir
    2nargo execute

    வெளியீடு இதைப் போலவே இருக்க வேண்டும்

    1ori@CryptoDocGuy:~/noir/250911-zk-bank/server/noir$ nargo execute
    2
    3[zkBank] Circuit witness successfully solved
    4[zkBank] Witness saved to target/zkBank.gz
    5[zkBank] Circuit output: (0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b, 0x0cfc0a67cb7308e4e9b254026b54204e34f6c8b041be207e64c5db77d95dd82d, 0x450cf9da6e180d6159290554ae3d8787, 0x6d8bc5a15b9037e52fb59b6b98722a85)
  10. செய்தி சரியாக ஹாஷ் செய்யப்பட்டுள்ளதா என்பதைப் பார்க்க, இணைய உலாவியில் நீங்கள் காணும் ஹாஷுடன் கடைசி இரண்டு மதிப்புகளை ஒப்பிடவும்.

server/noir/Prover.toml

இந்தக் கோப்பு (opens in a new tab) Noir எதிர்பார்க்கும் தகவல் வடிவமைப்பைக் காட்டுகிறது.

1message="send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 500 finney (milliEth) 0 "

செய்தி உரை வடிவத்தில் உள்ளது, இது பயனருக்குப் புரிந்துகொள்வதை எளிதாக்குகிறது (கையொப்பமிடும்போது இது அவசியம்) மற்றும் Noir குறியீட்டைப் பாகுபடுத்துவதற்கும் (parse) எளிதாக்குகிறது. ஒருபுறம் பின்ன இடமாற்றங்களைச் செயல்படுத்துவதற்கும், மறுபுறம் எளிதாகப் படிக்கக்கூடியதாக இருப்பதற்கும் தொகை ஃபின்னிகளில் (finneys) குறிப்பிடப்பட்டுள்ளது. கடைசி எண் நான்ஸ் (opens in a new tab) ஆகும்.

சரம் (string) 100 எழுத்துகள் நீளமானது. பூஜ்ஜிய-அறிவுச் சான்றுகள் மாறுபடும் அளவிலான தரவைச் சரியாகக் கையாளுவதில்லை, எனவே தரவை நிரப்புவது (pad) பெரும்பாலும் அவசியமாகிறது.

1pubKeyX=["0x83",...,"0x75"]
2pubKeyY=["0x35",...,"0xa5"]
3signature=["0xb1",...,"0x0d"]

இந்த மூன்று அளவுருக்களும் நிலையான அளவிலான பைட் வரிசைகளாகும் (byte arrays).

1[[accounts]]
2address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
3balance=100_000
4nonce=0
5
6[[accounts]]
7address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
8balance=100_000
9nonce=0
அனைத்தையும் காட்டு

கட்டமைப்புகளின் வரிசையைக் குறிப்பிடுவதற்கான வழி இதுவாகும். ஒவ்வொரு உள்ளீட்டிற்கும், முகவரி, இருப்பு (milliETH அல்லது finney (opens in a new tab)-இல்) மற்றும் அடுத்த நான்ஸ் மதிப்பைக் குறிப்பிடுகிறோம்.

client/src/Transfer.tsx

இந்தக் கோப்பு (opens in a new tab) கிளையன்ட் பக்கச் செயலாக்கத்தைச் செயல்படுத்துகிறது மற்றும் server/noir/Prover.toml கோப்பை உருவாக்குகிறது (பூஜ்ஜிய-அறிவு அளவுருக்களை உள்ளடக்கியது).

மிகவும் சுவாரஸ்யமான பகுதிகளின் விளக்கம் இங்கே.

1export default attrs => {

இந்தச் செயல்பாடு Transfer React கூறுகளை உருவாக்குகிறது, இதை மற்ற கோப்புகள் இறக்குமதி செய்யலாம்.

1 const accounts = [
2 "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
3 "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
4 "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
5 "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
6 "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
7 ]

இவை கணக்கு முகவரிகள், test ... test junk கடவுச்சொற்றொடரால் உருவாக்கப்பட்ட முகவரிகள். உங்கள் சொந்த முகவரிகளைப் பயன்படுத்த விரும்பினால், இந்த வரையறையை மாற்றியமைக்கவும்.

1 const account = useAccount()
2 const wallet = createWalletClient({
3 transport: custom(window.ethereum!)
4 })

இந்த Wagmi ஹூக்குகள் (opens in a new tab) viem (opens in a new tab) நூலகத்தையும் வாலட்டையும் அணுக அனுமதிக்கின்றன.

1 const message = `send ${toAccount} ${ethAmount*1000} finney (milliEth) ${nonce}`.padEnd(100, " ")

இது இடைவெளிகளுடன் நிரப்பப்பட்ட செய்தியாகும். ஒவ்வொரு முறையும் useState (opens in a new tab) மாறிகளில் ஒன்று மாறும்போது, கூறு மீண்டும் வரையப்பட்டு message புதுப்பிக்கப்படும்.

1 const sign = async () => {

பயனர் Sign பொத்தானைக் கிளிக் செய்யும்போது இந்தச் செயல்பாடு அழைக்கப்படுகிறது. செய்தி தானாகவே புதுப்பிக்கப்படும், ஆனால் கையொப்பத்திற்கு வாலட்டில் பயனர் ஒப்புதல் தேவை, மேலும் தேவைப்படாவிட்டால் அதைக் கேட்க நாங்கள் விரும்பவில்லை.

1 const signature = await wallet.signMessage({
2 account: fromAccount,
3 message,
4 })

செய்தியில் கையொப்பமிட (opens in a new tab) வாலட்டைக் கேட்கவும்.

1 const hash = hashMessage(message)

செய்தி ஹாஷைப் பெறவும். பிழைத்திருத்தத்திற்காக (Noir குறியீட்டின்) பயனருக்கு இதை வழங்குவது உதவியாக இருக்கும்.

1 const pubKey = await recoverPublicKey({
2 hash,
3 signature
4 })

பொதுத் திறவுகோலைப் பெறவும் (opens in a new tab). Noir ecrecover (opens in a new tab) செயல்பாட்டிற்கு இது தேவை.

1 setSignature(signature)
2 setHash(hash)
3 setPubKey(pubKey)

நிலை மாறிகளை அமைக்கவும். இதைச் செய்வது கூறுகளை மீண்டும் வரைகிறது (sign செயல்பாடு வெளியேறிய பிறகு) மற்றும் புதுப்பிக்கப்பட்ட மதிப்புகளைப் பயனருக்குக் காட்டுகிறது.

1 let proverToml = `

Prover.toml-க்கான உரை.

1message="${message}"
2
3pubKeyX=${hexToArray(pubKey.slice(4,4+2*32))}
4pubKeyY=${hexToArray(pubKey.slice(4+2*32))}

Viem பொதுத் திறவுகோலை 65-பைட் ஹெக்ஸாடெசிமல் சரமாக நமக்கு வழங்குகிறது. முதல் பைட் 0x04, இது ஒரு பதிப்புக் குறிப்பான். இதைத் தொடர்ந்து பொதுத் திறவுகோலின் x-க்கு 32 பைட்டுகளும், பின்னர் பொதுத் திறவுகோலின் y-க்கு 32 பைட்டுகளும் உள்ளன.

இருப்பினும், Noir இந்தத் தகவலை இரண்டு-பைட் வரிசைகளாகப் பெற எதிர்பார்க்கிறது, ஒன்று x-க்கும் மற்றொன்று y-க்கும். பூஜ்ஜிய-அறிவுச் சான்றின் ஒரு பகுதியாக இல்லாமல், கிளையன்டில் இதைப் பாகுபடுத்துவது எளிது.

பொதுவாகப் பூஜ்ஜிய-அறிவில் இது ஒரு நல்ல நடைமுறை என்பதை நினைவில் கொள்ளவும். பூஜ்ஜிய-அறிவுச் சான்றுக்குள் உள்ள குறியீடு விலை உயர்ந்தது, எனவே பூஜ்ஜிய-அறிவுச் சான்றுக்கு வெளியே செய்யக்கூடிய எந்தவொரு செயலாக்கமும் பூஜ்ஜிய-அறிவுச் சான்றுக்கு வெளியே செய்யப்பட வேண்டும்.

1signature=${hexToArray(signature.slice(2,-2))}

கையொப்பமும் 65-பைட் ஹெக்ஸாடெசிமல் சரமாக வழங்கப்படுகிறது. இருப்பினும், பொதுத் திறவுகோலை மீட்டெடுக்கக் கடைசி பைட் மட்டுமே அவசியம். பொதுத் திறவுகோல் ஏற்கனவே Noir குறியீட்டிற்கு வழங்கப்பட்டிருக்கும் என்பதால், கையொப்பத்தைச் சரிபார்க்க அது நமக்குத் தேவையில்லை, மேலும் Noir குறியீட்டிற்கும் அது தேவையில்லை.

1${accounts.map(accountInProverToml).reduce((a,b) => a+b, "")}
2`

கணக்குகளை வழங்கவும்.

1 setProverToml(proverToml)
2 }
3
4 return (
5 <>
6 <h2>Transfer</h2>

இது கூறின் HTML (மிகத் துல்லியமாக, JSX (opens in a new tab)) வடிவமாகும்.

server/noir/src/main.nr

இந்தக் கோப்பு (opens in a new tab) உண்மையான பூஜ்ஜிய-அறிவுக் குறியீடாகும்.

1use std::hash::pedersen_hash;

Pedersen ஹாஷ் (opens in a new tab) Noir நிலையான நூலகத்துடன் (opens in a new tab) வழங்கப்படுகிறது. பூஜ்ஜிய-அறிவுச் சான்றுகள் பொதுவாக இந்த ஹாஷ் செயல்பாட்டைப் பயன்படுத்துகின்றன. நிலையான ஹாஷ் செயல்பாடுகளுடன் ஒப்பிடும்போது எண்கணிதச் சுற்றுகளுக்குள் (arithmetic circuits) (opens in a new tab) கணக்கிடுவது மிகவும் எளிதானது.

1use keccak256::keccak256;
2use dep::ecrecover;

இந்த இரண்டு செயல்பாடுகளும் Nargo.toml (opens in a new tab)-இல் வரையறுக்கப்பட்ட வெளிப்புற நூலகங்களாகும். அவை அவற்றின் பெயருக்கு ஏற்றவாறு துல்லியமாகச் செயல்படுகின்றன, keccak256 ஹாஷைக் (opens in a new tab) கணக்கிடும் ஒரு செயல்பாடு மற்றும் Ethereum கையொப்பங்களைச் சரிபார்த்துக் கையொப்பமிட்டவரின் Ethereum முகவரியை மீட்டெடுக்கும் ஒரு செயல்பாடு.

1global ACCOUNT_NUMBER : u32 = 5;

Noir Rust (opens in a new tab)-ஆல் ஈர்க்கப்பட்டது. மாறிகள், இயல்புநிலையாக, மாறிலிகளாகும் (constants). உலகளாவிய உள்ளமைவு மாறிலிகளை நாம் இப்படித்தான் வரையறுக்கிறோம். குறிப்பாக, ACCOUNT_NUMBER என்பது நாம் சேமிக்கும் கணக்குகளின் எண்ணிக்கையாகும்.

u<number> எனப் பெயரிடப்பட்ட தரவு வகைகள் அந்த எண்ணிக்கையிலான பிட்களைக் கொண்டவை, கையொப்பமிடப்படாதவை (unsigned). ஆதரிக்கப்படும் ஒரே வகைகள் u8, u16, u32, u64 மற்றும் u128 ஆகும்.

1global FLAT_ACCOUNT_FIELDS : u32 = 2;

கீழே விளக்கப்பட்டுள்ளபடி, கணக்குகளின் Pedersen ஹாஷுக்கு இந்த மாறி பயன்படுத்தப்படுகிறது.

1global MESSAGE_LENGTH : u32 = 100;

மேலே விளக்கியபடி, செய்தியின் நீளம் நிலையானது. அது இங்கே குறிப்பிடப்பட்டுள்ளது.

1global ASCII_MESSAGE_LENGTH : [u8; 3] = [0x31, 0x30, 0x30];
2global HASH_BUFFER_SIZE : u32 = 26+3+MESSAGE_LENGTH;

EIP-191 கையொப்பங்களுக்கு (opens in a new tab) 26-பைட் முன்னொட்டுடன் கூடிய இடையகம் (buffer) தேவை, அதைத் தொடர்ந்து ASCII-இல் செய்தியின் நீளம், இறுதியாகச் செய்தி ஆகியவை தேவை.

1struct Account {
2 balance: u128,
3 address: Field,
4 nonce: u32,
5}

ஒரு கணக்கைப் பற்றி நாம் சேமிக்கும் தகவல். Field (opens in a new tab) என்பது ஒரு எண்ணாகும், பொதுவாக 253 பிட்கள் வரை இருக்கும், இது பூஜ்ஜிய-அறிவுச் சான்றைச் செயல்படுத்தும் எண்கணிதச் சுற்றில் (opens in a new tab) நேரடியாகப் பயன்படுத்தப்படலாம். இங்கே நாம் 160-பிட் Ethereum முகவரியைச் சேமிக்க Field-ஐப் பயன்படுத்துகிறோம்.

1struct TransferTxn {
2 from: Field,
3 to: Field,
4 amount: u128,
5 nonce: u32
6}

பரிமாற்றப் பரிவர்த்தனைக்காக நாம் சேமிக்கும் தகவல்.

1fn flatten_account(account: Account) -> [Field; FLAT_ACCOUNT_FIELDS] {

ஒரு செயல்பாட்டு வரையறை. அளவுரு என்பது Account தகவல். இதன் முடிவு Field மாறிகளின் வரிசையாகும், இதன் நீளம் FLAT_ACCOUNT_FIELDS ஆகும்

1 let flat = [
2 account.address,
3 ((account.balance << 32) + account.nonce.into()).into(),
4 ];

வரிசையில் உள்ள முதல் மதிப்பு கணக்கு முகவரியாகும். இரண்டாவதில் இருப்பு மற்றும் நான்ஸ் ஆகிய இரண்டும் அடங்கும். .into() அழைப்புகள் ஒரு எண்ணை அது இருக்க வேண்டிய தரவு வகைக்கு மாற்றுகின்றன. account.nonce என்பது u32 மதிப்பாகும், ஆனால் அதை account.balance << 32 என்ற u128 மதிப்புடன் சேர்க்க, அது u128-ஆக இருக்க வேண்டும். அதுதான் முதல் .into(). இரண்டாவது u128 முடிவை Field-ஆக மாற்றுகிறது, எனவே அது வரிசைக்குள் பொருந்தும்.

1 flat
2}

Noir-இல், செயல்பாடுகள் முடிவில் மட்டுமே மதிப்பை வழங்க முடியும் (முன்கூட்டியே திரும்பப் பெறுதல் இல்லை). திரும்பப் பெறும் மதிப்பைக் குறிப்பிட, செயல்பாட்டின் இறுதி அடைப்புக்குறிக்குச் சற்று முன்பு அதை மதிப்பிடுகிறீர்கள்.

1fn flatten_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] {

இந்தச் செயல்பாடு கணக்கு வரிசையை Field வரிசையாக மாற்றுகிறது, இது Petersen ஹாஷுக்கான உள்ளீடாகப் பயன்படுத்தப்படலாம்.

1 let mut flat: [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] = [0; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER];

மாற்றக்கூடிய மாறியை, அதாவது மாறிலி அல்லாததை நீங்கள் எப்படிக் குறிப்பிடுகிறீர்கள் என்பது இதுதான். Noir-இல் உள்ள மாறிகள் எப்போதும் ஒரு மதிப்பைக் கொண்டிருக்க வேண்டும், எனவே இந்த மாறியை நாம் அனைத்துப் பூஜ்ஜியங்களுக்கும் துவக்குகிறோம்.

1 for i in 0..ACCOUNT_NUMBER {

இது ஒரு for லூப் ஆகும். எல்லைகள் மாறிலிகள் என்பதை நினைவில் கொள்ளவும். Noir லூப்கள் அவற்றின் எல்லைகளைத் தொகுக்கும் நேரத்தில் (compile time) அறிந்திருக்க வேண்டும். இதற்குக் காரணம், எண்கணிதச் சுற்றுகள் ஓட்டக் கட்டுப்பாட்டை (flow control) ஆதரிக்காது. for லூப்பைச் செயலாக்கும்போது, கம்பைலர் அதனுள் உள்ள குறியீட்டைப் பல முறை வைக்கிறது, ஒவ்வொரு மறு செய்கைக்கும் (iteration) ஒன்று.

1 let fields = flatten_account(accounts[i]);
2 for j in 0..FLAT_ACCOUNT_FIELDS {
3 flat[i*FLAT_ACCOUNT_FIELDS + j] = fields[j];
4 }
5 }
6
7 flat
8}
9
10fn hash_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> Field {
11 pedersen_hash(flatten_accounts(accounts))
12}
அனைத்தையும் காட்டு

இறுதியாக, கணக்கு வரிசையை ஹாஷ் செய்யும் செயல்பாட்டிற்கு வந்தோம்.

1fn find_account(accounts: [Account; ACCOUNT_NUMBER], address: Field) -> u32 {
2 let mut account : u32 = ACCOUNT_NUMBER;
3
4 for i in 0..ACCOUNT_NUMBER {
5 if accounts[i].address == address {
6 account = i;
7 }
8 }

இந்தச் செயல்பாடு ஒரு குறிப்பிட்ட முகவரியுடன் கணக்கைக் கண்டறிகிறது. இந்தச் செயல்பாடு நிலையான குறியீட்டில் மிகவும் திறனற்றதாக இருக்கும், ஏனெனில் இது முகவரியைக் கண்டறிந்த பிறகும் அனைத்துக் கணக்குகளையும் மீண்டும் மீண்டும் செய்கிறது.

இருப்பினும், பூஜ்ஜிய-அறிவுச் சான்றுகளில், ஓட்டக் கட்டுப்பாடு இல்லை. நாம் எப்போதாவது ஒரு நிபந்தனையைச் சரிபார்க்க வேண்டும் என்றால், ஒவ்வொரு முறையும் அதைச் சரிபார்க்க வேண்டும்.

if அறிக்கைகளிலும் இதே போன்ற ஒன்று நடக்கிறது. மேலே உள்ள லூப்பில் உள்ள if அறிக்கை இந்தக் கணித அறிக்கைகளாக மொழிபெயர்க்கப்பட்டுள்ளது.

conditionresult = accounts[i].address == address // அவை சமமாக இருந்தால் ஒன்று, இல்லையெனில் பூஜ்ஜியம்

accountnew = conditionresult*i + (1-conditionresult)*accountold

1 assert (account < ACCOUNT_NUMBER, f"{address} does not have an account");
2
3 account
4}

வலியுறுத்தல் தவறாக இருந்தால், assert (opens in a new tab) செயல்பாடு பூஜ்ஜிய-அறிவுச் சான்றைச் செயலிழக்கச் செய்கிறது. இந்த நிலையில், தொடர்புடைய முகவரியுடன் ஒரு கணக்கை நம்மால் கண்டுபிடிக்க முடியவில்லை என்றால். முகவரியைப் புகாரளிக்க, நாம் ஒரு வடிவமைக்கப்பட்ட சரத்தைப் (format string) (opens in a new tab) பயன்படுத்துகிறோம்.

1fn apply_transfer_txn(accounts: [Account; ACCOUNT_NUMBER], txn: TransferTxn) -> [Account; ACCOUNT_NUMBER] {

இந்தச் செயல்பாடு ஒரு பரிமாற்றப் பரிவர்த்தனையைப் பயன்படுத்துகிறது மற்றும் புதிய கணக்கு வரிசையை வழங்குகிறது.

1 let from = find_account(accounts, txn.from);
2 let to = find_account(accounts, txn.to);
3
4 let (txnFrom, txnAmount, txnNonce, accountNonce) =
5 (txn.from, txn.amount, txn.nonce, accounts[from].nonce);

Noir-இல் உள்ள வடிவமைப்புச் சரத்திற்குள் கட்டமைப்பு உறுப்புகளை நம்மால் அணுக முடியாது, எனவே நாம் பயன்படுத்தக்கூடிய நகலை உருவாக்குகிறோம்.

1 assert (accounts[from].balance >= txn.amount,
2 f"{txnFrom} does not have {txnAmount} finney");
3
4 assert (accounts[from].nonce == txn.nonce,
5 f"Transaction has nonce {txnNonce}, but the account is expected to use {accountNonce}");

இவை ஒரு பரிவர்த்தனையைச் செல்லாததாக்கக்கூடிய இரண்டு நிபந்தனைகளாகும்.

1 let mut newAccounts = accounts;
2
3 newAccounts[from].balance -= txn.amount;
4 newAccounts[from].nonce += 1;
5 newAccounts[to].balance += txn.amount;
6
7 newAccounts
8}

புதிய கணக்கு வரிசையை உருவாக்கி, பின்னர் அதை வழங்கவும்.

1fn readAddress(messageBytes: [u8; MESSAGE_LENGTH]) -> Field

இந்தச் செயல்பாடு செய்தியிலிருந்து முகவரியைப் படிக்கிறது.

1{
2 let mut result : Field = 0;
3
4 for i in 7..47 {

முகவரி எப்போதும் 20 பைட்டுகள் (அதாவது 40 ஹெக்ஸாடெசிமல் இலக்கங்கள்) நீளமானது, மேலும் எழுத்து #7-இல் தொடங்குகிறது.

1 result *= 0x10;
2 if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-9
3 result += (messageBytes[i]-48).into();
4 }
5 if messageBytes[i] >= 65 & messageBytes[i] <= 70 { // A-F
6 result += (messageBytes[i]-65+10).into()
7 }
8 if messageBytes[i] >= 97 & messageBytes[i] <= 102 { // a-f
9 result += (messageBytes[i]-97+10).into()
10 }
11 }
12
13 result
14}
15
16fn readAmountAndNonce(messageBytes: [u8; MESSAGE_LENGTH]) -> (u128, u32)
அனைத்தையும் காட்டு

செய்தியிலிருந்து தொகை மற்றும் நான்ஸைப் படிக்கவும்.

1{
2 let mut amount : u128 = 0;
3 let mut nonce: u32 = 0;
4 let mut stillReadingAmount: bool = true;
5 let mut lookingForNonce: bool = false;
6 let mut stillReadingNonce: bool = false;

செய்தியில், முகவரிக்குப் பிறகு வரும் முதல் எண் மாற்ற வேண்டிய ஃபின்னியின் (அதாவது ETH-இன் ஆயிரத்தில் ஒரு பங்கு) தொகையாகும். இரண்டாவது எண் நான்ஸ் ஆகும். அவற்றுக்கிடையேயான எந்த உரையும் புறக்கணிக்கப்படும்.

1 for i in 48..MESSAGE_LENGTH {
2 if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-9
3 let digit = (messageBytes[i]-48);
4
5 if stillReadingAmount {
6 amount = amount*10 + digit.into();
7 }
8
9 if lookingForNonce { // நாங்கள் அதை இப்போதுதான் கண்டுபிடித்தோம்
10 stillReadingNonce = true;
11 lookingForNonce = false;
12 }
13
14 if stillReadingNonce {
15 nonce = nonce*10 + digit.into();
16 }
17 } else {
18 if stillReadingAmount {
19 stillReadingAmount = false;
20 lookingForNonce = true;
21 }
22 if stillReadingNonce {
23 stillReadingNonce = false;
24 }
25 }
26 }
27
28 (amount, nonce)
29}
அனைத்தையும் காட்டு

ஒரு டூப்பிளை (tuple) (opens in a new tab) வழங்குவது என்பது ஒரு செயல்பாட்டிலிருந்து பல மதிப்புகளை வழங்குவதற்கான Noir வழியாகும்.

1fn readTransferTxn(message: str<MESSAGE_LENGTH>) -> TransferTxn
2{
3 let mut txn: TransferTxn = TransferTxn { from: 0, to: 0, amount:0, nonce:0 };
4 let messageBytes = message.as_bytes();
5
6 txn.to = readAddress(messageBytes);
7 let (amount, nonce) = readAmountAndNonce(messageBytes);
8 txn.amount = amount;
9 txn.nonce = nonce;
10
11 txn
12}
அனைத்தையும் காட்டு

இந்தச் செயல்பாடு செய்தியை பைட்டுகளாக மாற்றுகிறது, பின்னர் தொகைகளை TransferTxn-ஆக மாற்றுகிறது.

1// Viem இன் hashMessage-க்கு சமமானது
2// https://viem.sh/docs/utilities/hashMessage#hashmessage
3fn hashMessage(message: str<MESSAGE_LENGTH>) -> [u8;32] {

கணக்குகள் பூஜ்ஜிய-அறிவுச் சான்றுக்குள் மட்டுமே ஹாஷ் செய்யப்படுவதால், கணக்குகளுக்குப் Pedersen ஹாஷைப் பயன்படுத்த முடிந்தது. இருப்பினும், இந்தக் குறியீட்டில் உலாவியால் உருவாக்கப்பட்ட செய்தியின் கையொப்பத்தை நாம் சரிபார்க்க வேண்டும். அதற்கு, EIP 191 (opens in a new tab)-இல் உள்ள Ethereum கையொப்பமிடும் வடிவமைப்பைப் பின்பற்ற வேண்டும். அதாவது, நிலையான முன்னொட்டு, ASCII-இல் செய்தியின் நீளம் மற்றும் செய்தி ஆகியவற்றுடன் ஒருங்கிணைந்த இடையகத்தை உருவாக்கி, அதை ஹாஷ் செய்ய Ethereum நிலையான keccak256-ஐப் பயன்படுத்த வேண்டும்.

1 // ASCII முன்னொட்டு
2 let prefix_bytes = [
3 0x19, // \x19
4 0x45, // 'E'
5 0x74, // 't'
6 0x68, // 'h'
7 0x65, // 'e'
8 0x72, // 'r'
9 0x65, // 'e'
10 0x75, // 'u'
11 0x6D, // 'm'
12 0x20, // ' '
13 0x53, // 'S'
14 0x69, // 'i'
15 0x67, // 'g'
16 0x6E, // 'n'
17 0x65, // 'e'
18 0x64, // 'd'
19 0x20, // ' '
20 0x4D, // 'M'
21 0x65, // 'e'
22 0x73, // 's'
23 0x73, // 's'
24 0x61, // 'a'
25 0x67, // 'g'
26 0x65, // 'e'
27 0x3A, // ':'
28 0x0A // '\n'
29 ];
அனைத்தையும் காட்டு

ஒரு பரிவர்த்தனையாக அல்லது வேறு சில நோக்கங்களுக்காகப் பயன்படுத்தக்கூடிய ஒரு செய்தியில் கையொப்பமிடுமாறு ஒரு பயன்பாடு பயனரைக் கேட்கும் நிகழ்வுகளைத் தவிர்க்க, அனைத்துக் கையொப்பமிடப்பட்ட செய்திகளும் 0x19 (செல்லுபடியாகும் ASCII எழுத்து அல்ல) என்ற எழுத்துடன் தொடங்க வேண்டும், அதைத் தொடர்ந்து Ethereum Signed Message: மற்றும் ஒரு புதிய வரி இருக்க வேண்டும் என்று EIP 191 குறிப்பிடுகிறது.

1 let mut buffer: [u8; HASH_BUFFER_SIZE] = [0u8; HASH_BUFFER_SIZE];
2 for i in 0..26 {
3 buffer[i] = prefix_bytes[i];
4 }
5
6 let messageBytes : [u8; MESSAGE_LENGTH] = message.as_bytes();
7
8 if MESSAGE_LENGTH <= 9 {
9 for i in 0..1 {
10 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
11 }
12
13 for i in 0..MESSAGE_LENGTH {
14 buffer[i+26+1] = messageBytes[i];
15 }
16 }
17
18 if MESSAGE_LENGTH >= 10 & MESSAGE_LENGTH <= 99 {
19 for i in 0..2 {
20 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
21 }
22
23 for i in 0..MESSAGE_LENGTH {
24 buffer[i+26+2] = messageBytes[i];
25 }
26 }
27
28 if MESSAGE_LENGTH >= 100 {
29 for i in 0..3 {
30 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
31 }
32
33 for i in 0..MESSAGE_LENGTH {
34 buffer[i+26+3] = messageBytes[i];
35 }
36 }
37
38 assert(MESSAGE_LENGTH < 1000, "Messages whose length is over three digits are not supported");
அனைத்தையும் காட்டு

999 வரையிலான செய்தி நீளங்களைக் கையாளவும், அது அதிகமாக இருந்தால் தோல்வியடையும். செய்தியின் நீளம் ஒரு மாறிலியாக இருந்தாலும், அதை மாற்றுவதை எளிதாக்குவதால் இந்தக் குறியீட்டைச் சேர்த்துள்ளேன். ஒரு உற்பத்தி அமைப்பில், சிறந்த செயல்திறனுக்காக MESSAGE_LENGTH மாறாது என்று நீங்கள் கருதுவீர்கள்.

1 keccak256::keccak256(buffer, HASH_BUFFER_SIZE)
2}

Ethereum நிலையான keccak256 செயல்பாட்டைப் பயன்படுத்தவும்.

1fn signatureToAddressAndHash(
2 message: str<MESSAGE_LENGTH>,
3 pubKeyX: [u8; 32],
4 pubKeyY: [u8; 32],
5 signature: [u8; 64]
6 ) -> (Field, Field, Field) // முகவரி, ஹாஷின் முதல் 16 பைட்டுகள், ஹாஷின் கடைசி 16 பைட்டுகள்
7{

இந்தச் செயல்பாடு கையொப்பத்தைச் சரிபார்க்கிறது, இதற்குச் செய்தி ஹாஷ் தேவை. பின்னர் அது கையொப்பமிட்ட முகவரி மற்றும் செய்தி ஹாஷை நமக்கு வழங்குகிறது. செய்தி ஹாஷ் இரண்டு Field மதிப்புகளில் வழங்கப்படுகிறது, ஏனெனில் அவை பைட் வரிசையை விட நிரலின் மற்ற பகுதிகளில் பயன்படுத்த எளிதானவை.

நாம் இரண்டு Field மதிப்புகளைப் பயன்படுத்த வேண்டும், ஏனெனில் புலக் கணக்கீடுகள் ஒரு பெரிய எண்ணின் மாடுலோ (modulo) (opens in a new tab) செய்யப்படுகின்றன, ஆனால் அந்த எண் பொதுவாக 256 பிட்களுக்கும் குறைவாக இருக்கும் (இல்லையெனில் EVM-இல் இந்தக் கணக்கீடுகளைச் செய்வது கடினமாக இருக்கும்).

1 let hash = hashMessage(message);
2
3 let mut (hash1, hash2) = (0,0);
4
5 for i in 0..16 {
6 hash1 = hash1*256 + hash[31-i].into();
7 hash2 = hash2*256 + hash[15-i].into();
8 }

hash1 மற்றும் hash2-ஐ மாற்றக்கூடிய மாறிகளாகக் குறிப்பிட்டு, ஹாஷை பைட்டாக பைட்டாக அவற்றில் எழுதவும்.

1 (
2 ecrecover::ecrecover(pubKeyX, pubKeyY, signature, hash),

இது Solidity-இன் ecrecover (opens in a new tab)-ஐப் போன்றது, இரண்டு முக்கியமான வேறுபாடுகளுடன்:

  • கையொப்பம் செல்லுபடியாகவில்லை என்றால், அழைப்பு assert-இல் தோல்வியடையும் மற்றும் நிரல் நிறுத்தப்படும்.
  • கையொப்பம் மற்றும் ஹாஷிலிருந்து பொதுத் திறவுகோலை மீட்டெடுக்க முடியும் என்றாலும், இது வெளிப்புறமாகச் செய்யக்கூடிய செயலாக்கமாகும், எனவே, பூஜ்ஜிய-அறிவுச் சான்றுக்குள் செய்வது மதிப்புக்குரியதல்ல. இங்கே யாராவது நம்மை ஏமாற்ற முயன்றால், கையொப்பச் சரிபார்ப்பு தோல்வியடையும்.
1 hash1,
2 hash2
3 )
4}
5
6fn main(
7 accounts: [Account; ACCOUNT_NUMBER],
8 message: str<MESSAGE_LENGTH>,
9 pubKeyX: [u8; 32],
10 pubKeyY: [u8; 32],
11 signature: [u8; 64],
12 ) -> pub (
13 Field, // பழைய கணக்குகள் வரிசையின் ஹாஷ்
14 Field, // புதிய கணக்குகள் வரிசையின் ஹாஷ்
15 Field, // செய்தி ஹாஷின் முதல் 16 பைட்டுகள்
16 Field, // செய்தி ஹாஷின் கடைசி 16 பைட்டுகள்
17 )
அனைத்தையும் காட்டு

இறுதியாக, நாம் main செயல்பாட்டை அடைகிறோம். கணக்குகளின் ஹாஷைப் பழைய மதிப்பிலிருந்து புதிய மதிப்பிற்குச் செல்லுபடியாகும் வகையில் மாற்றும் ஒரு பரிவர்த்தனை நம்மிடம் உள்ளது என்பதை நாம் நிரூபிக்க வேண்டும். இந்த குறிப்பிட்ட பரிவர்த்தனை ஹாஷ் இதில் உள்ளது என்பதையும் நாம் நிரூபிக்க வேண்டும், இதனால் அதை அனுப்பிய நபர் தங்கள் பரிவர்த்தனை செயல்படுத்தப்பட்டதை அறிந்துகொள்வார்.

1{
2 let mut txn = readTransferTxn(message);

txn மாற்றக்கூடியதாக இருக்க வேண்டும், ஏனெனில் நாம் செய்தியிலிருந்து முகவரியைப் படிக்கவில்லை, கையொப்பத்திலிருந்து அதைப் படிக்கிறோம்.

1 let (fromAddress, txnHash1, txnHash2) = signatureToAddressAndHash(
2 message,
3 pubKeyX,
4 pubKeyY,
5 signature);
6
7 txn.from = fromAddress;
8
9 let newAccounts = apply_transfer_txn(accounts, txn);
10
11 (
12 hash_accounts(accounts),
13 hash_accounts(newAccounts),
14 txnHash1,
15 txnHash2
16 )
17}
அனைத்தையும் காட்டு

கட்டம் 2 - சேவையகத்தைச் சேர்த்தல்

இரண்டாவது கட்டத்தில், உலாவியிலிருந்து பரிமாற்றப் பரிவர்த்தனைகளைப் பெற்றுச் செயல்படுத்தும் சேவையகத்தைச் சேர்க்கிறோம்.

இது செயல்படுவதைப் பார்க்க:

  1. Vite இயங்கினால் அதை நிறுத்தவும்.

  2. சேவையகத்தை உள்ளடக்கிய கிளையைப் பதிவிறக்கி, தேவையான அனைத்துத் தொகுதிகளும் உங்களிடம் இருப்பதை உறுதிசெய்யவும்.

    1git checkout 02-add-server
    2cd client
    3npm install
    4cd ../server
    5npm install

    Noir குறியீட்டைத் தொகுக்க வேண்டிய அவசியமில்லை, இது கட்டம் 1-க்குப் பயன்படுத்திய அதே குறியீடாகும்.

  3. சேவையகத்தைத் தொடங்கவும்.

    1npm run start
  4. ஒரு தனி கட்டளை வரிச் சாளரத்தில், உலாவிக் குறியீட்டை வழங்க Vite-ஐ இயக்கவும்.

    1cd client
    2npm run dev
  5. http://localhost:5173 (opens in a new tab)-இல் உள்ள கிளையன்ட் குறியீட்டிற்கு உலாவவும்

  6. நீங்கள் ஒரு பரிவர்த்தனையை வழங்குவதற்கு முன், நான்ஸ் மற்றும் நீங்கள் அனுப்பக்கூடிய தொகையை நீங்கள் தெரிந்து கொள்ள வேண்டும். இந்தத் தகவலைப் பெற, Update account data என்பதைக் கிளிக் செய்து செய்தியில் கையொப்பமிடவும்.

    இங்கே நமக்கு ஒரு குழப்பம் உள்ளது. ஒருபுறம், மீண்டும் பயன்படுத்தக்கூடிய ஒரு செய்தியில் கையொப்பமிட நாங்கள் விரும்பவில்லை (ஒரு மறுஇயக்கத் தாக்குதல் (opens in a new tab)), அதனால்தான் முதலில் எங்களுக்கு ஒரு நான்ஸ் தேவை. இருப்பினும், எங்களிடம் இன்னும் நான்ஸ் இல்லை. ஒரு முறை மட்டுமே பயன்படுத்தக்கூடிய மற்றும் தற்போதைய நேரம் போன்ற இருபுறமும் ஏற்கனவே உள்ள ஒரு நான்ஸைத் தேர்ந்தெடுப்பதே இதற்கான தீர்வாகும்.

    இந்தத் தீர்வில் உள்ள சிக்கல் என்னவென்றால், நேரம் சரியாக ஒத்திசைக்கப்படாமல் இருக்கலாம். எனவே அதற்குப் பதிலாக, ஒவ்வொரு நிமிடமும் மாறும் ஒரு மதிப்பில் கையொப்பமிடுகிறோம். இதன் பொருள் மறுஇயக்கத் தாக்குதல்களுக்கான நமது பாதிப்புச் சாளரம் அதிகபட்சம் ஒரு நிமிடம் ஆகும். உற்பத்தியில் கையொப்பமிடப்பட்ட கோரிக்கை TLS-ஆல் பாதுகாக்கப்படும் என்பதையும், சுரங்கப்பாதையின் மறுபக்கம்---சேவையகம்---ஏற்கனவே இருப்பு மற்றும் நான்ஸை வெளிப்படுத்த முடியும் (அது வேலை செய்ய அவற்றை அறிந்திருக்க வேண்டும்) என்பதையும் கருத்தில் கொண்டு, இது ஏற்றுக்கொள்ளக்கூடிய ஆபத்து.

  7. உலாவி இருப்பு மற்றும் நான்ஸைத் திரும்பப் பெற்றவுடன், அது பரிமாற்றப் படிவத்தைக் காட்டுகிறது. இலக்கு முகவரி மற்றும் தொகையைத் தேர்ந்தெடுத்து Transfer என்பதைக் கிளிக் செய்யவும். இந்தக் கோரிக்கையில் கையொப்பமிடவும்.

  8. பரிமாற்றத்தைப் பார்க்க, Update account data என்பதைக் கிளிக் செய்யவும் அல்லது நீங்கள் சேவையகத்தை இயக்கும் சாளரத்தைப் பார்க்கவும். ஒவ்வொரு முறை மாறும்போதும் சேவையகம் நிலையைப் பதிவு செய்கிறது.

    1ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start
    2
    3> server@1.0.0 start
    4> node --experimental-json-modules index.mjs
    5
    6Listening on port 3000
    7Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 36000 finney (milliEth) 0 processed
    8New state:
    90xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 64000 (1)
    100x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 100000 (0)
    110x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
    120x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)
    130x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
    14Txn send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 7200 finney (milliEth) 1 processed
    15New state:
    160xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 56800 (2)
    170x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)
    180x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
    190x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)
    200x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
    21Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 3000 finney (milliEth) 2 processed
    22New state:
    230xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 53800 (3)
    240x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)
    250x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
    260x90F79bf6EB2c4f870365E785982E1f101E93b906 has 139000 (0)
    270x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
    அனைத்தையும் காட்டு

server/index.mjs

இந்தக் கோப்பு (opens in a new tab) சேவையகச் செயல்முறையைக் கொண்டுள்ளது, மேலும் main.nr (opens in a new tab)-இல் உள்ள Noir குறியீட்டுடன் தொடர்பு கொள்கிறது. சுவாரஸ்யமான பகுதிகளின் விளக்கம் இங்கே.

1import { Noir } from '@noir-lang/noir_js'

noir.js (opens in a new tab) நூலகம் JavaScript குறியீடு மற்றும் Noir குறியீட்டிற்கு இடையே இடைமுகமாகச் செயல்படுகிறது.

1const circuit = JSON.parse(await fs.readFile("./noir/target/zkBank.json"))
2const noir = new Noir(circuit)

முந்தைய கட்டத்தில் நாம் உருவாக்கிய தொகுக்கப்பட்ட Noir நிரலான எண்கணிதச் சுற்றை ஏற்றி, அதைச் செயல்படுத்தத் தயாராகுங்கள்.

1// கையொப்பமிடப்பட்ட கோரிக்கைக்கு பதிலளிக்கும் விதமாக மட்டுமே நாங்கள் கணக்கு தகவலை வழங்குகிறோம்
2const accountInformation = async signature => {
3 const fromAddress = await recoverAddress({
4 hash: hashMessage("Get account data " + Math.floor((new Date().getTime())/60000)),
5 signature
6 })

கணக்குத் தகவலை வழங்க, நமக்குக் கையொப்பம் மட்டுமே தேவை. காரணம், செய்தி என்னவாக இருக்கும் என்பது நமக்கு ஏற்கனவே தெரியும், எனவே செய்தி ஹாஷும் தெரியும்.

1const processMessage = async (message, signature) => {

ஒரு செய்தியைச் செயலாக்கி, அது குறியாக்கம் செய்யும் பரிவர்த்தனையைச் செயல்படுத்தவும்.

1 // பொது திறவுகோலைப் பெறுக
2 const pubKey = await recoverPublicKey({
3 hash,
4 signature
5 })

இப்போது நாம் சேவையகத்தில் JavaScript-ஐ இயக்குவதால், கிளையன்டிற்குப் பதிலாகப் பொதுத் திறவுகோலை அங்கேயே மீட்டெடுக்கலாம்.

1 let noirResult
2 try {
3 noirResult = await noir.execute({
4 message,
5 signature: signature.slice(2,-2).match(/.{2}/g).map(x => `0x${x}`),
6 pubKeyX,
7 pubKeyY,
8 accounts: Accounts
9 })
அனைத்தையும் காட்டு

noir.execute Noir நிரலை இயக்குகிறது. அளவுருக்கள் Prover.toml (opens in a new tab)-இல் வழங்கப்பட்டவற்றுக்குச் சமமானவை. Viem செய்வதைப் போல, நீண்ட மதிப்புகள் ஒற்றை ஹெக்ஸாடெசிமல் மதிப்பாக (0x60A7) அல்லாமல், ஹெக்ஸாடெசிமல் சரங்களின் வரிசையாக (["0x60", "0xA7"]) வழங்கப்படுகின்றன என்பதை நினைவில் கொள்ளவும்.

1 } catch (err) {
2 console.log(`Noir error: ${err}`)
3 throw Error("Invalid transaction, not processed")
4 }

பிழை இருந்தால், அதைப் பிடித்து, பின்னர் எளிமைப்படுத்தப்பட்ட பதிப்பைக் கிளையன்டிற்கு அனுப்பவும்.

1 Accounts[fromAccountNumber].nonce++
2 Accounts[fromAccountNumber].balance -= amount
3 Accounts[toAccountNumber].balance += amount

பரிவர்த்தனையைப் பயன்படுத்தவும். நாம் ஏற்கனவே Noir குறியீட்டில் இதைச் செய்துள்ளோம், ஆனால் அங்கிருந்து முடிவைப் பிரித்தெடுப்பதை விட இங்கே மீண்டும் செய்வது எளிது.

1let Accounts = [
2 {
3 address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
4 balance: 5000,
5 nonce: 0,
6 },

ஆரம்ப Accounts கட்டமைப்பு.

கட்டம் 3 - Ethereum ஸ்மார்ட் ஒப்பந்தங்கள்

  1. சேவையகம் மற்றும் கிளையன்ட் செயல்முறைகளை நிறுத்தவும்.

  2. ஸ்மார்ட் ஒப்பந்தங்களுடன் கிளையைப் பதிவிறக்கி, தேவையான அனைத்துத் தொகுதிகளும் உங்களிடம் இருப்பதை உறுதிசெய்யவும்.

    1git checkout 03-smart-contracts
    2cd client
    3npm install
    4cd ../server
    5npm install
  3. ஒரு தனி கட்டளை வரிச் சாளரத்தில் anvil-ஐ இயக்கவும்.

  4. சரிபார்ப்புத் திறவுகோல் மற்றும் solidity சரிபார்ப்பானை (verifier) உருவாக்கவும், பின்னர் சரிபார்ப்பான் குறியீட்டை Solidity திட்டத்திற்கு நகலெடுக்கவும்.

    1cd noir
    2bb write_vk -b ./target/zkBank.json -o ./target --oracle_hash keccak
    3bb write_solidity_verifier -k ./target/vk -o ./target/Verifier.sol
    4cp target/Verifier.sol ../../smart-contracts/src
  5. ஸ்மார்ட் ஒப்பந்தங்களுக்குச் சென்று anvil பிளாக்செயினைப் பயன்படுத்தச் சூழல் மாறிகளை (environment variables) அமைக்கவும்.

    1cd ../../smart-contracts
    2export ETH_RPC_URL=http://localhost:8545
    3ETH_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
  6. Verifier.sol-ஐப் பயன்படுத்தவும் மற்றும் முகவரியை ஒரு சூழல் மாறியில் சேமிக்கவும்.

    1VERIFIER_ADDRESS=`forge create src/Verifier.sol:HonkVerifier --private-key $ETH_PRIVATE_KEY --optimize --broadcast | awk '/Deployed to:/ {print $3}'`
    2echo $VERIFIER_ADDRESS
  7. ZkBank ஒப்பந்தத்தைப் பயன்படுத்தவும்.

    1ZKBANK_ADDRESS=`forge create ZkBank --private-key $ETH_PRIVATE_KEY --broadcast --constructor-args $VERIFIER_ADDRESS 0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b | awk '/Deployed to:/ {print $3}'`
    2echo $ZKBANK_ADDRESS

    0x199..67b மதிப்பு என்பது Accounts-இன் ஆரம்ப நிலையின் Pederson ஹாஷ் ஆகும். server/index.mjs-இல் இந்த ஆரம்ப நிலையை நீங்கள் மாற்றியமைத்தால், பூஜ்ஜிய-அறிவுச் சான்றால் புகாரளிக்கப்பட்ட ஆரம்ப ஹாஷைப் பார்க்க நீங்கள் ஒரு பரிவர்த்தனையை இயக்கலாம்.

  8. சேவையகத்தை இயக்கவும்.

    1cd ../server
    2npm run start
  9. வேறு கட்டளை வரிச் சாளரத்தில் கிளையன்டை இயக்கவும்.

    1cd client
    2npm run dev
  10. சில பரிவர்த்தனைகளை இயக்கவும்.

  11. ஆன்செயினில் நிலை மாறியுள்ளதா என்பதைச் சரிபார்க்க, சேவையகச் செயல்முறையை மறுதொடக்கம் செய்யவும். பரிவர்த்தனைகளில் உள்ள அசல் ஹாஷ் மதிப்பு ஆன்செயினில் சேமிக்கப்பட்ட ஹாஷ் மதிப்பிலிருந்து வேறுபடுவதால், ZkBank இனி பரிவர்த்தனைகளை ஏற்காது என்பதைப் பார்க்கவும்.

    இது எதிர்பார்க்கப்படும் பிழையின் வகையாகும்.

    1ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start
    2
    3> server@1.0.0 start
    4> node --experimental-json-modules index.mjs
    5
    6Listening on port 3000
    7Verification error: ContractFunctionExecutionError: The contract function "processTransaction" reverted with the following reason:
    8Wrong old state hash
    9
    10Contract Call:
    11 address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
    12 function: processTransaction(bytes _proof, bytes32[] _publicInputs)
    13 args: (0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf00000000000000000000000000000000000000000000000b75c020998797da7800000000000000000000000000000000000000000000000
    அனைத்தையும் காட்டு

server/index.mjs

இந்தக் கோப்பில் உள்ள மாற்றங்கள் பெரும்பாலும் உண்மையான சான்றை உருவாக்குவது மற்றும் அதை ஆன்செயினில் சமர்ப்பிப்பது தொடர்பானது.

1import { exec } from 'child_process'
2import util from 'util'
3
4const execPromise = util.promisify(exec)

ஆன்செயினில் அனுப்ப உண்மையான சான்றை உருவாக்க நாம் Barretenberg தொகுப்பைப் (opens in a new tab) பயன்படுத்த வேண்டும். கட்டளை வரி இடைமுகத்தை (bb) இயக்குவதன் மூலமாகவோ அல்லது JavaScript நூலகமான bb.js (opens in a new tab)-ஐப் பயன்படுத்துவதன் மூலமாகவோ இந்தத் தொகுப்பைப் பயன்படுத்தலாம். JavaScript நூலகம் குறியீட்டை இயல்பாக இயக்குவதை விட மிகவும் மெதுவாக உள்ளது, எனவே கட்டளை வரியைப் பயன்படுத்த நாம் இங்கே exec (opens in a new tab)-ஐப் பயன்படுத்துகிறோம்.

நீங்கள் bb.js-ஐப் பயன்படுத்த முடிவு செய்தால், நீங்கள் பயன்படுத்தும் Noir பதிப்போடு இணக்கமான பதிப்பைப் பயன்படுத்த வேண்டும் என்பதை நினைவில் கொள்ளவும். எழுதும் நேரத்தில், தற்போதைய Noir பதிப்பு (1.0.0-beta.11) bb.js பதிப்பு 0.87-ஐப் பயன்படுத்துகிறது.

1const zkBankAddress = process.env.ZKBANK_ADDRESS || "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"

சுத்தமான anvil-உடன் தொடங்கி மேலே உள்ள வழிமுறைகளைப் பின்பற்றும்போது நீங்கள் பெறும் முகவரி இதுவாகும்.

1const walletClient = createWalletClient({
2 chain: anvil,
3 transport: http(),
4 account: privateKeyToAccount("0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6")
5})

இந்தத் தனிப்பட்ட திறவுகோல் anvil-இல் உள்ள இயல்புநிலை முன்-நிதியளிக்கப்பட்ட கணக்குகளில் ஒன்றாகும்.

1const generateProof = async (witness, fileID) => {

bb இயங்கக்கூடியதைப் பயன்படுத்தி ஒரு சான்றை உருவாக்கவும்.

1 const fname = `witness-${fileID}.gz`
2 await fs.writeFile(fname, witness)

சாட்சியை (witness) ஒரு கோப்பில் எழுதவும்.

1 await execPromise(`bb prove -b ./noir/target/zkBank.json -w ${fname} -o ${fileID} --oracle_hash keccak --output_format fields`)

உண்மையில் சான்றை உருவாக்கவும். இந்தப் படி பொது மாறிகளுடன் ஒரு கோப்பையும் உருவாக்குகிறது, ஆனால் அது நமக்குத் தேவையில்லை. noir.execute-இலிருந்து அந்த மாறிகளை நாம் ஏற்கனவே பெற்றுள்ளோம்.

1 const proof = "0x" + JSON.parse(await fs.readFile(`./${fileID}/proof_fields.json`)).reduce((a,b) => a+b, "").replace(/0x/g, "")

சான்று என்பது Field மதிப்புகளின் JSON வரிசையாகும், ஒவ்வொன்றும் ஹெக்ஸாடெசிமல் மதிப்பாகக் குறிக்கப்படுகிறது. இருப்பினும், நாம் அதைப் பரிவர்த்தனையில் ஒற்றை bytes மதிப்பாக அனுப்ப வேண்டும், இதை Viem ஒரு பெரிய ஹெக்ஸாடெசிமல் சரத்தால் குறிக்கிறது. இங்கே நாம் அனைத்து மதிப்புகளையும் இணைத்து, அனைத்து 0x-களையும் அகற்றி, பின்னர் முடிவில் ஒன்றைச் சேர்ப்பதன் மூலம் வடிவமைப்பை மாற்றுகிறோம்.

1 await execPromise(`rm -r ${fname} ${fileID}`)
2
3 return proof
4}

சுத்தம் செய்து சான்றை வழங்கவும்.

1const processMessage = async (message, signature) => {
2 .
3 .
4 .
5
6 const publicFields = noirResult.returnValue.map(x=>'0x' + x.slice(2).padStart(64, "0"))

பொதுப் புலங்கள் 32-பைட் மதிப்புகளின் வரிசையாக இருக்க வேண்டும். இருப்பினும், பரிவர்த்தனை ஹாஷை இரண்டு Field மதிப்புகளுக்கு இடையே பிரிக்க வேண்டியிருந்ததால், அது 16-பைட் மதிப்பாகத் தோன்றுகிறது. இங்கே நாம் பூஜ்ஜியங்களைச் சேர்க்கிறோம், எனவே இது உண்மையில் 32 பைட்டுகள் என்பதை Viem புரிந்துகொள்ளும்.

1 const proof = await generateProof(noirResult.witness, `${fromAddress}-${nonce}`)

ஒவ்வொரு முகவரியும் ஒவ்வொரு நான்ஸையும் ஒரு முறை மட்டுமே பயன்படுத்துகிறது, இதனால் சாட்சிக் கோப்பு மற்றும் வெளியீட்டு அடைவுக்கான தனிப்பட்ட அடையாளங்காட்டியாக fromAddress மற்றும் nonce ஆகியவற்றின் கலவையை நாம் பயன்படுத்தலாம்.

1 try {
2 await zkBank.write.processTransaction([
3 proof, publicFields])
4 } catch (err) {
5 console.log(`Verification error: ${err}`)
6 throw Error("Can't verify the transaction onchain")
7 }
8 .
9 .
10 .
11}
அனைத்தையும் காட்டு

சங்கிலிக்குப் பரிவர்த்தனையை அனுப்பவும்.

smart-contracts/src/ZkBank.sol

இது பரிவர்த்தனையைப் பெறும் ஆன்செயின் குறியீடாகும்.

1// SPDX-License-Identifier: MIT
2
3pragma solidity >=0.8.21;
4
5import {HonkVerifier} from "./Verifier.sol";
6
7contract ZkBank {
8 HonkVerifier immutable myVerifier;
9 bytes32 currentStateHash;
10
11 constructor(address _verifierAddress, bytes32 _initialStateHash) {
12 currentStateHash = _initialStateHash;
13 myVerifier = HonkVerifier(_verifierAddress);
14 }
அனைத்தையும் காட்டு

ஆன்செயின் குறியீடு இரண்டு மாறிகளைக் கண்காணிக்க வேண்டும்: சரிபார்ப்பான் (nargo-ஆல் உருவாக்கப்பட்ட ஒரு தனி ஒப்பந்தம்) மற்றும் தற்போதைய நிலை ஹாஷ்.

1 event TransactionProcessed(
2 bytes32 indexed transactionHash,
3 bytes32 oldStateHash,
4 bytes32 newStateHash
5 );

ஒவ்வொரு முறை நிலை மாறும்போதும், நாம் ஒரு TransactionProcessed நிகழ்வை வெளியிடுகிறோம்.

1 function processTransaction(
2 bytes calldata _proof,
3 bytes32[] calldata _publicFields
4 ) public {

இந்தச் செயல்பாடு பரிவர்த்தனைகளைச் செயலாக்குகிறது. இது சரிபார்ப்பானுக்குத் தேவையான வடிவத்தில் (ஆன்செயின் செயலாக்கத்தைக் குறைக்கவும், எனவே எரிவாயுச் செலவுகளைக் குறைக்கவும்) சான்றை (bytes-ஆக) மற்றும் பொது உள்ளீடுகளை (bytes32 வரிசையாக) பெறுகிறது.

1 require(_publicInputs[0] == currentStateHash,
2 "Wrong old state hash");

பரிவர்த்தனை நமது தற்போதைய ஹாஷிலிருந்து புதியதாக மாறுகிறது என்பதே பூஜ்ஜிய-அறிவுச் சான்றாக இருக்க வேண்டும்.

1 myVerifier.verify(_proof, _publicFields);

பூஜ்ஜிய-அறிவுச் சான்றைச் சரிபார்க்கச் சரிபார்ப்பான் ஒப்பந்தத்தை அழைக்கவும். பூஜ்ஜிய-அறிவுச் சான்று தவறாக இருந்தால் இந்தப் படி பரிவர்த்தனையைத் திரும்பப் பெறுகிறது.

1 currentStateHash = _publicFields[1];
2
3 emit TransactionProcessed(
4 _publicFields[2]<<128 | _publicFields[3],
5 _publicFields[0],
6 _publicFields[1]
7 );
8 }
9}
அனைத்தையும் காட்டு

எல்லாம் சரியாக இருந்தால், நிலை ஹாஷைப் புதிய மதிப்பிற்குப் புதுப்பித்து, TransactionProcessed நிகழ்வை வெளியிடவும்.

மையப்படுத்தப்பட்ட கூறின் முறைகேடுகள்

தகவல் பாதுகாப்பு மூன்று பண்புகளைக் கொண்டுள்ளது:

  • ரகசியத்தன்மை (Confidentiality), பயனர்கள் படிக்க அங்கீகரிக்கப்படாத தகவல்களைப் படிக்க முடியாது.
  • ஒருமைப்பாடு (Integrity), அங்கீகரிக்கப்பட்ட பயனர்களால் அங்கீகரிக்கப்பட்ட முறையில் தவிரத் தகவல்களை மாற்ற முடியாது.
  • கிடைக்கும் தன்மை (Availability), அங்கீகரிக்கப்பட்ட பயனர்கள் அமைப்பைப் பயன்படுத்தலாம்.

இந்த அமைப்பில், பூஜ்ஜிய-அறிவுச் சான்றுகள் மூலம் ஒருமைப்பாடு வழங்கப்படுகிறது. கிடைக்கும் தன்மைக்கு உத்தரவாதம் அளிப்பது மிகவும் கடினம், மேலும் ரகசியத்தன்மை சாத்தியமற்றது, ஏனெனில் வங்கி ஒவ்வொரு கணக்கின் இருப்பு மற்றும் அனைத்துப் பரிவர்த்தனைகளையும் அறிந்திருக்க வேண்டும். தகவலைக் கொண்ட ஒரு நிறுவனம் அந்தத் தகவலைப் பகிர்வதைத் தடுக்க எந்த வழியும் இல்லை.

மறைநிலை முகவரிகளைப் (stealth addresses) (opens in a new tab) பயன்படுத்தி உண்மையிலேயே ரகசியமான வங்கியை உருவாக்க முடியும், ஆனால் அது இந்தக் கட்டுரையின் வரம்பிற்கு அப்பாற்பட்டது.

தவறான தகவல்

தரவு கோரப்படும்போது (opens in a new tab) தவறான தகவலை வழங்குவது சேவையகம் ஒருமைப்பாட்டை மீறும் ஒரு வழியாகும்.

இதைத் தீர்க்க, கணக்குகளைத் தனிப்பட்ட உள்ளீடாகவும், தகவல் கோரப்படும் முகவரியைப் பொது உள்ளீடாகவும் பெறும் இரண்டாவது Noir நிரலை நாம் எழுதலாம். அந்த முகவரியின் இருப்பு மற்றும் நான்ஸ் மற்றும் கணக்குகளின் ஹாஷ் ஆகியவை வெளியீடாகும்.

நிச்சயமாக, இந்தச் சான்றை ஆன்செயினில் சரிபார்க்க முடியாது, ஏனெனில் நான்ஸ்கள் மற்றும் இருப்புகளை ஆன்செயினில் வெளியிட நாங்கள் விரும்பவில்லை. இருப்பினும், உலாவியில் இயங்கும் கிளையன்ட் குறியீட்டின் மூலம் இதைச் சரிபார்க்க முடியும்.

கட்டாயப் பரிவர்த்தனைகள்

L2-களில் கிடைக்கும் தன்மையை உறுதி செய்வதற்கும் தணிக்கையைத் தடுப்பதற்குமான வழக்கமான வழிமுறை கட்டாயப் பரிவர்த்தனைகள் (opens in a new tab) ஆகும். ஆனால் கட்டாயப் பரிவர்த்தனைகள் பூஜ்ஜிய-அறிவுச் சான்றுகளுடன் இணைவதில்லை. பரிவர்த்தனைகளைச் சரிபார்க்கக்கூடிய ஒரே நிறுவனம் சேவையகம் மட்டுமே.

கட்டாயப் பரிவர்த்தனைகளை ஏற்கவும், அவை செயலாக்கப்படும் வரை சேவையகம் நிலையை மாற்றுவதைத் தடுக்கவும் smart-contracts/src/ZkBank.sol-ஐ நாம் மாற்றியமைக்கலாம். இருப்பினும், இது ஒரு எளிய சேவை மறுப்புத் தாக்குதலுக்கு (denial-of-service attack) நம்மைத் திறக்கிறது. கட்டாயப் பரிவர்த்தனை செல்லாததாக இருந்தால், அதைச் செயலாக்க முடியாவிட்டால் என்ன செய்வது?

கட்டாயப் பரிவர்த்தனை செல்லாது என்பதற்கான பூஜ்ஜிய-அறிவுச் சான்றைக் கொண்டிருப்பதே இதற்கான தீர்வாகும். இது சேவையகத்திற்கு மூன்று விருப்பங்களை வழங்குகிறது:

  • கட்டாயப் பரிவர்த்தனையைச் செயலாக்கி, அது செயலாக்கப்பட்டது என்பதற்கான பூஜ்ஜிய-அறிவுச் சான்று மற்றும் புதிய நிலை ஹாஷை வழங்குதல்.
  • கட்டாயப் பரிவர்த்தனையை நிராகரித்து, பரிவர்த்தனை செல்லாது (தெரியாத முகவரி, தவறான நான்ஸ் அல்லது போதிய இருப்பு இல்லை) என்பதற்கான பூஜ்ஜிய-அறிவுச் சான்றை ஒப்பந்தத்திற்கு வழங்குதல்.
  • கட்டாயப் பரிவர்த்தனையைப் புறக்கணித்தல். பரிவர்த்தனையை உண்மையில் செயலாக்கச் சேவையகத்தைக் கட்டாயப்படுத்த எந்த வழியும் இல்லை, ஆனால் இதன் பொருள் முழு அமைப்பும் கிடைக்கவில்லை என்பதாகும்.

கிடைக்கும் தன்மைப் பத்திரங்கள்

நிஜ வாழ்க்கைச் செயலாக்கத்தில், சேவையகத்தை இயங்க வைப்பதற்குச் சில வகையான லாப நோக்கங்கள் இருக்கலாம். ஒரு குறிப்பிட்ட காலத்திற்குள் கட்டாயப் பரிவர்த்தனை செயலாக்கப்படாவிட்டால், எவரும் எரிக்கக்கூடிய கிடைக்கும் தன்மைப் பத்திரத்தைச் (availability bond) சேவையகம் வெளியிடுவதன் மூலம் இந்த ஊக்கத்தை நாம் வலுப்படுத்தலாம்.

மோசமான Noir குறியீடு

பொதுவாக, ஒரு ஸ்மார்ட் ஒப்பந்தத்தை மக்கள் நம்புவதற்கு, மூலக் குறியீட்டை ஒரு பிளாக் எக்ஸ்ப்ளோரரில் (opens in a new tab) பதிவேற்றுகிறோம். இருப்பினும், பூஜ்ஜிய-அறிவுச் சான்றுகளின் விஷயத்தில், அது போதாது.

Verifier.sol சரிபார்ப்புத் திறவுகோலைக் கொண்டுள்ளது, இது Noir நிரலின் செயல்பாடாகும். இருப்பினும், அந்தத் திறவுகோல் Noir நிரல் என்னவாக இருந்தது என்பதை நமக்குச் சொல்லாது. உண்மையில் நம்பகமான தீர்வைக் கொண்டிருக்க, நீங்கள் Noir நிரலை (மற்றும் அதை உருவாக்கிய பதிப்பை) பதிவேற்ற வேண்டும். இல்லையெனில், பூஜ்ஜிய-அறிவுச் சான்றுகள் வேறுபட்ட நிரலை, பின்கதவு (back door) கொண்ட ஒன்றைப் பிரதிபலிக்கக்கூடும்.

பிளாக் எக்ஸ்ப்ளோரர்கள் Noir நிரல்களைப் பதிவேற்றவும் சரிபார்க்கவும் அனுமதிக்கும் வரை, நீங்கள் அதை நீங்களே செய்ய வேண்டும் (முன்னுரிமையாக IPFS-க்கு). பின்னர் அதிநவீனப் பயனர்கள் மூலக் குறியீட்டைப் பதிவிறக்கம் செய்து, அதைத் தாங்களே தொகுத்து, Verifier.sol-ஐ உருவாக்கி, அது ஆன்செயினில் உள்ளதைப் போலவே இருப்பதைச் சரிபார்க்க முடியும்.

முடிவுரை

பிளாஸ்மா வகை பயன்பாடுகளுக்குத் தகவல் சேமிப்பகமாக ஒரு மையப்படுத்தப்பட்ட கூறு தேவைப்படுகிறது. இது சாத்தியமான பாதிப்புகளைத் திறக்கிறது, ஆனால், அதற்குப் பதிலாக, பிளாக்செயினில் கிடைக்காத வழிகளில் தனியுரிமையைப் பாதுகாக்க அனுமதிக்கிறது. பூஜ்ஜிய-அறிவுச் சான்றுகள் மூலம் நாம் ஒருமைப்பாட்டை உறுதிப்படுத்த முடியும் மற்றும் மையப்படுத்தப்பட்ட கூறுகளை இயக்குபவர் கிடைக்கும் தன்மையைப் பராமரிப்பதைப் பொருளாதார ரீதியாகச் சாதகமாக்க முடியும்.

எனது மேலும் பல பணிகளுக்கு இங்கே பார்க்கவும் (opens in a new tab).

நன்றிகள்

  • ஜோஷ் க்ரைட்ஸ் (Josh Crites) இந்தக் கட்டுரையின் வரைவைப் படித்து, ஒரு சிக்கலான Noir சிக்கலில் எனக்கு உதவினார்.

மீதமுள்ள பிழைகள் ஏதேனும் இருந்தால் அது எனது பொறுப்பாகும்.

பக்கம் கடைசியாகப் புதுப்பிக்கப்பட்டது: 3 மார்ச், 2026

இந்த வழிகாட்டி பயனுள்ளதாக இருந்ததா?