Interfaces con privacidad en Solidity y zk-WASM
Published: 2024-06-25 20:08:14

---
title: Interfaces con privacidad en Solidity y zk-WASM
published: true
description: 
tags: 
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-06-14 03:08 +0000
---

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.

![diagrama aplicacion zk](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/768ne4jea2p858udb07z.png)
<p><small><em>Para mantener los parámetros privados, estos nunca deben salir de nuestro navegador</em></small></p>

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

Para este ejemplo usaremos circom, si no lo tienes instalado puedes hacerlo con los comandos a continuación.

```bash
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
git clone https://github.com/iden3/circom.git
cd circom
cargo build --release
cargo install --path circom
npm install -g snarkjs
```

## 1. Crea un circuito

Haremos un ejemplo muy sencillo: generar una prueba de computación de una multiplicación `a*b=c` pero manteniendo privados `a` y `b`. Si te interesa un ejemplo más avanzado con un caso de uso real visita [mi artículo anterior](https://dev.to/turupawn/smart-contracts-privados-con-solidity-y-circom-3a8h).

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`

```js
pragma circom 2.0.0;

template Multiplier() {
    signal input a;
    signal input b;
    signal output c;
    c <== a*b;
 }

 component main = Multiplier();
```

Ahora compílalo y genera los artefactos que usaremos más adelante.

```bash
circom myCircuit.circom --r1cs --wasm --sym
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
snarkjs powersoftau prepare phase2 pot12_0001.ptau pot12_final.ptau -v
snarkjs groth16 setup myCircuit.r1cs pot12_final.ptau myCircuit_0000.zkey
snarkjs zkey contribute myCircuit_0000.zkey myCircuit_0001.zkey --name="1st Contributor Name" -v
snarkjs zkey export verificationkey myCircuit_0001.zkey verification_key.json
```

## 2. Lanza los contratos

El siguiente comando generará un contrato verificador en el archivo de `verifier.sol`. Lánzalo en un blockchain de preferencia. Este contrato contiene la función `verifyProof()` que recibe por parámetro una prueba de computación hecha con nuestro circuito y devuelve `true` 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.

```bash
snarkjs zkey export solidityverifier myCircuit_0001.zkey verifier.sol
```

Ahora lanza el siguiente contrato de _ló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.

```javascript
// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface ICircomVerifier {
    function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) external view returns (bool);
}

contract CircomCustomLogic {
    ICircomVerifier circomVerifier;
    uint public publicInput;

    constructor(address circomVeriferAddress) {
        circomVerifier = ICircomVerifier(circomVeriferAddress);
    }

    function sendProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public {
        // ZK verification
        circomVerifier.verifyProof(_pA, _pB, _pC, _pubSignals);

        // Your custom logic
        publicInput = _pubSignals[0];
    }
}
```

## 3. Construye un frontend

Ahora crea la siguiente estructura de archivos:

```
js/
  blockchain_stuff.js
  snarkjs.min.js
json_abi/
  MyContract.json
zk_artifacts/
  myCircuit_final.zkey
  myCircuit.wasm
  verification_key.json
index.html
```

* `js/snarkjs.min.js`: descarga [este archivo](https://github.com/iden3/snarkjs/releases/tag/v0.7.4) que contiene la librería de snark.js
* `json_abi/MyContract.json`: el ABI del contrato `CircomCustomLogic` 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 de `myCircuit_0002.zkey` por `myCircuit_final.zkey`
* `index.html` y `js/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`
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <input id="connect_button" type="button" value="Connect" onclick="connectWallet()" style="display: none"></input>
  <p id="account_address" style="display: none"></p>
  <p id="web3_message"></p>
  <p id="contract_state"></p>
  <input type="input"  value="" id="a"></input>
  <input type="input"  value="" id="b"></input>
  <input type="button" value="Send Proof" onclick="_sendProof()"></input>
  <br>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.3.5/web3.min.js"></script>
  <script type="text/javascript" src="js/blockchain_stuff.js"></script>
  <script type="text/javascript" src="js/snarkjs.min.js"></script>
</body>
</html>

<script>
  function _sendProof()
  {
    a = document.getElementById("a").value
    b = document.getElementById("b").value
    sendProof(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`
```js
const NETWORK_ID = 534351

const MY_CONTRACT_ADDRESS = "0xFdAFc996a60bC5fEB307AAF81b1eD0A34a954F06"
const MY_CONTRACT_ABI_PATH = "./json_abi/MyContract.json"
var my_contract

var accounts
var web3

function metamaskReloadCallback() {
  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()
  })
}

const getWeb3 = async () => {
  return new Promise((resolve, reject) => {
    if(document.readyState=="complete")
    {
      if (window.ethereum) {
        const web3 = new Web3(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) {
          const web3 = new Web3(window.ethereum)
          resolve(web3)
        } else {
          reject("must install MetaMask")
          document.getElementById("web3_message").textContent="Error: Please install Metamask";
        }
      });
    }
  });
};

const getContract = async (web3, address, abi_path) => {
  const response = await fetch(abi_path);
  const data = await response.json();

  const netId = await web3.eth.net.getId();
  contract = new web3.eth.Contract(
    data,
    address
    );
  return contract
}

async function loadDapp() {
  metamaskReloadCallback()
  document.getElementById("web3_message").textContent="Please connect to Metamask"
  var awaitWeb3 = async function () {
    web3 = await getWeb3()
    web3.eth.net.getId((err, netId) => {
      if (netId == NETWORK_ID) {
        var awaitContract = async function () {
          my_contract = await getContract(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 = _accounts
            if (err != null)
            {
              console.error("An error occurred: "+err)
            } else if (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();
}

async function connectWallet() {
  await window.ethereum.request({ method: "eth_requestAccounts" })
  accounts = await web3.eth.getAccounts()
  onWalletConnectedCallback()
}

loadDapp()

const onContractInitCallback = async () => {
  var publicInput = await my_contract.methods.publicInput().call()
  var contract_state = "Public input: " + publicInput
  document.getElementById("contract_state").textContent = contract_state;
}

const onWalletConnectedCallback = async () => {
}


//// Functions ////

const sendProof = async (a, b) => {
  document.getElementById("web3_message").textContent="Generating proof...";

  const { proof, publicSignals } = await snarkjs.groth16.fullProve( { a: a, b: b}, "../zk_artifacts/myCircuit.wasm", "../zk_artifacts/myCircuit_final.zkey");

  const vkey = await fetch("../zk_artifacts/verification_key.json").then( function(res) {
    return res.json();
  });

  const res = await snarkjs.groth16.verify(vkey, publicSignals, proof);

  pA = proof.pi_a
  pA.pop()
  pB = proof.pi_b
  pB.pop()
  pC = proof.pi_c
  pC.pop()

  document.getElementById("web3_message").textContent="Proof generated please confirm transaction.";

  const result = await my_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 variables `NETWORK_ID` y `MY_CONTRACT_ADDRESS` en `js/blockchain_stuff.js`. `NETWORK_ID` es el identificador único de la chain que estés usando, en este ejemplo estoy usando `534351` que representa a Scroll Sepolia Testnet, si deseas usar otro te recomiendo buscar el identificador en [chainlist](https://chainlist.org/). Por otro coloca el address del contrato `CircomCustomLogic` que recién lanzaste en la variable `MY_CONTRACT_ADDRESS`.

Ahora estás listo para probar la aplicación en cualquier servidor web. Usualmente yo uso `lite-server` para desarrollar, así lo instalas y levantas un servidor, solo asegúrate de estar en la carpeta del proyecto:

```bash
npm install -g lite-server #para instalar
lite-server #para levantar el servidor
```

![ejemplo de circuito multiplicador zk](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a24eo00jc3rbfxe8bgpx.png)
<html><small><em>Una vez todo esté listo, tu aplicación debería de verse de esta manera</em></small></html>

## ¿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:

* [Documentación oficial de circom](https://docs.circom.io/)
* [Solidity en 15 minutos](https://dev.to/turupawn/como-lanzar-un-dex-paso-a-pasox-2cnm)
* [Playlist de introducción a desarrollo en ZK](https://www.youtube.com/watch?v=gLXlIuEafo4&list=PLhTCvqIxiz5yj1ysnVo4jlYzS2BoF7NPI)

**¡Gracias por leer esta guía!**

Sígueme en dev.to y en [Youtube](https://www.youtube.com/channel/UCNRB4tgwp09z4391JRjEsRA) para todo lo relacionado al desarrollo en Blockchain en Español.