Los usuarios de blockchain necesitan privacidad en sus finanzas, identidad, redes sociales y más. Pero web3 es transparente y público. Entonces, ¿cómo pueden los usuarios proteger su anonimato en un ambiente con estas características?
La clave es crear pruebas de computación en un lugar donde únicamente el usuario tiene acceso, donde los datos del usuario estén seguros. Ese lugar es precisamente el navegador, antes que los datos del usuarios toquen el internet. A esto le llamamos generación de pruebas a nivel de cliente, "client side proving", o "browser proving" en inglés.
Para mantener los parámetros privados, estos nunca deben salir de nuestro navegador
Conozcamos, con un ejemplo práctico y sencillo, cómo crear interfaces que hagan uso de zk-wasm, la tecnología que hace esto posible.
Dependencias
Si aún no lo tienes, instala NPM, yo estaré usando la versión 20.
curl-o-https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash nvm install 20
Para este ejemplo usaremos circom, si no lo tienes instalado puedes hacerlo con los comandos a continuación.
curl--proto'=https'--tlsv1.2 https://sh.rustup.rs-sSf | shgit clone https://github.com/iden3/circom.gitcdcircomcargo build--releasecargoinstall--path circomnpminstall-g snarkjs
1. Crea un circuito
Haremos un ejemplo muy sencillo: generar una prueba de computación de una multiplicacióna*b=c
pero manteniendo privadosa
yb
. Si te interesa un ejemplo más avanzado con un caso de uso real visitami artículo anterior.
Circom nos permite crear circuitos, que permiten generar pruebas de ejecución ofuscando los parámetros.
Comienza creando el circuito a continuación.
myCircuit.circom
pragmacircom2.0.0;templateMultiplier(){signalinputa;signalinputb;signaloutputc;c<==a*b;}componentmain=Multiplier();
Ahora compílalo y genera los artefactos que usaremos más adelante.
circom myCircuit.circom--r1cs--wasm--symsnarkjs powersoftau new bn128 12 pot12_0000.ptau-vsnarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau--name="First contribution"-vsnarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau-vsnarkjs groth16 setup myCircuit.r1cs pot12_final.ptau myCircuit_0000.zkeysnarkjs zkey contribute myCircuit_0000.zkey myCircuit_0001.zkey--name="1st Contributor Name"-vsnarkjs zkeyexportverificationkey myCircuit_0001.zkey verification_key.json
2. Lanza los contratos
El siguiente comando generará un contrato verificador en el archivo deverifier.sol
. Lánzalo en un blockchain de preferencia. Este contrato contiene la funciónverifyProof()
que recibe por parámetro una prueba de computación hecha con nuestro circuito y devuelvetrue
si la prueba es correcta.
Nota: Este contrato es compatible con L1 EVMs, L2 optimistas, pero en términos de L2 ZK, actualmente únicamente es compatible con Scroll.
snarkjs zkeyexportsolidityverifier myCircuit_0001.zkey verifier.sol
Ahora lanza el siguiente contrato delógica personalizada pasando como parámetro de constructor el address del contrato verificador que lanzamos anteriormente. En este contrato puedes agregar la lógica en solidity que desees, por ejemplo el conteo de votos de un sistema de votación o la recepción o envío de tokens ERC20 en un sistema DeFi anónimo. En este ejemplo únicamente almacenaremos el resultado de la multiplicación que hicimos en nuestro circuito.
// SPDX-License-Identifier: MITpragmasolidity>=0.7.0<0.9.0;interfaceICircomVerifier{functionverifyProof(uint[2]calldata_pA,uint[2][2]calldata_pB,uint[2]calldata_pC,uint[1]calldata_pubSignals)externalviewreturns(bool);}contractCircomCustomLogic{ICircomVerifiercircomVerifier;uintpublicpublicInput;constructor(addresscircomVeriferAddress){circomVerifier=ICircomVerifier(circomVeriferAddress);}functionsendProof(uint[2]calldata_pA,uint[2][2]calldata_pB,uint[2]calldata_pC,uint[1]calldata_pubSignals)public{// ZK verificationcircomVerifier.verifyProof(_pA,_pB,_pC,_pubSignals);// Your custom logicpublicInput=_pubSignals[0];}}
3. Construye un frontend
Ahora crea la siguiente estructura de archivos:
js/ blockchain_stuff.js snarkjs.min.jsjson_abi/ MyContract.jsonzk_artifacts/ myCircuit_final.zkey myCircuit.wasm verification_key.jsonindex.html
js/snarkjs.min.js
: descargaeste archivo que contiene la librería de snark.jsjson_abi/MyContract.json
: el ABI del contratoCircomCustomLogic
que recién lanzamos, por ejemplo en Remix, lo puedes hacer dando clic en el botón "ABI" en la pestaña de compilación.zk_artifacts
: coloca en esta carpeta los artefactos generados anteriormente. Nota: Cambia el nombre demyCircuit_0002.zkey
pormyCircuit_final.zkey
index.html
yjs/blockchain_stuff.js
los detallo a continuación
El archivo HTML a continuación describe la interfaz gráfica donde colocaremos los números a multiplicar. En un ambiente de producción recomendaría usar un frontend framework como react, vue o angular. Este ejemplo es con fines didácticos.
index.html
<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"></head><body><inputid="connect_button"type="button"value="Connect"onclick="connectWallet()"style="display: none"></input><pid="account_address"style="display: none"></p><pid="web3_message"></p><pid="contract_state"></p><inputtype="input"value=""id="a"></input><inputtype="input"value=""id="b"></input><inputtype="button"value="Send Proof"onclick="_sendProof()"></input><br><scripttype="text/javascript"src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js"></script><scripttype="text/javascript"src="js/blockchain_stuff.js"></script><scripttype="text/javascript"src="js/snarkjs.min.js"></script></body></html><script>function_sendProof(){a=document.getElementById("a").valueb=document.getElementById("b").valuesendProof(a,b)}</script>
Nuestro archivo de javascript contiene tanto la lógica de generación de pruebas zk usando la librería snark.js como la lógica de blockchain usando la librería web3.js. En un ambiente de producción recomendaría usar typescript y no javascript vainilla, este ejemplo es con fines didácticos.
js/blockchain_stuff.js
constNETWORK_ID=534351constMY_CONTRACT_ADDRESS="0xFdAFc996a60bC5fEB307AAF81b1eD0A34a954F06"constMY_CONTRACT_ABI_PATH="./json_abi/MyContract.json"varmy_contractvaraccountsvarweb3functionmetamaskReloadCallback(){window.ethereum.on('accountsChanged',(accounts)=>{document.getElementById("web3_message").textContent="Se cambió el account, refrescando...";window.location.reload()})window.ethereum.on('networkChanged',(accounts)=>{document.getElementById("web3_message").textContent="Se el network, refrescando...";window.location.reload()})}constgetWeb3=async()=>{returnnewPromise((resolve,reject)=>{if(document.readyState=="complete"){if(window.ethereum){constweb3=newWeb3(window.ethereum)window.location.reload()resolve(web3)}else{reject("must install MetaMask")document.getElementById("web3_message").textContent="Error: Porfavor conéctate a Metamask";}}else{window.addEventListener("load",async()=>{if(window.ethereum){constweb3=newWeb3(window.ethereum)resolve(web3)}else{reject("must install MetaMask")document.getElementById("web3_message").textContent="Error: Please install Metamask";}});}});};constgetContract=async(web3,address,abi_path)=>{constresponse=awaitfetch(abi_path);constdata=awaitresponse.json();constnetId=awaitweb3.eth.net.getId();contract=newweb3.eth.Contract(data,address);returncontract}asyncfunctionloadDapp(){metamaskReloadCallback()document.getElementById("web3_message").textContent="Please connect to Metamask"varawaitWeb3=asyncfunction(){web3=awaitgetWeb3()web3.eth.net.getId((err,netId)=>{if(netId==NETWORK_ID){varawaitContract=asyncfunction(){my_contract=awaitgetContract(web3,MY_CONTRACT_ADDRESS,MY_CONTRACT_ABI_PATH)document.getElementById("web3_message").textContent="You are connected to Metamask"onContractInitCallback()web3.eth.getAccounts(function(err,_accounts){accounts=_accountsif(err!=null){console.error("An error occurred:"+err)}elseif(accounts.length>0){onWalletConnectedCallback()document.getElementById("account_address").style.display="block"}else{document.getElementById("connect_button").style.display="block"}});};awaitContract();}else{document.getElementById("web3_message").textContent="Please connect to Scroll Testnet";}});};awaitWeb3();}asyncfunctionconnectWallet(){awaitwindow.ethereum.request({method:"eth_requestAccounts"})accounts=awaitweb3.eth.getAccounts()onWalletConnectedCallback()}loadDapp()constonContractInitCallback=async()=>{varpublicInput=awaitmy_contract.methods.publicInput().call()varcontract_state="Public input:"+publicInputdocument.getElementById("contract_state").textContent=contract_state;}constonWalletConnectedCallback=async()=>{}//// Functions ////constsendProof=async(a,b)=>{document.getElementById("web3_message").textContent="Generating proof...";const{proof,publicSignals}=awaitsnarkjs.groth16.fullProve({a:a,b:b},"../zk_artifacts/myCircuit.wasm","../zk_artifacts/myCircuit_final.zkey");constvkey=awaitfetch("../zk_artifacts/verification_key.json").then(function(res){returnres.json();});constres=awaitsnarkjs.groth16.verify(vkey,publicSignals,proof);pA=proof.pi_apA.pop()pB=proof.pi_bpB.pop()pC=proof.pi_cpC.pop()document.getElementById("web3_message").textContent="Proof generated please confirm transaction.";constresult=awaitmy_contract.methods.sendProof(pA,pB,pC,publicSignals).send({from:accounts[0],gas:0,value:0}).on('transactionHash',function(hash){document.getElementById("web3_message").textContent="Executing...";}).on('receipt',function(receipt){document.getElementById("web3_message").textContent="Success.";}).catch((revertReason)=>{console.log("ERROR! Transaction reverted:"+revertReason.receipt.transactionHash)});}
4. Prueba la aplicación
Antes de probar, debes adaptar las variablesNETWORK_ID
yMY_CONTRACT_ADDRESS
enjs/blockchain_stuff.js
.NETWORK_ID
es el identificador único de la chain que estés usando, en este ejemplo estoy usando534351
que representa a Scroll Sepolia Testnet, si deseas usar otro te recomiendo buscar el identificador enchainlist. Por otro coloca el address del contratoCircomCustomLogic
que recién lanzaste en la variableMY_CONTRACT_ADDRESS
.
Ahora estás listo para probar la aplicación en cualquier servidor web. Usualmente yo usolite-server
para desarrollar, así lo instalas y levantas un servidor, solo asegúrate de estar en la carpeta del proyecto:
npminstall-g lite-server#para instalarlite-server#para levantar el servidor
Una vez todo esté listo, tu aplicación debería de verse de esta manera
¿Qué más necesito aprender para desarrollar una zkDapp?
Para desarrollar una aplicación descentralizada y anónima necesitarás una combinación de conocimientos en Circom, Solidity y programación web. Dependiendo de los casos de uso también ocuparás un poco de programación backend en el caso de necesitar un Relayer. Estaré creando guías sobre estos temas así que te invito a que te suscribas.
También te dejo un par de materiales de aprendizaje como siguiente paso:
¡Gracias por leer esta guía!
Sígueme en dev.to y enYoutube para todo lo relacionado al desarrollo en Blockchain en Español.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse