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.
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
Instala keccak256, merkletreejs y fs ejecutando:
npm install keccak256 merkletreejs fs
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");
Luego de importar crea un Array de Direcciones de Ethereum:
const address = [
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
];
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,
});
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())}`);
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,
};
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");
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;
}
});
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;
}
});
Guarda el archivo y ejecútalo.
node merkleTree.js
Generará una raíz del hash y un archivo whiteList.json
Y .json como:
{
"whiteList": [
{
"address": "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db",
"leaf": "0x04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54",
"proof": [
"0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
"0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229"
]
},
{
"address": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"leaf": "0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb",
"proof": [
"0x04a10bfd00977f54cc3450c9b25c9b3a502a089eba0097ba35fc33c4ea5fcb54",
"0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229"
]
},
{
"address": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"leaf": "0x5931b4ed56ace4c46b68524cb5bcbf4195f1bbaacbe5228fbd090546c88dd229",
"proof": [
"0x39a01635c6a38f8beb0adde454f205fffbb2157797bf1980f8f93a5f70c9f8e6"
]
}
]
}
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";
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;
}
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);
}
Esto crea un modificador con el nombre isWhiteListedAddress
modifier isWhiteListedAddress(bytes32[] calldata proof){
require(isValidProof(proof,keccak256(abi.encodePacked(msg.sender))),"Not WhiteListed Address");
_;
}
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++;
}
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++;
}
}
Guarda y despliega el contrato inteligente MerkleTreeWhiteListCounter usando RemixIDE y pasa MerkleTree RootHash al constructor.
Despliega el contrato, verás la dirección del contrato abajo:
Ahora invoca la función whiteListIncrement con la prueba válida y usa la dirección blanqueada para llamar a esto
Y puedes ejecutar tu transacción exitosamente.
Y si no usas una prueba o dirección válida, te mostrará un error
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)