WEB3DEV Español

Hector
Hector

Posted on

Creando una Lista Blanca Usando el Árbol de Merkle en Solidity

Este artículo es una traducción de Shubham Yadam, hecha por Héctor Botero. Puedes encontrar el artículo original aquí.
Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.
Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Introducción: Un Árbol de Merkle, también conocido como el árbol de hash, es una estructura de datos que se utiliza comúnmente en criptografía y ciencias de la computación. Su nombre se debe a Ralph Merkle, quien inventó el concepto a finales de la década de los 70.

El árbol de Merkle se construye usando una estructura jerárquica de nodos, donde los nodos de la hoja representan los datos reales, y los nodos intermedios son creados aplicando una función hash a la concatenación de sus nodos hijos. El proceso continúa hasta obtener un único nodo raíz, conocido como la raíz de Merkle.

Image description

Para que entiendas mejor

Paso 1: Configurar el Proyecto

Crea un nuevo directorio para tu proyecto y navega hasta él usando una interfaz de línea de comandos.

Inicializa un nuevo proyecto npm ejecutando:

npm init -y
Enter fullscreen mode Exit fullscreen mode

Instala keccak256, merkletreejs y fs ejecutando:

npm install keccak256 merkletreejs fs
Enter fullscreen mode Exit fullscreen mode

Paso 2: Implementa un Árbol de Merkle

Crea un nuevo archivo (es decir, MerkleTree.js) e importa keccak256 desde keccak256 y MerkelTree desde merkletreejs


const keccak256 = require("keccak256");
const { default: MerkleTree } = require("merkletreejs");

Enter fullscreen mode Exit fullscreen mode

Luego de importar crea un Array de Direcciones de Ethereum:

const address = [
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
];

Enter fullscreen mode Exit fullscreen mode

Usé tres direcciones de Ethereum para blanquear esta dirección. En vez de estas, puedes usar la dirección que quieres blanquear.

Ahora, Hashea Todas las Hojas Individuales y Construye el Árbol de Merkle

// Hashea Todas las Hojas Individuales
const leaves = address.map((leaf) => keccak256(leaf));

// Construye el Árbol de Merkle
const tree = new MerkleTree(leaves, keccak256, {
sortPairs: true,
});

Enter fullscreen mode Exit fullscreen mode

Ahora, escribe una Función de Utilidad para convertir desde el Buffer al Hex y registra para obtener la Raíz del Árbol de Merkle

// Función de Utilidad para convertir desde el Buffer al Hex
const buf2Hex = (x) => "0x" + x.toString("hex");

// Obtén la Raíz del Árbol de Merkle
console.log(`Here is Root Hash: ${buf2Hex(tree.getRoot())}`);

Enter fullscreen mode Exit fullscreen mode

Ahora, crea un objeto a blanquear que contenga cada dirección del usuario para blanquear, hoja y la prueba del blanqueo.

let data = [];

// Empuja toda la prueba y hoja en un array de datos
address.forEach((address) => {
const leaf = keccak256(address);

const proof = tree.getProof(leaf);

let tempData = [];

proof.map((x) => tempData.push(buf2Hex(x.data)));

data.push({
address: address,
leaf: buf2Hex(leaf),
proof: tempData,
});
});

// Crea un WhiteList Object para escribir el archivo JSON

let whiteList = {
whiteList: data,
};

Enter fullscreen mode Exit fullscreen mode

Ahora importa fs desde fs para escribir un archivo .json para todos los usuarios que están en la lista blanca.

const fs = require("fs");

Enter fullscreen mode Exit fullscreen mode

Ahora escribe un archivo whiteList.json en la raíz del directorio:

// Stringify el objeto whiteList y formatea
const metadata = JSON.stringify(whiteList, null, 2);

// Escribe un archivo whiteList.json en la raíz del directorio
fs.writeFile(`whiteList.json`, metadata, (err) => {
if (err) {
throw err;
}
});

Enter fullscreen mode Exit fullscreen mode

Ahora junta todo el código.

Aquí está el MerkleTree.js actualizado

const keccak256 = require("keccak256");
const { default: MerkleTree } = require("merkletreejs");
const fs = require("fs");

const address = [
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
];

// Hashea Todas las Hojas Individuales
const leaves = address.map((leaf) => keccak256(leaf));

// Construye el Árbol de Merkle
const tree = new MerkleTree(leaves, keccak256, {
sortPairs: true,
});

// Función de Utilidad para convertir desde el Buffer al Hex
const buf2Hex = (x) => "0x" + x.toString("hex");

// Obtén la Raíz del Árbol de Merkle
console.log(`Here is Root Hash: ${buf2Hex(tree.getRoot())}`);

let data = [];

// Empuja toda la prueba y hoja en un array de datos
address.forEach((address) => {
const leaf = keccak256(address);

const proof = tree.getProof(leaf);

let tempData = [];

proof.map((x) => tempData.push(buf2Hex(x.data)));

data.push({
address: address,
leaf: buf2Hex(leaf),
proof: tempData,
});
});

// Crea un WhiteList Object para escribir el archivo JSON

let whiteList = {
whiteList: data,
};

// Stringify el objeto whiteList y formatea
const metadata = JSON.stringify(whiteList, null, 2);

// Escribe un archivo whiteList.json en la raíz del directorio
fs.writeFile(`whiteList.json`, metadata, (err) => {
if (err) {
throw err;
}
});


Enter fullscreen mode Exit fullscreen mode

Guarda el archivo y ejecútalo.

node merkleTree.js

Enter fullscreen mode Exit fullscreen mode

Generará una raíz del hash y un archivo whiteList.json

Image description

Y .json como:

{
"whiteList": [
{
"address": "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"leaf": "0x04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54",
"proof": [
"0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
"0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229"
]
},
{
"address": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"leaf": "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
"proof": [
"0x04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54",
"0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229"
]
},
{
"address": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"leaf": "0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229",
"proof": [
"0x39a01635c6a38f8beb0adde454f205fffbb2157797bf1980f8f93a5f70c9f8e6"
]
}
]
}
Enter fullscreen mode Exit fullscreen mode

En este Json, puedes ver la dirección, hoja y prueba de Merkel.

Paso 3: Integra el Árbol de Merkle en el Contrato Inteligente de Solidity

Ahora estamos creando un contra contrato inteligente y sólo el usuario blanqueado puede incrementar el conteo. Para crear un contrato inteligente abre RemixIDE en el navegador https://remix.ethereum.org/

Crea un nuevo archivo (es decir, MerkleTreeWhiteListCounter.sol) y importa MerkleProof.sol desde la biblioteca pública de openzeppelin

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
Enter fullscreen mode Exit fullscreen mode

Ahora crea un contrato con el nombre MerkleTreeWhiteListCount y define un constructor con entradas de bytes32, este es tu Merkel Tree RootHash e inicialízalo es una variable de estado y también define una variable de conteo.

contract MerkleTreeWhiteListCounter {
bytes32 public rootHash;
uint256 public count;

constructor(bytes32 _rootHash){
rootHash = _rootHash;
}
Enter fullscreen mode Exit fullscreen mode

Ahora crea una función con el nombre isValidProof y asegúrate que sea definido con una función privada. Esta función verificará si la prueba es válida o no. Esto tomará que la primera entrada sea MerkleProof y la segunda sea la hoja

function isValidProof(bytes32[] calldata proof, bytes32 leaf) private view returns (bool) {
return MerkleProof.verify(proof, rootHash, leaf);
}
Enter fullscreen mode Exit fullscreen mode

Esto crea un modificador con el nombre isWhiteListedAddress

modifier isWhiteListedAddress(bytes32[] calldata proof){
require(isValidProof(proof,keccak256(abi.encodePacked(msg.sender))),"Not WhiteListed Address");
_;
}
Enter fullscreen mode Exit fullscreen mode

Este modificador tomará una entrada que es MerkleProof y llamará internamente a la función IsValidProof, pasará la prueba y generará una hoja usando keccak256 y pasará (msg.sender) para asegurarse que pase (msg.sender) si pasa directamente alguna dirección, significa que ya no es para los usuarios blanqueados porque cualquiera puede venir y llamar a la función de blanquear usando la prueba de Merkle y la dirección de otro usuario.

Ahora, crea una función que sea llamada sólo por los usuarios blanqueados y usa el modificador isWhiteListedAddress. Solo necesitas pasar el MerkleProof sin necesidad de pasar la hoja. La hoja generará usando keccak256 usando msg.sender

function whiteListIncrement(bytes32[] calldata proof) isWhiteListedAddress(proof) external {
count++;
}
Enter fullscreen mode Exit fullscreen mode

Ahora ejecuta todo el código.

Aquí está el MerkleTreeWhiteListCounter.sol actualizado.

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.18;
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract MerkleTreeWhiteListCounter {
bytes32 public rootHash;
uint256 public count;

constructor(bytes32 _rootHash){
rootHash = _rootHash;
}

modifier isWhiteListedAddress(bytes32[] calldata proof){
require(isValidProof(proof,keccak256(abi.encodePacked(msg.sender))),"Not WhiteListed Address");
_;
}

function isValidProof(bytes32[] calldata proof, bytes32 leaf) private view returns (bool) {
return MerkleProof.verify(proof, rootHash, leaf);
}

function whiteListIncrement(bytes32[] calldata proof) isWhiteListedAddress(proof) external {
count++;
}
}
Enter fullscreen mode Exit fullscreen mode

Guarda y despliega el contrato inteligente MerkleTreeWhiteListCounter usando RemixIDE y pasa MerkleTree RootHash al constructor.

Image description

Despliega el contrato, verás la dirección del contrato abajo:

Image description

Ahora invoca la función whiteListIncrement con la prueba válida y usa la dirección blanqueada para llamar a esto

Image description

Y puedes ejecutar tu transacción exitosamente.

Y si no usas una prueba o dirección válida, te mostrará un error

Image description

Conclusión: el árbol de Merkle es una estructura de datos jerárquicos que es usado ampliamente en la criptografía y ciencias de la computación. Proporciona una verificación eficiente de la integridad de los datos, seguridad contra la manipulación y almacenamiento eficiente de grandes conjuntos de datos. Los árboles de Merkle son utilizados en varias aplicaciones, incluyendo la tecnología blockchain, donde juegan un rol crucial para garantizar la inmutabilidad y confiabilidad de las transacciones y los datos.

Discussion (0)