ప్రధాన కంటెంట్‌కు దాటవేయి

గోప్యతను కాపాడే యాప్-నిర్దిష్ట ప్లాస్మాను వ్రాయండి

జీరో-కనౌలెడ్జి
సర్వర్
ఆఫ్‌చైన్
గోప్యత
అధునాతనం
Ori Pomerantz
15 అక్టోబర్, 2025
27 నిమిషం పఠనం

పరిచయం

రోలప్‌లుకు భిన్నంగా, ప్లాస్మాలు సమగ్రత కోసం ఇతీరియము మెయిన్‌నెట్‌ను ఉపయోగిస్తాయి, కానీ లభ్యత కోసం కాదు. ఈ వ్యాసంలో, మేము ప్లాస్మా లాగా ప్రవర్తించే ఒక అప్లికేషన్‌ను వ్రాస్తాము, ఇతీరియము సమగ్రతకు (అనధికార మార్పులు లేవు) హామీ ఇస్తుంది కానీ లభ్యతకు కాదు (ఒక కేంద్రీకృత భాగం డౌన్ అయి మొత్తం వ్యవస్థను నిలిపివేయవచ్చు).

మేము ఇక్కడ వ్రాసే అప్లికేషన్ ఒక గోప్యతను కాపాడే బ్యాంకు. వివిధ చిరునామాలకు బ్యాలెన్స్‌లతో ఖాతాలు ఉన్నాయి, మరియు వారు ఇతర ఖాతాలకు డబ్బు (ETH) పంపవచ్చు. బ్యాంకు స్థితి (ఖాతాలు మరియు వాటి బ్యాలెన్స్‌లు) మరియు లావాదేవీల హాష్‌లను పోస్ట్ చేస్తుంది, కానీ అసలు బ్యాలెన్స్‌లను ఆఫ్‌చైన్‌లో ఉంచుతుంది, అక్కడ అవి ప్రైవేట్‌గా ఉండగలవు.

డిజైన్

ఇది ఉత్పత్తికి-సిద్ధమైన వ్యవస్థ కాదు, కానీ ఒక బోధనా సాధనం. అందువల్ల, ఇది అనేక సరళీకరణ అంచనాలతో వ్రాయబడింది.

  • స్థిర ఖాతా పూల్. నిర్దిష్ట సంఖ్యలో ఖాతాలు ఉన్నాయి, మరియు ప్రతి ఖాతా ముందుగా నిర్ణయించిన చిరునామాకు చెందినది. ఇది చాలా సరళమైన వ్యవస్థను చేస్తుంది ఎందుకంటే జీరో-కనౌలెడ్జి రుజువులలో వేరియబుల్-సైజ్ డేటా నిర్మాణాలను నిర్వహించడం కష్టం. ఉత్పత్తికి-సిద్ధమైన వ్యవస్థ కోసం, మనం స్థితి హాష్‌గా మెర్కిల్ రూట్‌ను ఉపయోగించవచ్చు మరియు అవసరమైన బ్యాలెన్స్‌ల కోసం మెర్కిల్ రుజువులను అందించవచ్చు.

  • మెమరీ నిల్వ. ఒక ఉత్పత్తి వ్యవస్థలో, పునఃప్రారంభం అయినప్పుడు వాటిని భద్రపరచడానికి మనం అన్ని ఖాతా బ్యాలెన్స్‌లను డిస్క్‌కు వ్రాయాలి. ఇక్కడ, సమాచారం కేవలం పోయినా పర్వాలేదు.

  • బదిలీలు మాత్రమే. ఒక ఉత్పత్తి వ్యవస్థకు బ్యాంకులోకి ఆస్తులను డిపాజిట్ చేయడానికి మరియు వాటిని విత్‌డ్రా చేయడానికి ఒక మార్గం అవసరం. కానీ ఇక్కడ ఉద్దేశ్యం కేవలం భావనను వివరించడం, కాబట్టి ఈ బ్యాంకు బదిలీలకు పరిమితం చేయబడింది.

జీరో-కనౌలెడ్జి రుజువులు

ప్రాథమిక స్థాయిలో, ఒక జీరో-కనౌలెడ్జి రుజువు ప్రూవర్‌కు కొన్ని డేటా, Dataprivate తెలుసని చూపిస్తుంది, అలాంటి డేటాకు పబ్లిక్ డేటా, Datapublic మరియు Dataprivate మధ్య ఒక సంబంధం Relationship ఉంటుంది. వెరిఫైయర్‌కు Relationship మరియు Datapublic తెలుసు.

గోప్యతను కాపాడటానికి, మనకు స్థితులు మరియు లావాదేవీలు ప్రైవేట్‌గా ఉండాలి. కానీ సమగ్రతను నిర్ధారించడానికి, మనకు స్థితుల క్రిప్టోగ్రాఫిక్ హాష్ (opens in a new tab) పబ్లిక్‌గా ఉండాలి. లావాదేవీలను సమర్పించే వ్యక్తులకు ఆ లావాదేవీలు నిజంగా జరిగాయని నిరూపించడానికి, మనం లావాదేవీ హాష్‌లను కూడా పోస్ట్ చేయాలి.

చాలా సందర్భాలలో, Dataprivate జీరో-కనౌలెడ్జి రుజువు ప్రోగ్రామ్‌కు ఇన్‌పుట్, మరియు Datapublic అవుట్‌పుట్.

_Dataprivate_లో ఈ ఫీల్డులు:

  • Staten, పాత స్థితి
  • Staten+1, కొత్త స్థితి
  • Transaction, పాత స్థితి నుండి కొత్త స్థితికి మార్చే ఒక లావాదేవీ. ఈ లావాదేవీలో ఈ ఫీల్డులను చేర్చాలి:
    • బదిలీని స్వీకరించే గమ్యస్థాన చిరునామా
    • బదిలీ చేయబడుతున్న మొత్తం
    • ప్రతి లావాదేవీ ఒకసారి మాత్రమే ప్రాసెస్ చేయబడుతుందని నిర్ధారించడానికి నాన్స్. మూల చిరునామా లావాదేవీలో ఉండవలసిన అవసరం లేదు, ఎందుకంటే దానిని సంతకం నుండి తిరిగి పొందవచ్చు.
  • సంతకం, లావాదేవీని నిర్వహించడానికి అధికారం పొందిన ఒక సంతకం. మా విషయంలో, లావాదేవీని నిర్వహించడానికి అధికారం పొందిన ఏకైక చిరునామా మూల చిరునామా. మా జీరో-కనౌలెడ్జి వ్యవస్థ పనిచేసే విధానం కారణంగా, ఇతీరియము సంతకంతో పాటు మాకు ఖాతా యొక్క పబ్లిక్ కీ కూడా అవసరం.

_Datapublic_లో ఇవి ఫీల్డులు:

  • Hash(Staten) పాత స్థితి యొక్క హాష్
  • Hash(Staten+1) కొత్త స్థితి యొక్క హాష్
  • Hash(Transaction) Staten నుండి _Staten+1_కి స్థితిని మార్చే లావాదేవీ యొక్క హాష్.

ఈ సంబంధం అనేక పరిస్థితులను తనిఖీ చేస్తుంది:

  • పబ్లిక్ హాష్‌లు నిజంగా ప్రైవేట్ ఫీల్డులకు సరైన హాష్‌లు.
  • లావాదేవీ, పాత స్థితికి వర్తింపజేసినప్పుడు, కొత్త స్థితికి దారితీస్తుంది.
  • సంతకం లావాదేవీ యొక్క మూల చిరునామా నుండి వస్తుంది.

క్రిప్టోగ్రాఫిక్ హాష్ ఫంక్షన్ల లక్షణాల కారణంగా, ఈ పరిస్థితులను నిరూపించడం సమగ్రతను నిర్ధారించడానికి సరిపోతుంది.

డేటా స్ట్రక్చర్‌లు

ప్రాథమిక డేటా నిర్మాణం సర్వర్ ద్వారా నిర్వహించబడే స్థితి. ప్రతి ఖాతా కోసం, సర్వర్ ఖాతా బ్యాలెన్స్ మరియు నాన్స్ (opens in a new tab)ను ట్రాక్ చేస్తుంది, ఇది రీప్లే దాడులను (opens in a new tab) నివారించడానికి ఉపయోగించబడుతుంది.

భాగాలు

ఈ వ్యవస్థకు రెండు భాగాలు అవసరం:

  • లావాదేవీలను స్వీకరించి, వాటిని ప్రాసెస్ చేసి, జీరో-కనౌలెడ్జి రుజువులతో పాటు చైన్‌కు హాష్‌లను పోస్ట్ చేసే సర్వర్.
  • హాష్‌లను నిల్వ చేసే మరియు స్థితి మార్పులు చట్టబద్ధమైనవని నిర్ధారించడానికి జీరో-కనౌలెడ్జి రుజువులను ధృవీకరించే స్మార్ట్ కాంట్రాక్ట్.

డేటా మరియు నియంత్రణ ప్రవాహం

ఒక ఖాతా నుండి మరొక ఖాతాకు బదిలీ చేయడానికి వివిధ భాగాలు సంభాషించే మార్గాలు ఇవి.

  1. ఒక వెబ్ బ్రౌజర్, సంతకం చేసినవారి ఖాతా నుండి వేరొక ఖాతాకు బదిలీ కోసం అడుగుతూ సంతకం చేసిన లావాదేవీని సమర్పిస్తుంది.

  2. సర్వర్ లావాదేవీ చెల్లుబాటు అయ్యేదని ధృవీకరిస్తుంది:

    • సంతకం చేసినవారికి బ్యాంకులో తగినంత బ్యాలెన్సుతో ఒక ఖాతా ఉంది.
    • స్వీకర్తకు బ్యాంకులో ఒక ఖాతా ఉంది.
  3. సర్వర్, సంతకం చేసినవారి బ్యాలెన్స్ నుండి బదిలీ చేయబడిన మొత్తాన్ని తీసివేసి, స్వీకర్త బ్యాలెన్సుకు జోడించడం ద్వారా కొత్త స్థితిని గణిస్తుంది.

  4. సర్వర్, స్థితి మార్పు చెల్లుబాటు అయ్యేదని రుజువు చేసే జీరో-కనౌలెడ్జి రుజువును గణిస్తుంది.

  5. సర్వర్ ఇతీరియముకు ఒక లావాదేవీని సమర్పిస్తుంది, అందులో ఇవి ఉంటాయి:

    • కొత్త స్థితి హాష్
    • లావాదేవీ హాష్ (కాబట్టి లావాదేవీ పంపినవారు అది ప్రాసెస్ చేయబడిందని తెలుసుకోవచ్చు)
    • కొత్త స్థితికి మార్పు చెల్లుబాటు అయ్యేదని నిరూపించే జీరో-కనౌలెడ్జి రుజువు
  6. స్మార్ట్ కాంట్రాక్ట్ జీరో-కనౌలెడ్జి రుజువును ధృవీకరిస్తుంది.

  7. జీరో-కనౌలెడ్జి రుజువు సరిపోలితే, స్మార్ట్ కాంట్రాక్ట్ ఈ చర్యలను చేస్తుంది:

    • ప్రస్తుత స్థితి హాష్‌ను కొత్త స్థితి హాష్‌కి నవీకరించండి
    • కొత్త స్థితి హాష్ మరియు లావాదేవీ హాష్‌తో ఒక లాగ్ ఎంట్రీని జారీ చేయండి

ఉపకరణాలు

క్లయింట్-సైడ్ కోడ్ కోసం, మేము 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) ఉపయోగించి వ్రాయబడింది. జీరో-కనౌలెడ్జి భాగం Noir (opens in a new tab)లో వ్రాయబడింది. మాకు వెర్షన్ 1.0.0-beta.10 అవసరం, కాబట్టి మీరు Noirని సూచించిన విధంగా ఇన్‌స్టాల్ చేసిన (opens in a new tab) తర్వాత, రన్ చేయండి:

noirup -v 1.0.0-beta.10

మనం ఉపయోగించే బ్లాక్ చైను anvil, ఇది Foundry (opens in a new tab)లో భాగమైన ఒక స్థానిక పరీక్ష బ్లాక్ చైను.

అమలు

ఇది ఒక సంక్లిష్టమైన వ్యవస్థ కాబట్టి, మేము దానిని దశలవారీగా అమలు చేస్తాము.

దశ 1 - మాన్యువల్ జీరో కనౌలెడ్జి

మొదటి దశ కోసం, మేము బ్రౌజర్‌లో ఒక లావాదేవీకి సంతకం చేసి, ఆపై మాన్యువల్‌గా జీరో-కనౌలెడ్జి రుజువుకు సమాచారాన్ని అందిస్తాము. జీరో-కనౌలెడ్జి కోడ్ ఆ సమాచారాన్ని server/noir/Prover.tomlలో పొందాలని ఆశిస్తుంది (ఇక్కడ డాక్యుమెంట్ చేయబడింది here (opens in a new tab)).

చర్యలో చూడటానికి:

  1. మీరు నోడ్స్ (opens in a new tab) మరియు Noir (opens in a new tab) ఇన్‌స్టాల్ చేసుకున్నారని నిర్ధారించుకోండి. Node (opens in a new tab) మరియు Noir (opens in a new tab) ఇన్‌స్టాల్ చేయబడిందని నిర్ధారించుకోండి. ప్రాధాన్యంగా, వాటిని macOS, Linux, లేదా WSL (opens in a new tab) వంటి UNIX సిస్టమ్‌లో ఇన్‌స్టాల్ చేయండి.

  2. దశ 1 కోడ్‌ను డౌన్‌లోడ్ చేసి, క్లయింట్ కోడ్‌ను అందించడానికి వెబ్ సర్వర్‌ను ప్రారంభించండి.

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

    మీకు ఇక్కడ ఒక వెబ్ సర్వర్ అవసరం కావడానికి కారణం, కొన్ని రకాల మోసాలను నివారించడానికి, అనేక వాలెట్‌లు (MetaMask వంటివి) డిస్క్ నుండి నేరుగా అందించబడిన ఫైల్‌లను అంగీకరించవు

  3. వాలెట్‌తో ఒక బ్రౌజర్‌ను తెరవండి.

  4. వాలెట్‌లో, కొత్త పాస్‌ఫ్రేజ్‌ను నమోదు చేయండి. గమనించండి ఇది మీ ప్రస్తుత పాస్‌ఫ్రేజ్‌ను తొలగిస్తుంది, కాబట్టి మీకు బ్యాకప్ ఉందని నిర్ధారించుకోండి.

    పాస్‌ఫ్రేజ్ test test test test test test test test test test test junk, ఇది ఆన్విల్ కోసం డిఫాల్ట్ పరీక్ష పాస్‌ఫ్రేజ్.

  5. క్లయింట్-సైడ్ కోడ్‌కు (opens in a new tab) బ్రౌజ్ చేయండి.

  6. వాలెట్‌కు కనెక్ట్ అవ్వండి మరియు మీ గమ్యస్థాన ఖాతా మరియు మొత్తాన్ని ఎంచుకోండి.

  7. Sign క్లిక్ చేసి, లావాదేవీపై సంతకం చేయండి.

  8. Prover.toml శీర్షిక క్రింద, మీరు టెక్స్ట్ కనుగొంటారు. server/noir/Prover.tomlను ఆ టెక్స్ట్‌తో భర్తీ చేయండి.

  9. జీరో-కనౌలెడ్జి రుజువును అమలు చేయండి.

    cd ../server/noir
    nargo execute
    

    అవుట్‌పుట్ ఇలా ఉండాలి

    ori@CryptoDocGuy:~/noir/250911-zk-bank/server/noir$ nargo execute
    
    [zkBank] Circuit witness successfully solved
    [zkBank] Witness saved to target/zkBank.gz
    [zkBank] Circuit output: (0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b, 0x0cfc0a67cb7308e4e9b254026b54204e34f6c8b041be207e64c5db77d95dd82d, 0x450cf9da6e180d6159290554ae3d8787, 0x6d8bc5a15b9037e52fb59b6b98722a85)
    
  10. సందేశం సరిగ్గా హాష్ చేయబడిందో లేదో చూడటానికి వెబ్ బ్రౌజర్‌లో మీరు చూసే హాష్‌తో చివరి రెండు విలువలను పోల్చండి.

server/noir/Prover.toml

ఈ ఫైల్ (opens in a new tab) Noir ఆశించే సమాచార ఫార్మాట్‌ను చూపిస్తుంది.

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

సందేశం టెక్స్ట్ ఫార్మాట్‌లో ఉంది, ఇది వినియోగదారుకు అర్థం చేసుకోవడానికి సులభం చేస్తుంది (ఇది సంతకం చేసేటప్పుడు అవసరం) మరియు Noir కోడ్ పార్స్ చేయడానికి సులభం చేస్తుంది. మొత్తం ఒక వైపు భిన్న బదిలీలను ప్రారంభించడానికి మరియు మరోవైపు సులభంగా చదవడానికి వీలుగా ఫిన్నీలలో కోట్ చేయబడింది. చివరి సంఖ్య నాన్స్ (opens in a new tab).

స్ట్రింగ్ 100 అక్షరాల పొడవు ఉంటుంది. జీరో-కనౌలెడ్జి రుజువులు వేరియబుల్-సైజ్ డేటాను సరిగ్గా నిర్వహించవు, కాబట్టి తరచుగా డేటాను ప్యాడ్ చేయడం అవసరం.

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

ఈ మూడు పారామితులు స్థిర-పరిమాణ బైట్ శ్రేణులు.

నిర్మాణాల శ్రేణిని పేర్కొనడానికి ఇది మార్గం. ప్రతి ఎంట్రీ కోసం, మేము చిరునామా, బ్యాలెన్స్ (మిల్లీఈటీహెచ్‌లో, అంటే, ఫిన్నీ (opens in a new tab)), మరియు తదుపరి నాన్స్ విలువను పేర్కొంటాము.

client/src/Transfer.tsx

ఈ ఫైల్ (opens in a new tab) క్లయింట్-సైడ్ ప్రాసెసింగ్‌ను అమలు చేస్తుంది మరియు server/noir/Prover.toml ఫైల్‌ను ఉత్పత్తి చేస్తుంది (జీరో-కనౌలెడ్జి పారామితులను కలిగి ఉన్నది).

మరింత ఆసక్తికరమైన భాగాల వివరణ ఇక్కడ ఉంది.

export default attrs =>  {

ఈ ఫంక్షన్ Transfer React కాంపోనెంట్‌ను సృష్టిస్తుంది, దీనిని ఇతర ఫైళ్లు దిగుమతి చేసుకోవచ్చు.

  const accounts = [
    "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
    "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
    "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
    "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
  ]

ఇవి ఖాతా చిరునామాలు, test ... test junk పాస్‌ఫ్రేజ్ ద్వారా సృష్టించబడిన చిరునామాలు. మీరు మీ స్వంత చిరునామాలను ఉపయోగించాలనుకుంటే, ఈ నిర్వచనాన్ని సవరించండి.

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

Wagmi hooks (opens in a new tab) మాకు viem (opens in a new tab) లైబ్రరీ మరియు వాలెట్‌ను యాక్సెస్ చేయడానికి అనుమతిస్తాయి.

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

ఇది సందేశం, ఖాళీలతో ప్యాడ్ చేయబడింది. ప్రతిసారీ useState (opens in a new tab) వేరియబుల్స్ ఒకటి మారినప్పుడు, కాంపోనెంట్ తిరిగి గీయబడుతుంది మరియు message నవీకరించబడుతుంది.

  const sign = async () => {

వినియోగదారు Sign బటన్‌ను క్లిక్ చేసినప్పుడు ఈ ఫంక్షన్ కాల్ చేయబడుతుంది. సందేశం స్వయంచాలకంగా నవీకరించబడుతుంది, కానీ సంతకానికి వాలెట్‌లో వినియోగదారు ఆమోదం అవసరం, మరియు అవసరం లేకపోతే మేము దానిని అడగాలనుకోవడం లేదు.

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

వాలెట్‌ను సందేశానికి సంతకం చేయమని (opens in a new tab) అడగండి.

    const hash = hashMessage(message)

సందేశం హాష్ పొందండి. వినియోగదారుకు డీబగ్గింగ్ (Noir కోడ్ యొక్క) కోసం దీనిని అందించడం సహాయకరంగా ఉంటుంది.

    const pubKey = await recoverPublicKey({
        hash,
        signature
    })

పబ్లిక్ కీ పొందండి (opens in a new tab). ఇది Noir ecrecover (opens in a new tab) ఫంక్షన్ కోసం అవసరం.

    setSignature(signature)
    setHash(hash)
    setPubKey(pubKey)

స్థితి వేరియబుల్స్‌ను సెట్ చేయండి. ఇది చేయడం వల్ల కాంపోనెంట్ తిరిగి గీయబడుతుంది (sign ఫంక్షన్ నిష్క్రమించిన తర్వాత) మరియు వినియోగదారుకు నవీకరించబడిన విలువలను చూపుతుంది.

    let proverToml = `

Prover.toml కోసం టెక్స్ట్.

message="${message}"

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

Viem మాకు పబ్లిక్ కీని 65-బైట్ హెక్సాడెసిమల్ స్ట్రింగ్‌గా అందిస్తుంది. మొదటి బైట్ 0x04, ఒక వెర్షన్ మార్కర్. దీని తర్వాత పబ్లిక్ కీ యొక్క x కోసం 32 బైట్లు మరియు తర్వాత పబ్లిక్ కీ యొక్క y కోసం 32 బైట్లు ఉంటాయి.

అయితే, Noir ఈ సమాచారాన్ని రెండు బైట్ శ్రేణులుగా పొందాలని ఆశిస్తుంది, ఒకటి x కోసం మరియు మరొకటి y కోసం. జీరో-కనౌలెడ్జి రుజువులో భాగంగా కాకుండా ఇక్కడ క్లయింట్‌లో పార్స్ చేయడం సులభం.

గమనించండి ఇది సాధారణంగా జీరో-కనౌలెడ్జిలో మంచి పద్ధతి. జీరో-కనౌలెడ్జి రుజువులోని కోడ్ ఖరీదైనది, కాబట్టి జీరో-కనౌలెడ్జి రుజువు వెలుపల చేయగల ఏ ప్రాసెసింగ్ అయినా జీరో-కనౌలెడ్జి రుజువు వెలుపల చేయాలి.

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

సంతకం కూడా 65-బైట్ హెక్సాడెసిమల్ స్ట్రింగ్‌గా అందించబడుతుంది. అయితే, పబ్లిక్ కీని తిరిగి పొందడానికి చివరి బైట్ మాత్రమే అవసరం. పబ్లిక్ కీ ఇప్పటికే Noir కోడ్‌కు అందించబడుతుంది కాబట్టి, సంతకాన్ని ధృవీకరించడానికి మాకు ఇది అవసరం లేదు, మరియు Noir కోడ్‌కు ఇది అవసరం లేదు.

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

ఖాతాలను అందించండి.

    setProverToml(proverToml)
  }

  return (
    <>
        <h2>Transfer</h2>

ఇది కాంపోనెంట్ యొక్క HTML (మరింత కచ్చితంగా, JSX (opens in a new tab)) ఫార్మాట్.

server/noir/src/main.nr

ఈ ఫైల్ (opens in a new tab) అసలు జీరో-కనౌలెడ్జి కోడ్.

use std::hash::pedersen_hash;

Pedersen hash (opens in a new tab) Noir స్టాండర్డ్ లైబ్రరీ (opens in a new tab)తో అందించబడుతుంది. జీరో-కనౌలెడ్జి రుజువులు సాధారణంగా ఈ హాష్ ఫంక్షన్‌ను ఉపయోగిస్తాయి. స్టాండర్డ్ హాష్ ఫంక్షన్లతో పోలిస్తే అంకగణిత సర్క్యూట్‌లలో (opens in a new tab) గణించడం చాలా సులభం.

use keccak256::keccak256;
use dep::ecrecover;

ఈ రెండు ఫంక్షన్లు బాహ్య లైబ్రరీలు, ఇవి Nargo.toml (opens in a new tab)లో నిర్వచించబడ్డాయి. అవి ఖచ్చితంగా వాటి పేరుకు తగ్గట్టుగా ఉంటాయి, keccak256 హాష్ (opens in a new tab)ను గణించే ఒక ఫంక్షన్ మరియు ఇతీరియము సంతకాలను ధృవీకరించి, సంతకం చేసినవారి ఇతీరియము చిరునామాను తిరిగి పొందే ఒక ఫంక్షన్.

global ACCOUNT_NUMBER : u32 = 5;

Noir Rust (opens in a new tab) నుండి ప్రేరణ పొందింది. వేరియబుల్స్, డిఫాల్ట్‌గా, స్థిరాంకాలు. గ్లోబల్ కాన్ఫిగరేషన్ స్థిరాంకాలను మనం ఇలా నిర్వచిస్తాము. ప్రత్యేకంగా, ACCOUNT_NUMBER అనేది మనం నిల్వ చేసే ఖాతాల సంఖ్య.

u<number> అని పేరు పెట్టబడిన డేటా రకాలు ఆ సంఖ్య బిట్స్, సంతకం చేయనివి. మద్దతు ఉన్న ఏకైక రకాలు u8, u16, u32, u64, మరియు u128.

global FLAT_ACCOUNT_FIELDS : u32 = 2;

ఈ వేరియబుల్ ఖాతాల పెడెర్సెన్ హాష్ కోసం ఉపయోగించబడుతుంది, క్రింద వివరించిన విధంగా.

global MESSAGE_LENGTH : u32 = 100;

పైన వివరించినట్లుగా, సందేశం పొడవు స్థిరంగా ఉంటుంది. ఇది ఇక్కడ పేర్కొనబడింది.

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

EIP-191 సంతకాలకు (opens in a new tab) 26-బైట్ ఉపసర్గతో ఒక బఫర్ అవసరం, దాని తర్వాత ASCIIలో సందేశం పొడవు, మరియు చివరగా సందేశం ఉంటుంది.

struct Account {
    balance: u128,
    address: Field,
    nonce: u32,
}

ఖాతా గురించి మనం నిల్వ చేసే సమాచారం. Field (opens in a new tab) అనేది ఒక సంఖ్య, సాధారణంగా 253 బిట్ల వరకు ఉంటుంది, దీనిని జీరో-కనౌలెడ్జి రుజువును అమలు చేసే అంకగణిత సర్క్యూట్‌లో (opens in a new tab) నేరుగా ఉపయోగించవచ్చు. ఇక్కడ మనం Fieldను 160-బిట్ ఇతీరియము చిరునామాను నిల్వ చేయడానికి ఉపయోగిస్తాము.

struct TransferTxn {
    from: Field,
    to: Field,
    amount: u128,
    nonce: u32
}

బదిలీ లావాదేవీ కోసం మనం నిల్వ చేసే సమాచారం.

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

ఒక ఫంక్షన్ నిర్వచనం. పారామితి Account సమాచారం. ఫలితం Field వేరియబుల్స్ యొక్క శ్రేణి, దీని పొడవు FLAT_ACCOUNT_FIELDS

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

శ్రేణిలోని మొదటి విలువ ఖాతా చిరునామా. రెండవది బ్యాలెన్స్ మరియు నాన్స్ రెండింటినీ కలిగి ఉంటుంది. .into() కాల్స్ ఒక సంఖ్యను దాని అవసరమైన డేటా రకానికి మారుస్తాయి. account.nonce ఒక u32 విలువ, కానీ దానిని account.balance « 32కు, ఒక u128 విలువకు, జోడించడానికి అది u128గా ఉండాలి. అది మొదటి .into(). రెండవది u128 ఫలితాన్ని Fieldగా మారుస్తుంది, తద్వారా అది శ్రేణిలో సరిపోతుంది.

    flat
}

Noirలో, ఫంక్షన్లు చివరలో మాత్రమే ఒక విలువను తిరిగి ఇవ్వగలవు (ప్రారంభ రిటర్న్ లేదు). రిటర్న్ విలువను పేర్కొనడానికి, మీరు దానిని ఫంక్షన్ యొక్క ముగింపు బ్రాకెట్‌కు ముందు అంచనా వేస్తారు.

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

ఈ ఫంక్షన్ ఖాతాల శ్రేణిని Field శ్రేణిగా మారుస్తుంది, దీనిని పీటర్సన్ హాష్‌కు ఇన్‌పుట్‌గా ఉపయోగించవచ్చు.

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

మార్చదగిన వేరియబుల్‌ను పేర్కొనడానికి ఇది మార్గం, అంటే కాదు ఒక స్థిరాంకం. Noirలోని వేరియబుల్స్ ఎల్లప్పుడూ ఒక విలువను కలిగి ఉండాలి, కాబట్టి మనం ఈ వేరియబుల్‌ను అన్ని సున్నాలకు ప్రారంభీకరిస్తాము.

    for i in 0..ACCOUNT_NUMBER {

ఇది for లూప్. గమనించండి సరిహద్దులు స్థిరాంకాలు. Noir లూప్‌లు వాటి సరిహద్దులను కంపైల్ సమయంలో తెలిసి ఉండాలి. కారణం అంకగణిత సర్క్యూట్‌లు ప్రవాహ నియంత్రణకు మద్దతు ఇవ్వవు. for లూప్‌ను ప్రాసెస్ చేసేటప్పుడు, కంపైలర్ దానిలోని కోడ్‌ను అనేక సార్లు ఉంచుతుంది, ప్రతి పునరావృత్తికి ఒకటి.

చివరగా, మనం ఖాతాల శ్రేణిని హాష్ చేసే ఫంక్షన్‌కు వచ్చాము.

ఈ ఫంక్షన్ నిర్దిష్ట చిరునామాతో ఉన్న ఖాతాను కనుగొంటుంది. ఈ ఫంక్షన్ ప్రామాణిక కోడ్‌లో చాలా అసమర్థంగా ఉంటుంది ఎందుకంటే ఇది చిరునామాను కనుగొన్న తర్వాత కూడా అన్ని ఖాతాల మీద పునరావృతమవుతుంది.

అయితే, జీరో-కనౌలెడ్జి రుజువులలో, ప్రవాహ నియంత్రణ లేదు. మనం ఎప్పుడైనా ఒక షరతును తనిఖీ చేయవలసి వస్తే, మనం దానిని ప్రతిసారీ తనిఖీ చేయాలి.

if స్టేట్‌మెంట్లతో ఇలాంటిదే జరుగుతుంది. పైన ఉన్న లూప్‌లోని if స్టేట్‌మెంట్ ఈ గణిత స్టేట్‌మెంట్‌లకు అనువదించబడింది.

conditionresult = accounts[i].address == address // అవి సమానంగా ఉంటే ఒకటి, లేకపోతే సున్నా

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

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

    account

assert (opens in a new tab) ఫంక్షన్, నిర్ధారణ తప్పు అయితే జీరో-కనౌలెడ్జి రుజువును క్రాష్ చేస్తుంది. ఈ సందర్భంలో, సంబంధిత చిరునామాతో ఖాతాను కనుగొనలేకపోతే. చిరునామాను నివేదించడానికి, మనం ఫార్మాట్ స్ట్రింగ్ (opens in a new tab)ను ఉపయోగిస్తాము.

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

ఈ ఫంక్షన్ ఒక బదిలీ లావాదేవీని వర్తింపజేసి, కొత్త ఖాతాల శ్రేణిని తిరిగి ఇస్తుంది.

    let from = find_account(accounts, txn.from);
    let to = find_account(accounts, txn.to);

    let (txnFrom, txnAmount, txnNonce, accountNonce) =
        (txn.from, txn.amount, txn.nonce, accounts[from].nonce);

మనం Noirలోని ఫార్మాట్ స్ట్రింగ్‌లో నిర్మాణ మూలకాలను యాక్సెస్ చేయలేము, కాబట్టి మనం ఉపయోగపడే కాపీని సృష్టిస్తాము.

    assert (accounts[from].balance >= txn.amount,
        f"{txnFrom} does not have {txnAmount} finney");

    assert (accounts[from].nonce == txn.nonce,
        f"Transaction has nonce {txnNonce}, but the account is expected to use {accountNonce}");

ఇవి ఒక లావాదేవీని చెల్లనివిగా చేసే రెండు షరతులు.

    let mut newAccounts = accounts;

    newAccounts[from].balance -= txn.amount;
    newAccounts[from].nonce += 1;
    newAccounts[to].balance += txn.amount;

    newAccounts

కొత్త ఖాతాల శ్రేణిని సృష్టించి, ఆపై దానిని తిరిగి ఇవ్వండి.

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

ఈ ఫంక్షన్ సందేశం నుండి చిరునామాను చదువుతుంది.

{
    let mut result : Field = 0;

    for i in 7..47 {

చిరునామా ఎల్లప్పుడూ 20 బైట్లు (అంటే, 40 హెక్సాడెసిమల్ అంకెలు) పొడవు ఉంటుంది, మరియు అక్షరం #7 వద్ద ప్రారంభమవుతుంది.

సందేశం నుండి మొత్తం మరియు నాన్స్‌ను చదవండి.

{
    let mut amount : u128 = 0;
    let mut nonce: u32 = 0;
    let mut stillReadingAmount: bool = true;
    let mut lookingForNonce: bool = false;
    let mut stillReadingNonce: bool = false;

సందేశంలో, చిరునామా తర్వాత మొదటి సంఖ్య బదిలీ చేయాల్సిన ఫిన్నీ మొత్తం (అంటే, ETHలో వెయ్యవ వంతు). రెండవ సంఖ్య నాన్స్. వాటి మధ్య ఉన్న ఏ టెక్స్ట్ అయినా విస్మరించబడుతుంది.

ఒక ట్యూపుల్ (opens in a new tab)ను తిరిగి ఇవ్వడం అనేది ఒక ఫంక్షన్ నుండి బహుళ విలువలను తిరిగి ఇవ్వడానికి Noir మార్గం.

ఈ ఫంక్షన్ సందేశాన్ని బైట్‌లుగా మారుస్తుంది, ఆపై మొత్తాలను TransferTxnగా మారుస్తుంది.

// The equivalent to Viem's hashMessage
// https://viem.sh/docs/utilities/hashMessage#hashmessage
fn hashMessage(message: str<MESSAGE_LENGTH>) -> [u8;32] {

మనం ఖాతాల కోసం పెడెర్సెన్ హాష్‌ను ఉపయోగించగలిగాము ఎందుకంటే అవి జీరో-కనౌలెడ్జి రుజువులో మాత్రమే హాష్ చేయబడతాయి. అయితే, ఈ కోడ్‌లో మనం సందేశం యొక్క సంతకాన్ని తనిఖీ చేయాలి, ఇది బ్రౌజర్ ద్వారా ఉత్పత్తి చేయబడుతుంది. దాని కోసం, మనం EIP 191 (opens in a new tab)లో ఇతీరియము సంతకం ఫార్మాట్‌ను అనుసరించాలి. అంటే మనం ఒక ప్రామాణిక ఉపసర్గ, ASCIIలో సందేశం పొడవు, మరియు సందేశాన్ని కలిగి ఉన్న ఒక మిశ్రమ బఫర్‌ను సృష్టించి, దానిని హాష్ చేయడానికి ఇతీరియము ప్రామాణిక keccak256ను ఉపయోగించాలి.

ఒక అప్లికేషన్ వినియోగదారుని ఒక లావాదేవీగా లేదా మరేదైనా ప్రయోజనం కోసం ఉపయోగించగల సందేశానికి సంతకం చేయమని అడిగే సందర్భాలను నివారించడానికి, EIP 191 అన్ని సంతకం చేసిన సందేశాలు అక్షరం 0x19 (చెల్లుబాటు అయ్యే ASCII అక్షరం కాదు) తర్వాత Ethereum Signed Message: మరియు ఒక కొత్త లైన్‌తో ప్రారంభం కావాలని నిర్దేశిస్తుంది.

999 వరకు సందేశం పొడవులను నిర్వహించి, అది ఎక్కువ ఉంటే విఫలమవండి. సందేశం పొడవు ఒక స్థిరాంకం అయినప్పటికీ, నేను ఈ కోడ్‌ను జోడించాను ఎందుకంటే ఇది దానిని మార్చడం సులభం చేస్తుంది. ఒక ఉత్పత్తి వ్యవస్థలో, మీరు బహుశా మెరుగైన పనితీరు కోసం MESSAGE_LENGTH మారదని ఊహించుకుంటారు.

    keccak256::keccak256(buffer, HASH_BUFFER_SIZE)
}

ఇతీరియము ప్రామాణిక keccak256 ఫంక్షన్‌ను ఉపయోగించండి.

fn signatureToAddressAndHash(
        message: str<MESSAGE_LENGTH>, 
        pubKeyX: [u8; 32],
        pubKeyY: [u8; 32],
        signature: [u8; 64]
    ) -> (Field, Field, Field)   // address, first 16 bytes of hash, last 16 bytes of hash        
{

ఈ ఫంక్షన్ సంతకాన్ని ధృవీకరిస్తుంది, దీనికి సందేశం హాష్ అవసరం. ఆపై అది సంతకం చేసిన చిరునామా మరియు సందేశం హాష్‌ను మాకు అందిస్తుంది. సందేశం హాష్ రెండు Field విలువలుగా అందించబడుతుంది ఎందుకంటే అవి ప్రోగ్రామ్‌లోని మిగిలిన భాగంలో ఒక బైట్ శ్రేణి కంటే ఉపయోగించడం సులభం.

ఫీల్డ్ గణనలు ఒక పెద్ద సంఖ్యకు మాడ్యూలో (opens in a new tab) చేయబడతాయి కాబట్టి మనం రెండు Field విలువలను ఉపయోగించాలి, కానీ ఆ సంఖ్య సాధారణంగా 256 బిట్ల కంటే తక్కువగా ఉంటుంది (లేకపోతే EVMలో ఆ గణనలను చేయడం కష్టం).

    let hash = hashMessage(message);

    let mut (hash1, hash2) = (0,0);

    for i in 0..16 {
        hash1 = hash1*256 + hash[31-i].into();
        hash2 = hash2*256 + hash[15-i].into();
    }

hash1 మరియు hash2లను మార్చదగిన వేరియబుల్స్‌గా పేర్కొనండి, మరియు హాష్‌ను వాటిలో బైట్ బైట్‌గా వ్రాయండి.

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

ఇది Solidity యొక్క ecrecover (opens in a new tab)తో సమానంగా ఉంటుంది, రెండు ముఖ్యమైన తేడాలతో:

  • సంతకం చెల్లనిది అయితే, కాల్ assertను విఫలం చేస్తుంది మరియు ప్రోగ్రామ్ రద్దు చేయబడుతుంది.
  • పబ్లిక్ కీ సంతకం మరియు హాష్ నుండి తిరిగి పొందగలిగినప్పటికీ, ఇది బాహ్యంగా చేయగల ప్రాసెసింగ్ మరియు అందువల్ల జీరో-కనౌలెడ్జి రుజువులో చేయడం విలువైనది కాదు. ఎవరైనా ఇక్కడ మమ్మల్ని మోసం చేయడానికి ప్రయత్నిస్తే, సంతకం ధృవీకరణ విఫలమవుతుంది.

చివరగా, మనం main ఫంక్షన్‌కు చేరుకున్నాము. ఖాతాల హాష్‌ను పాత విలువ నుండి కొత్తదానికి చెల్లుబాటు అయ్యే విధంగా మార్చే ఒక లావాదేవీ మా వద్ద ఉందని మేము నిరూపించాలి. దీనికి ఈ నిర్దిష్ట లావాదేవీ హాష్ ఉందని కూడా మేము నిరూపించాలి, తద్వారా దానిని పంపిన వ్యక్తికి వారి లావాదేవీ ప్రాసెస్ చేయబడిందని తెలుస్తుంది.

{
    let mut txn = readTransferTxn(message);

txn మార్చదగినదిగా ఉండాలి ఎందుకంటే మనం సందేశం నుండి చిరునామాను చదవము, సంతకం నుండి చదువుతాము.

దశ 2 - ఒక సర్వర్‌ను జోడించడం

రెండవ దశలో, మేము బ్రౌజర్ నుండి బదిలీ లావాదేవీలను స్వీకరించి, అమలు చేసే ఒక సర్వర్‌ను జోడిస్తాము.

చర్యలో చూడటానికి:

  1. Vite నడుస్తుంటే దాన్ని ఆపండి.

  2. సర్వర్‌ను కలిగి ఉన్న శాఖను డౌన్‌లోడ్ చేయండి మరియు మీకు అవసరమైన అన్ని మాడ్యూల్స్ ఉన్నాయని నిర్ధారించుకోండి.

    git checkout 02-add-server
    cd client
    npm install
    cd ../server
    npm install
    

    Noir కోడ్‌ను కంపైల్ చేయాల్సిన అవసరం లేదు, ఇది మీరు దశ 1 కోసం ఉపయోగించిన కోడ్‌తో సమానంగా ఉంటుంది.

  3. సర్వర్‌ను ప్రారంభించండి.

    npm run start
    
  4. వేరే కమాండ్-లైన్ విండోలో, బ్రౌజర్ కోడ్‌ను అందించడానికి Viteని రన్ చేయండి.

    cd client
    npm 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 చేయండి లేదా మీరు సర్వర్‌ను రన్ చేసే విండోలో చూడండి. సర్వర్ ప్రతిసారి మారినప్పుడు స్థితిని లాగ్ చేస్తుంది.

server/index.mjs

ఈ ఫైల్ (opens in a new tab) సర్వర్ ప్రాసెస్‌ను కలిగి ఉంది, మరియు main.nr (opens in a new tab) వద్ద Noir కోడ్‌తో సంకర్షణ చెందుతుంది. ఆసక్తికరమైన భాగాల వివరణ ఇక్కడ ఉంది.

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

noir.js (opens in a new tab) లైబ్రరీ జావాస్క్రిప్ట్ కోడ్ మరియు Noir కోడ్ మధ్య ఇంటర్ఫేస్ చేస్తుంది.

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

అంకగణిత సర్క్యూట్‌ను లోడ్ చేయండి---మునుపటి దశలో మనం సృష్టించిన కంపైల్ చేయబడిన Noir ప్రోగ్రామ్---మరియు దానిని అమలు చేయడానికి సిద్ధం చేయండి.

// We only provide account information in return to a signed request
const accountInformation = async signature => {
    const fromAddress = await recoverAddress({
        hash: hashMessage("Get account data " + Math.floor((new Date().getTime())/60000)),
        signature
    })

ఖాతా సమాచారాన్ని అందించడానికి, మాకు సంతకం మాత్రమే అవసరం. కారణం, సందేశం ఏమి కాబోతోందో మాకు ఇప్పటికే తెలుసు, మరియు అందువల్ల సందేశం హాష్ కూడా.

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

ఒక సందేశాన్ని ప్రాసెస్ చేసి, అది ఎన్కోడ్ చేసే లావాదేవీని అమలు చేయండి.

    // Get the public key
    const pubKey = await recoverPublicKey({
        hash,
        signature
    })

ఇప్పుడు మేము సర్వర్‌లో జావాస్క్రిప్ట్‌ను రన్ చేస్తున్నాము కాబట్టి, మేము పబ్లిక్ కీని క్లయింట్‌లో కాకుండా అక్కడ తిరిగి పొందవచ్చు.

noir.execute Noir ప్రోగ్రామ్‌ను రన్ చేస్తుంది. పారామితులు Prover.toml (opens in a new tab)లో అందించిన వాటికి సమానంగా ఉంటాయి. గమనించండి పొడవైన విలువలు హెక్సాడెసిమల్ స్ట్రింగ్‌ల శ్రేణిగా అందించబడతాయి (["0x60", "0xA7"]), ఒకే హెక్సాడెసిమల్ విలువగా కాదు (0x60A7), Viem చేసే విధంగా.

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

ఒకవేళ లోపం ఉంటే, దాన్ని పట్టుకుని, ఆపై ఒక సరళీకృత వెర్షన్‌ను క్లయింట్‌కు పంపండి.

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

లావాదేవీని వర్తించండి. మేము ఇప్పటికే దానిని Noir కోడ్‌లో చేసాము, కానీ ఫలితాన్ని అక్కడ నుండి సంగ్రహించడం కంటే ఇక్కడ మళ్ళీ చేయడం సులభం.

let Accounts = [
    {
        address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        balance: 5000,
        nonce: 0,
    },

ప్రారంభ Accounts నిర్మాణం.

దశ 3 - ఇతీరియము స్మార్ట్ కాంట్రాక్టులు

  1. సర్వర్ మరియు క్లయింట్ ప్రక్రియలను ఆపండి.

  2. స్మార్ట్ కాంట్రాక్టులతో ఉన్న శాఖను డౌన్‌లోడ్ చేయండి మరియు మీకు అవసరమైన అన్ని మాడ్యూల్స్ ఉన్నాయని నిర్ధారించుకోండి.

    git checkout 03-smart-contracts
    cd client
    npm install
    cd ../server
    npm install
    
  3. anvilను వేరే కమాండ్-లైన్ విండోలో రన్ చేయండి.

  4. ధృవీకరణ కీ మరియు సాలిడిటీ వెరిఫైయర్‌ను ఉత్పత్తి చేయండి, ఆపై వెరిఫైయర్ కోడ్‌ను Solidity ప్రాజెక్ట్‌కు కాపీ చేయండి.

    cd noir
    bb write_vk -b ./target/zkBank.json -o ./target --oracle_hash keccak
    bb write_solidity_verifier -k ./target/vk -o ./target/Verifier.sol
    cp target/Verifier.sol ../../smart-contracts/src
    
  5. స్మార్ట్ కాంట్రాక్టులకు వెళ్లి, anvil బ్లాక్ చైనును ఉపయోగించడానికి పర్యావరణ వేరియబుల్స్‌ను సెట్ చేయండి.

    cd ../../smart-contracts
    export ETH_RPC_URL=http://localhost:8545
    ETH_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    
  6. Verifier.solను అమలు చేయండి మరియు చిరునామాను పర్యావరణ వేరియబుల్‌లో నిల్వ చేయండి.

    VERIFIER_ADDRESS=`forge create src/Verifier.sol:HonkVerifier --private-key $ETH_PRIVATE_KEY --optimize --broadcast | awk '/Deployed to:/ {print $3}'`
    echo $VERIFIER_ADDRESS
    
  7. ZkBank కాంట్రాక్టును అమలు చేయండి.

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

    0x199..67b విలువ Accounts యొక్క ప్రారంభ స్థితి యొక్క పెడెర్సెన్ హాష్. మీరు server/index.mjsలో ఈ ప్రారంభ స్థితిని సవరించినట్లయితే, జీరో-కనౌలెడ్జి రుజువు ద్వారా నివేదించబడిన ప్రారంభ హాష్‌ను చూడటానికి ఒక లావాదేవీని రన్ చేయవచ్చు.

  8. సర్వర్‌ను రన్ చేయండి.

    cd ../server
    npm run start
    
  9. క్లయింట్‌ను వేరే కమాండ్-లైన్ విండోలో రన్ చేయండి.

    cd client
    npm run dev
    
  10. కొన్ని లావాదేవీలను రన్ చేయండి.

  11. స్థితి ఆన్‌చైన్‌లో మారిందని ధృవీకరించడానికి, సర్వర్ ప్రాసెస్‌ను పునఃప్రారంభించండి. లావాదేవీలలోని అసలు హాష్ విలువ ఆన్‌చైన్‌లో నిల్వ చేయబడిన హాష్ విలువకు భిన్నంగా ఉన్నందున, ZkBank ఇకపై లావాదేవీలను అంగీకరించదని చూడండి.

    ఇది ఊహించిన రకమైన లోపం.

server/index.mjs

ఈ ఫైల్‌లోని మార్పులు ఎక్కువగా అసలు రుజువును సృష్టించి, దానిని ఆన్‌చైన్‌లో సమర్పించడానికి సంబంధించినవి.

import { exec } from 'child_process'
import util from 'util'

const execPromise = util.promisify(exec)

ఆన్‌చైన్‌కు పంపడానికి అసలు రుజువును సృష్టించడానికి మేము బారెటెన్‌బెర్గ్ ప్యాకేజీని (opens in a new tab) ఉపయోగించాలి. మేము ఈ ప్యాకేజీని కమాండ్-లైన్ ఇంటర్‌ఫేస్ (bb)ని రన్ చేయడం ద్వారా లేదా జావాస్క్రిప్ట్ లైబ్రరీ, bb.js (opens in a new tab)ని ఉపయోగించడం ద్వారా ఉపయోగించవచ్చు. జావాస్క్రిప్ట్ లైబ్రరీ కోడ్‌ను దేశీయంగా రన్ చేయడం కంటే చాలా నెమ్మదిగా ఉంటుంది, కాబట్టి మేము కమాండ్-లైన్‌ను ఉపయోగించడానికి ఇక్కడ exec (opens in a new tab)ని ఉపయోగిస్తాము.

గమనించండి మీరు bb.jsని ఉపయోగించాలని నిర్ణయించుకుంటే, మీరు ఉపయోగిస్తున్న Noir వెర్షన్‌తో అనుకూలమైన వెర్షన్‌ను ఉపయోగించాలి. వ్రాసే సమయంలో, ప్రస్తుత Noir వెర్షన్ (1.0.0-beta.11) bb.js వెర్షన్ 0.87ను ఉపయోగిస్తుంది.

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

ఇక్కడి చిరునామా, మీరు శుభ్రమైన anvilతో ప్రారంభించి, పైన ఉన్న ఆదేశాలను అనుసరించినప్పుడు పొందేది.

const walletClient = createWalletClient({ 
    chain: anvil, 
    transport: http(), 
    account: privateKeyToAccount("0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6")
})

ఈ ప్రైవేట్ కీ anvilలోని డిఫాల్ట్ ప్రీ-ఫండెడ్ ఖాతాలలో ఒకటి.

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

bb ఎగ్జిక్యూటబుల్ ఉపయోగించి ఒక రుజువును రూపొందించండి.

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

సాక్షిని ఒక ఫైల్‌కు వ్రాయండి.

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

నిజానికి రుజువును సృష్టించండి. ఈ దశ పబ్లిక్ వేరియబుల్స్‌తో ఒక ఫైల్‌ను కూడా సృష్టిస్తుంది, కానీ మాకు అది అవసరం లేదు. మేము ఆ వేరియబుల్స్‌ను noir.execute నుండి ఇప్పటికే పొందాము.

    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లను తీసివేసి, ఆపై చివరలో ఒకటి జోడించడం ద్వారా ఫార్మాట్‌ను మారుస్తాము.

    await execPromise(`rm -r ${fname} ${fileID}`)

    return proof
}

శుభ్రపరిచి, రుజువును తిరిగి ఇవ్వండి.

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

    const publicFields = noirResult.returnValue.map(x=>'0x' + x.slice(2).padStart(64, "0"))

పబ్లిక్ ఫీల్డ్‌లు 32-బైట్ విలువల శ్రేణిగా ఉండాలి. అయితే, లావాదేవీ హాష్‌ను రెండు Field విలువల మధ్య విభజించవలసి వచ్చినందున, ఇది 16-బైట్ విలువగా కనిపిస్తుంది. ఇక్కడ మేము సున్నాలను జోడిస్తాము, తద్వారా Viem అది నిజానికి 32 బైట్లు అని అర్థం చేసుకుంటుంది.

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

ప్రతి చిరునామా ప్రతి నాన్స్‌ను ఒకసారి మాత్రమే ఉపయోగిస్తుంది, తద్వారా మేము fromAddress మరియు nonce కలయికను సాక్షి ఫైల్ మరియు అవుట్‌పుట్ డైరెక్టరీ కోసం ఒక ప్రత్యేక ఐడెంటిఫైయర్‌గా ఉపయోగించవచ్చు.

లావాదేవీని చైన్‌కు పంపండి.

smart-contracts/src/ZkBank.sol

ఇది లావాదేవీని స్వీకరించే ఆన్‌చైన్ కోడ్.

ఆన్‌చైన్ కోడ్ రెండు వేరియబుల్స్‌ను ట్రాక్ చేయాలి: వెరిఫైయర్ (nargo ద్వారా సృష్టించబడిన ఒక ప్రత్యేక కాంట్రాక్ట్) మరియు ప్రస్తుత స్థితి హాష్.

    event TransactionProcessed(
        bytes32 indexed transactionHash,
        bytes32 oldStateHash,
        bytes32 newStateHash
    );

ప్రతిసారి స్థితి మారినప్పుడు, మేము TransactionProcessed ఈవెంట్‌ను విడుదల చేస్తాము.

    function processTransaction(
        bytes calldata _proof,
        bytes32[] calldata _publicFields
    ) public {

ఈ ఫంక్షన్ లావాదేవీలను ప్రాసెస్ చేస్తుంది. ఇది రుజువును (bytesగా) మరియు పబ్లిక్ ఇన్‌పుట్‌లను (bytes32 శ్రేణిగా), వెరిఫైయర్ అవసరమైన ఫార్మాట్‌లో (ఆన్‌చైన్ ప్రాసెసింగ్‌ను మరియు అందువల్ల గ్యాస్ ఖర్చులను తగ్గించడానికి) పొందుతుంది.

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

జీరో-కనౌలెడ్జి రుజువు లావాదేవీ మన ప్రస్తుత హాష్ నుండి కొత్తదానికి మారుతుందని ఉండాలి.

        myVerifier.verify(_proof, _publicFields);

జీరో-కనౌలెడ్జి రుజువును ధృవీకరించడానికి వెరిఫైయర్ కాంట్రాక్టును కాల్ చేయండి. జీరో-కనౌలెడ్జి రుజువు తప్పు అయితే ఈ దశ లావాదేవీని తిరిగి తీసుకుంటుంది.

అన్నీ సరిగ్గా ఉంటే, స్థితి హాష్‌ను కొత్త విలువకు నవీకరించి, TransactionProcessed ఈవెంట్‌ను విడుదల చేయండి.

కేంద్రీకృత భాగం ద్వారా దుర్వినియోగాలు

సమాచార భద్రత మూడు గుణాలను కలిగి ఉంటుంది:

  • గోప్యత, వినియోగదారులు వారు చదవడానికి అధికారం లేని సమాచారాన్ని చదవలేరు.
  • సమగ్రత, అధీకృత వినియోగదారులు అధీకృత పద్ధతిలో తప్ప సమాచారాన్ని మార్చలేరు.
  • లభ్యత, అధీకృత వినియోగదారులు వ్యవస్థను ఉపయోగించగలరు.

ఈ వ్యవస్థలో, సమగ్రత జీరో-కనౌలెడ్జి రుజువుల ద్వారా అందించబడుతుంది. లభ్యతను హామీ ఇవ్వడం చాలా కష్టం, మరియు గోప్యత అసాధ్యం, ఎందుకంటే బ్యాంకు ప్రతి ఖాతా యొక్క బ్యాలెన్స్ మరియు అన్ని లావాదేవీలను తెలుసుకోవాలి. సమాచారం ఉన్న ఒక సంస్థ ఆ సమాచారాన్ని పంచుకోకుండా నిరోధించడానికి మార్గం లేదు.

స్టీల్త్ చిరునామాలను (opens in a new tab) ఉపయోగించి నిజంగా గోప్యమైన బ్యాంకును సృష్టించడం సాధ్యం కావచ్చు, కానీ అది ఈ వ్యాసం పరిధికి మించినది.

తప్పుడు సమాచారం

డేటాను అభ్యర్థించినప్పుడు (opens in a new tab) తప్పుడు సమాచారాన్ని అందించడం ద్వారా సర్వర్ సమగ్రతను ఉల్లంఘించగల ఒక మార్గం.

దీన్ని పరిష్కరించడానికి, మేము ఖాతాలను ఒక ప్రైవేట్ ఇన్‌పుట్‌గా మరియు సమాచారం అభ్యర్థించబడిన చిరునామాను ఒక పబ్లిక్ ఇన్‌పుట్‌గా స్వీకరించే రెండవ Noir ప్రోగ్రామ్‌ను వ్రాయవచ్చు. అవుట్‌పుట్ ఆ చిరునామా యొక్క బ్యాలెన్స్ మరియు నాన్స్, మరియు ఖాతాల హాష్.

అయితే, ఈ రుజువును ఆన్‌చైన్‌లో ధృవీకరించలేము, ఎందుకంటే మేము నాన్స్‌లు మరియు బ్యాలెన్స్‌లను ఆన్‌చైన్‌లో పోస్ట్ చేయాలనుకోవడం లేదు. అయితే, దీనిని బ్రౌజర్‌లో నడుస్తున్న క్లయింట్ కోడ్ ద్వారా ధృవీకరించవచ్చు.

బలవంతపు లావాదేవీలు

L2లలో లభ్యతను నిర్ధారించడానికి మరియు సెన్సార్‌షిప్‌ను నివారించడానికి సాధారణ యంత్రాంగం బలవంతపు లావాదేవీలు (opens in a new tab). కానీ బలవంతపు లావాదేవీలు జీరో-కనౌలెడ్జి రుజువులతో కలపబడవు. సర్వర్ మాత్రమే లావాదేవీలను ధృవీకరించగల ఏకైక సంస్థ.

బలవంతపు లావాదేవీలను అంగీకరించడానికి మరియు అవి ప్రాసెస్ అయ్యే వరకు స్థితిని మార్చకుండా సర్వర్‌ను నిరోధించడానికి మేము smart-contracts/src/ZkBank.solను సవరించవచ్చు. అయితే, ఇది మాకు ఒక సాధారణ తిరస్కరణ-సేవ దాడికి గురి చేస్తుంది. ఒక బలవంతపు లావాదేవీ చెల్లనిది మరియు అందువల్ల ప్రాసెస్ చేయడం అసాధ్యం అయితే?

పరిష్కారం ఒక బలవంతపు లావాదేవీ చెల్లనిదని జీరో-కనౌలెడ్జి రుజువు కలిగి ఉండటం. ఇది సర్వర్‌కు మూడు ఎంపికలను ఇస్తుంది:

  • బలవంతపు లావాదేవీని ప్రాసెస్ చేయడం, అది ప్రాసెస్ చేయబడిందని మరియు కొత్త స్థితి హాష్ ఉందని జీరో-కనౌలెడ్జి రుజువును అందించడం.
  • బలవంతపు లావాదేవీని తిరస్కరించడం, మరియు లావాదేవీ చెల్లనిదని (తెలియని చిరునామా, చెడ్డ నాన్స్, లేదా తగినంత బ్యాలెన్స్ లేకపోవడం) కాంట్రాక్టుకు జీరో-కనౌలెడ్జి రుజువును అందించడం.
  • బలవంతపు లావాదేవీని విస్మరించడం. సర్వర్‌ను లావాదేవీని నిజంగా ప్రాసెస్ చేయమని బలవంతం చేయడానికి మార్గం లేదు, కానీ దీని అర్థం మొత్తం వ్యవస్థ అందుబాటులో లేదు.

లభ్యత బాండ్లు

నిజ-జీవిత అమలులో, సర్వర్‌ను నడుపుతూ ఉండటానికి బహుశా ఏదో ఒక రకమైన లాభ ప్రేరణ ఉంటుంది. నిర్దిష్ట కాలంలో బలవంతపు లావాదేవీ ప్రాసెస్ చేయబడకపోతే ఎవరైనా కాల్చగల లభ్యత బాండ్‌ను సర్వర్ పోస్ట్ చేయడం ద్వారా మేము ఈ ప్రోత్సాహాన్ని బలోపేతం చేయవచ్చు.

చెడ్డ Noir కోడ్

సాధారణంగా, ప్రజలు ఒక స్మార్ట్ కాంట్రాక్టును విశ్వసించేలా చేయడానికి మేము సోర్స్ కోడ్‌ను ఒక బ్లాక్ ఎక్స్‌ప్లోరర్‌కు (opens in a new tab) అప్‌లోడ్ చేస్తాము. అయితే, జీరో-కనౌలెడ్జి రుజువుల విషయంలో, అది సరిపోదు.

Verifier.sol ధృవీకరణ కీని కలిగి ఉంటుంది, ఇది Noir ప్రోగ్రామ్ యొక్క ఫంక్షన్. అయితే, ఆ కీ మాకు Noir ప్రోగ్రామ్ ఏమిటో చెప్పదు. నిజంగా విశ్వసనీయ పరిష్కారం కలిగి ఉండటానికి, మీరు Noir ప్రోగ్రామ్ (మరియు దానిని సృష్టించిన వెర్షన్)ను అప్‌లోడ్ చేయాలి. లేకపోతే, జీరో-కనౌలెడ్జి రుజువులు వేరే ప్రోగ్రామ్‌ను ప్రతిబింబించవచ్చు, ఒక వెనుక ద్వారం ఉన్నది.

బ్లాక్ ఎక్స్‌ప్లోరర్లు మాకు Noir ప్రోగ్రామ్‌లను అప్‌లోడ్ చేయడానికి మరియు ధృవీకరించడానికి అనుమతించే వరకు, మీరు దానిని మీరే చేయాలి (ప్రాధాన్యంగా IPFSకి). అప్పుడు అధునాతన వినియోగదారులు సోర్స్ కోడ్‌ను డౌన్‌లోడ్ చేసుకోగలరు, దానిని స్వయంగా కంపైల్ చేయగలరు, Verifier.solని సృష్టించగలరు మరియు అది ఆన్‌చైన్‌లో ఉన్న దానికి సమానంగా ఉందని ధృవీకరించగలరు.

ముగింపు

ప్లాస్మా-రకం అప్లికేషన్లకు సమాచార నిల్వగా ఒక కేంద్రీకృత భాగం అవసరం. ఇది సంభావ్య దుర్బలత్వాలను తెరుస్తుంది కానీ, ప్రతిఫలంగా, బ్లాక్ చైనులో అందుబాటులో లేని మార్గాలలో గోప్యతను కాపాడటానికి మనకు అనుమతిస్తుంది. జీరో-కనౌలెడ్జి రుజువులతో మనం సమగ్రతను నిర్ధారించవచ్చు మరియు కేంద్రీకృత భాగాన్ని నడుపుతున్న ఎవరికైనా లభ్యతను నిర్వహించడానికి ఆర్థికంగా ప్రయోజనకరంగా చేయవచ్చు.

నా మరిన్ని పనుల కోసం ఇక్కడ చూడండి (opens in a new tab).

ధన్యవాదాలు

  • జోష్ క్రైట్స్ ఈ వ్యాసం యొక్క డ్రాఫ్ట్‌ను చదివి, ఒక ముళ్లలాంటి Noir సమస్యతో నాకు సహాయం చేశారు.

మిగిలిన ఏవైనా లోపాలు నా బాధ్యత.

పేజీ చివరి నవీకరణ: 3 మార్చి, 2026

ఈ ట్యుటోరియల్ ఉపయోగకరంగా ఉందా?