Votos anónimos usando ZK [ZK ES Semana 4]
Published: 2023-06-16 19:27:28

---
title: Votos anónimos usando ZK [ZK ES Semana 4]
published: true
description: 
tags: 
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-04-27 22:28 +0000
---

{% youtube UBmKLABVmvE %}

En esta guía, crearemos un sistema de votación on-chain seguro y anónimo utilizando el lenguaje Noir de Aztec. Generaremos un merkle tree off-chain que contendrá los votantes autorizados. Votaremos en un contrato inteligente de Solidity que solo permitirá votos si verifica que el voto fué emitido por un miembro del merkle tree.

**Antes de comenzar**

Para este tutorial necesitarás [Metamask](https://metamask.io/) o cualquier otra billetera evm con fondos en Sepolia que puedes conseguir desde el [faucet](https://sepoliafaucet.com/). También necesitarás nargo `v0.6.0`. Que puedes encontrar las instrucciones de cómo instalarlo desde el [sitio web de Noir](https://noir-lang.org/dev/getting_started/nargo_installation).

## Paso 1: Hashea las hojas del merkle tree

Cada usuario será representado por una hoja en el merkle Tree. Pero el contenido de este árbol será público y es importante proteger las llaves privadas de los usuarios. Para lograr la privacidad y seguridad, cada usuario transformará sus llaves privadas en llaves públicas mediante una función hash, de manera que el merkle tree pueda ser almacenado on-chain sin exponer información sensible.

Iniciemos creando un nuevo proyecto:

```bash
nargo new zk_voting
cd zk_voting
```

Ahora cambiamos el circuito default por nuestro generador de hojas.

`src/main.nr`
```rust
use dep::std;

fn main(priv_key : Field, secret : Field) {
    let note_commitment = std::hash::pedersen([priv_key, secret]);

    std::println("Public leaf:");
    std::println(note_commitment);
}
```

Para verificar que todo está en orden y para crear un par de archivos extra ejecutamos el siguiente comando.

```
nargo check
```

Ejecutaremos este circuito tres veces, una vez por cada clave privada de los usuarios. Utilizaremos `1`, `2` y `3` como claves privadas, y `9` como un valor secreto. Este valor secreto ayudará a hacer las pruebas más seguras.

Para ejecutar el cicuito, estableceremos los parámetros en el archivo `Prover.toml` y luego ejecutaremos el comando `prove` utilizando `--show-output` para habilitar la visualización de los logs de consola:

`Prover.toml`
```
priv_key = "1"
secret = "9"
```

```bash
nargo prove p --show-output
```

Después de ejecutar el circuito tres veces (una vez para cada llave privada), obtendrás los nodos hoja resultantes del merkle tree que han sido hasheados.

```
0x1053e87bed5f2a4f5144b386c5403701212f4b3c21cb14683b6ebdfed16c854d
0x1eff4379fbc748b53a07ce5961c24eccdfeb8f17154490d2e41c6096a9519329
0x0e0adf2416341b3d868d88043127be8e6965f3758ce6a80d11a494fe21b0cdff
```

## Step 2: Construye el merkle tree

Una vez que tengamos los nodos hoja, generaremos el árbol de merkle, rama por rama, hasta llegar a la raíz. Por ejemplo, se realiza un hash entre la llave pública del usuario 1 y el usuario 2, luego se realiza un hash de esa rama con el usuario 3 para obtener la raíz. Este proceso se repite hasta construir todo el árbol.

`src/main.nr`
```rust
use dep::std;

fn main(index : Field, left_leaf : Field, right_path : [Field; 1]) {
    let root = std::merkle::compute_root_from_leaf(left_leaf, index, right_path);
    std::println(root);
}
```

Por ejemplo para construir la rama intermedia entre el votador `1` y `2` usamos los siguientes parametros.

```toml
index = "0"
left_leaf = "0x1053e87bed5f2a4f5144b386c5403701212f4b3c21cb14683b6ebdfed16c854d"
right_path = ["0x1eff4379fbc748b53a07ce5961c24eccdfeb8f17154490d2e41c6096a9519329"]

```

Una vez que hayas ejecutado el circuito anterior para cada rama y la raíz final, el árbol de merkle resultante será el siguiente:

```
└─ 0x2959dc1151fec22361702924ec0edcc117824d77c275044462e8a91fd1d707a5
   ├─ 0x0f76fbce0c43859cb1aaceedf9948995a1671979f522cb49886a9180a93cc3e1
   │  ├─ 0x1053e87bed5f2a4f5144b386c5403701212f4b3c21cb14683b6ebdfed16c854d
   │  └─ 0x1eff4379fbc748b53a07ce5961c24eccdfeb8f17154490d2e41c6096a9519329
   └─ 0x0e0adf2416341b3d868d88043127be8e6965f3758ce6a80d11a494fe21b0cdff
      └─ 0x0e0adf2416341b3d868d88043127be8e6965f3758ce6a80d11a494fe21b0cdff
```

## Paso 3: Verifica un voto

Para generar un voto, deberás proporcionar tanto la prueba de membrecía al merkle tree como las variables de votación que se pasarás como parámetro al contrato inteligente.


`src/main.nr`
```rust
use dep::std;

fn main(root : pub Field,
  index : Field,
  hash_path : [Field; 2],
  secret: Field, priv_key: Field,
  proposalId: pub Field,
  vote: pub Field) -> pub Field
{
    let note_commitment = std::hash::pedersen([priv_key, secret]);
    let nullifier = std::hash::pedersen([root, priv_key, proposalId]);

    let is_member = std::merkle::check_membership(root, note_commitment[0], index, hash_path);
    assert(is_member == 1);

    nullifier[0]
}
```

Antes de generar la prueba, generamos el smart contract verificador corriendo lo siguiente.

```
nargo codegen-verifier
```

Ahora lanzamos el contrato con nombre `UltraVerifier`.

Luego importamos el smart contract verificador en el siguiente contrato verificador de votos y lo lanzamos pasando por parámetro el merkle root y el address del contrato verificador que recién lanzamos.

```solidity
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

contract zkVoting {

    struct Proposal {
        string description;
        uint deadline;
        uint forVotes;
        uint againstVotes;
    }

    UltraVerifier ultraVerifier;

    bytes32 merkleRoot;
    uint proposalCount;
    mapping (uint proposalId => Proposal) public proposals;
    mapping (bytes32 nullifier => bool isNullified) public nullifiers;

    constructor(bytes32 _merkleRoot, address ultraVerifierAddress) {
        merkleRoot = _merkleRoot;
        ultraVerifier = UltraVerifier(ultraVerifierAddress);
    }

    function propose(string memory description, uint deadline) public {
        proposals[proposalCount] = Proposal(description, deadline, 0, 0);
        proposalCount += 1;
    }

    function castVote(bytes calldata _proof, bytes32[] calldata _publicInputs) public {
        require(ultraVerifier.verify(_proof, _publicInputs) ==  true, "Invalid proof");
        bytes32 merkleRootPublicInput = _publicInputs[0];
        uint proposalId = uint(_publicInputs[1]);
        uint vote = uint(_publicInputs[2]);
        bytes32 nullifier = _publicInputs[3];

        require(block.timestamp < proposals[proposalId].deadline, "Voting period is over");
        require(merkleRoot == merkleRootPublicInput, "Invalid merke root");
        require(!nullifiers[nullifier], "Vote already casted");

        nullifiers[nullifier] = true;

        if(vote == 1)
            proposals[proposalId].forVotes += 1;
        else if (vote == 2)
            proposals[proposalId].againstVotes += 1;
    }
}
```

Puedes crear una nueva propuesta on-chain ejecutando la función `propose` pasando por parámetro el nombre de la propuesta y el `deadline` que representa el timestamp máximo para emitir votos. Esto creará propuesta con id `0`.

Ahora podemos votar siempre y cuando se tengamos las llaves secretas `1`, `2` o `3`.

Por ejemplo podríamos generar un voto con los siguientes parámetros en el archivo `Prover.toml`. Donde votamos a favor de la primera propuesta, o sea la propuesta con id `0`. En el smart contract definimos que `1` representa un voto a favor y `0` en contra.

`Prover.toml`
```toml
hash_path = ["0x1eff4379fbc748b53a07ce5961c24eccdfeb8f17154490d2e41c6096a9519329", "0x0e0adf2416341b3d868d88043127be8e6965f3758ce6a80d11a494fe21b0cdff"]
index = "0"
priv_key = "1"
proposalId = "0"
root = "0x2959dc1151fec22361702924ec0edcc117824d77c275044462e8a91fd1d707a5"
secret = "9"
vote = "1"
```

Ejecutamos el comando siguiente para generar una prueba.

```bash
nargo prove p
```

Luego pasar los siguientes parámetros en el smart contract para enviar un voto on-chain.

`Proof` que la puedes encontrar en el archivo `proofs/p.proof`:
```
0x288eaef700174c5422d28af8bcfacf026a5006bda0581bd6b4c82b2744a12c8d0a8db1b1f8417ed8887ab6b3d0bc0ace81db7d0ff974c54b8af13456534ea566291e719e9c1f2c4d907e21ebd0b79fb5b68ddbeab6fa111707aaf9db0f9b3e9416ddf9e599cee1249e4f7e348bea27dde30e2c2264c1fea8668343d4d09e57371c7216da157adffe541adbe3bc5bb68306ca2ebee0067e818244a5a2d816b86a28136ff7765fc47abf876bad9e105e9d58df3f1371d2f86091478ebb870c24b907236843df6bce1cc1bfaa139ab1fefc1270a6c905985be506eeb8e1708a2b580cba2d24a1f43daad1f44e48eb77ef6d6829e2f9289240bb51404dfd221ad49723f154de83eca3b3626ffae700f95a823315159cfd028a6a870fbe4284d351f813a0494c43928fe87514e90413a0e2d67e6a3404297834910f0df15de1e0566724e0bc05d055a73751992a9195aba4b1957d201b97d59b4a62e515f25ce7627f2b81ac780dc2208da1975b8030b605cf7b3ec2d9069cde4e49ee15c9e059cd2b2f42962fbadedd7b6c0e0ebb643497834ae93837e3b2a253faa4146d3c229b6c305fbc31667a0dc5e27dae91c3b9fed66da4f173c4a622d50a1c9984899f1b5002374df34f2b26e8378cdbd4ba9f3c8f55881181c4dce221b8e55019e4a9e16f1ae7180d99eab0a729d3f55d46477d0b6e62e7348018b5e6fadd21dd1f4ab100072dccfc7920da2aa406bfe38c6438ff49bd8f586c64581e650532b22d9c16e91991e4bed48eec89bbacda343f3b936d444ade5dc00e6e6f24737ba891e7a06e20ca03b19c65672a535d94a7ddc054ca3f39a9a213951809611bdc81f5fb3b74047c0c591c9e5e2cdc550f9ff62a6cc023be6a59e0a79b0f01f2b232497c36be118ad6589fdc9483edb3defb2ff98ce33c501afb798ae741e92b0afd9ef462981009ef57f6402b93d2c109d378aac8fa4b4c476f6002c23feaa038d0bbabd67e1430072a272d60be721f48b84ec0896644aed86037f86505de857210850d8d250646bc1d2b592496b9ce9532c3101f9b8b502443e5723d79465c10d9917f7a3703fb99201015db091e4277a0b679d35f0c988afbea347c9fd68a283ca41f78bb282dd413843517865f0b5282f925ecc33558d538664d1ec519286e0a9f0b1cca1cbbe59ae99e7c7ae2336f031b812750c737db73fd8a92484d4a234d897d56c41503a05f33a160c5fc4d2623787f1dca151a9f990a1057750c950933b14fbc0d2082e4528dff1d5f8f16fa368cb5d051e2ac00822c2b582dc0857de914eeb0522b162e22cca454596eab7bca7783fe5dfec979ef2fd11c4e048472f99b756ef22f60c267275458196aa17af575adeef431965b550329483ca9669632840772231b10782731259b7a5101e69058f18d4269ffdb87ac16b4bcfc921f89ca68a0fe11dfdb80409df108c5a79095692432e3e11d68bfb7e14995803db8bdae092770132107e0852e6fe31b62a68e96f4261cfb3cc114bee2d6a53d5f552772899b8b1025eb70611e9ffad793ad9989e52a85c1677697715651edc91c28f00083569609be44efe62238d6f4c02ccb48402d0c52db9c42283139f01fd68ba1811c06ed02652329779604c13e0de369535dba83fad4c5c487926fcbcb77393e407f941729a997a422da8847bb111e7d7cdfb3850986465c6ee52c3538ab27da9e1b57a309164f4331228d0fab4bc35d64b6d3197ebf59db58707970dd83b54e2fad30a4008d247beaf4e06bd7de54c3e4125625f3f4b963bf31c1b4cfb41fbe00a7e9ff00134ed427d2c7c682cf3cc287e8403afee1070e0ed6e41823fff9a71c1e0ef80c04820c214773783a3cf049fdd6392788ad116729c42c871f2b4fde5c419afe1b8a4197101d35331a24228de5442d8676e8bbe16dd48d8b6b97f4a6795a3f53263159c72ad8ba9423bd8e946d35ce45204b86f0fc6af45ed8be1f2cd9b7dda30098649d5ce100f52ea3f178ae6b70dafe486f0982b42a43b32aa8b5828b8c57282e13c37b415be9afdf365c7803700a8d6d745776fa09a5f1f3ef883a5d0d252e4612c272abbe15dbb51034d8355c4d4a23e1b2ab0cc0a6ab7368965d83d1da26a24f9e5ca23a4d8523286f13948690665c40cc53edbcc19c65c644362cb074270b9cd5d2c3b835ab8f163be2e03894c230f9276e48ff94ec7e7cea6e149752109a8e780a40af4184c40b37fb931ce8f9cfefad8794ebe31478ef2c17c7b3ca22da6c24298ba7be1afa7469512e20b947ea4b95349428a220c9b35ffbd53333149467a5282448c19ba67aa6c1a47125621ce45f20f1adb447f4de44514ab40f09dbf95e962630fcaf3eacec1f8297dd0ae434e0638a5f60cbfd638a72016fd214930023b82d9a64737e47ea78afd6b8ae0d959a016cb18dc09c287b2552b50b134bd02da3757a0fa66c5be43971c5b7b709f7c8320f083690d0335b6df8c80d1dc6b83f39a4453ea8ddc89c537192fbd25b7bf6cc5816d57470503c4c49b10b2f6cb0c681a587a992b7bb2634e361f5a1ddda4bd4e5730d801b1f65139e4aec2a34b339d4ddec7beb7b5aaf15ae941ace555dacb88faf2dc04507d8b854fb4f1e7503afe9a06ee11bcea41b57c6bc0b545983f969b39b273437e2fb42a0b96b239fcf0798a6662b86374c63fad7f1f41fc5612ded9e780f649ae6cf6ddca9590954f3f27b86e32a6aa1780d10609dea9751434e562709ec19abfce395422f5a225f0de059e27d4f0d5e3c7576bdf98b97ae1343051cc7cd71d6ddfb06235c9028240f64061d1c5c5a489b1f4e565a96cd0f450ea5ebcd9ddb9797f3f105136d286dd91278a081b75e877b4f0fbec0e65ba0104512142c3874273cd0472dab3e207ab8ad5c196a8fbbc5f5bf11560dc76c27cfb708cba70acb2ec10608bab3781d806f6d45f781e1882d9f52f4217e82783d8faf0cbd58f16aef43c74130aa8e26748a0279e6143d4be0846ca4b04707aacdb6ba57e4a125491c67bcfeed37e4
```

`Public params` que puedes encontrar en el archivo `Verifier.toml` (toma en cuenta que debes usar el formato siguiente, donde pasas un arreglo que contiene los parámetros en el orden que están en el circuito: root, proposalId, vote, return):
```toml
["0x2959dc1151fec22361702924ec0edcc117824d77c275044462e8a91fd1d707a5","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001","0x0d45003dbf41a4cf37055cb9a03552f20a9aa750b9a746aa0c6c724f74333b4c"]
```

## Recursos adicionales

* Noir by example: https://noir-by-example.org/
* ERecover en Noir: https://github.com/colinnielsen/ecrecover-noir

## Para la siguiente semana

En la 5ta semana de este workshop daremos un vistazo en general sobre temas de actualidad en ZK. Y también haremos un ejercicio práctico donde veremos cómo funciona Halo2, la tecnología base que usa Scroll y la PSE para escalar Ethereum.

Para la siguiente semana asegúrate de traer instalado lo siguiente:

* Rust
* Dependencias:
  * Si estás usando Linux también trae instalado CMake y los build essencials