Lo que antes era especulación ahora es una realidad: la IA y blockchain son tecnologías con gran sinergia. Recientemente, los AI Agents han estado participando activamente en redes sociales y on-chain, realizando tareas como dar tips a usuarios, realizar swaps on-chain, y más.
Sin embargo, todavía existe un problema crítico: en el meta actual de los AI agents no es posible verificar la validez de la computación. Esto significa que el creador de un agent podría alterar o hacerse pasar por el modelo, de modo que los usuarios no puedan saber qué acciones realizó la IA o el creador. En este tutorial, exploraremos cómo Zero Knowledge, abreviada como ZK, puede ofrecer la experiencia más segura y fluida para todos en el espacio de los AI agents.
¿Porqué Zero Knowledge?
Zero Knowledge ofrece dos funcionalidades distintas: privacidad y escalabilidad.
Para este caso, nos centraremos en la segunda. Verificar directamente el cálculo de un modelo de Machine Learning (ML) on-chain es imposible por métodos normales, ya que resulta demasiado costoso y, en la mayoría de los casos, ni siquiera cabe en un bloque de Ethereum. En lugar de eso, realizaremos el cálculo del modelo de ML off-chain y utilizaremos ZK para generar una prueba de ese cálculo. Esta prueba luego puede ser verificada de manera económica en on-chain por un contrato inteligente en Solidity, garantizando confianza sin necesidad ejecutar la respuesta del IA on-chain.
Construyendo una AI Agent que dá tips verificables
En este artículo, crearemos un agente llamado Tipper que será capaz de enviar tips, propinas, únicamebte sí es capaz de demostrar que su modelo decidió hacerlo. decidir a quién enviar un tip basado en datos proporcionados, acomo una lista de comentarios en Farcaster, por ejemplo. Vamos a necesitar crear un contrato inteligente que actúe como una barrera de seguridad (guardrail), garantizando que el agente opere de manera autónoma sin ser controlado por su creador. Para lograr esto, utilizaremos EZKL, una herramienta para generar pruebas verificables de cálculos de ML.
En este tutorial, aprenderás a
- Crear un contrato verificador ZKML
- Generar una prueba ZK válida
- Lanzar un token de propinas sencillo
- Enviar propinas a usuarios cuando se envíe una prueba ZK válida
EZKL: Herramienta de ZKML para desarrolladores
EZKL ofrece Notebooks de Google Colab con el ambiente de desarrollo que necesitaremos. Usemos este Demo Sencillo.
Paso 0: Genera un modelo de AI
Existen muchas posibilidades en cuanto a lo que un agente es capaz de hacer con IA. Por ejemplo, puede decidir quién merece una propina basándose en quién tiene los mejores comentarios en Farcaster, o comprar un token en función de las condiciones del mercado, o también mintear un NFT según tus habilidades de canto.
En este ejemplo, lo mantendremos simple, generaremos un modelo que simplemente decida de manera determinista quién merece una propina de 1000
tokens a una dirección proporcionada.
Comencemos reemplazando el primer bloque proporcionado en Google Colab con lo siguiente. Además, reemplaza tip_recipient_address
con una billetera de tu elección; esta billetera recibirá los tokens que el agente de IA decida enviar como propina.
# Verifica si el notebook está en Colab
try:
# Instala ezkl y onnx si estás en Colab
import google.colab
import subprocess
import sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "ezkl"])
subprocess.check_call([sys.executable, "-m", "pip", "install", "onnx"])
except:
pass # Si no, usa tu setup de ezkl local
# Importa las librería necesarias
from torch import nn
import torch
import ezkl
import os
# Define el address que recibirá el tip
tip_recipient_address = 0x707e55a12557E89915D121932F83dEeEf09E5d70
# Convierte la dirección al formato que EZKL espera
def address_to_byte_tensor(address):
address_hex = f"{address:040x}"
return torch.tensor([int(address_hex[i:i+2], 16) for i in range(0, len(address_hex), 2)], dtype=torch.uint8)
tip_recipient_tensor = address_to_byte_tensor(tip_recipient_address)
print(f"Tip Recipient Tensor: {tip_recipient_tensor}")
# Define un modelo que genera como salida el address que recibirá el tip
class FixedHexAddressModel(nn.Module):
def __init__(self, address_tensor):
super(FixedHexAddressModel, self).__init__()
self.output_value = address_tensor
def forward(self, x):
# Ignora los inputs y siempre devuelve una dirección fija
batch_size = x.size(0)
return self.output_value.repeat(batch_size, 1) # Repeat for batch size
# Instancia el modelo
circuit = FixedHexAddressModel(tip_recipient_tensor)
# Input ejemplo
dummy_input = torch.ones(1, 1) # Dummy input (not used by the model)
# Genera el output
output = circuit(dummy_input)
print(f"Output (Hex Address): {output}")
# Exporta el model en el formato de Onnx
torch.onnx.export(
circuit,
dummy_input,
"fixed_hex_address_model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch_size"}, "output": {0: "batch_size"}}
)
print("Model saved as 'fixed_hex_address_model.onnx'!")
Paso 1: Crea un Contrato Verificador
A continuación, ejecuta todos los demás snippets en Google Colab. Esto generará un circuito ZK que se puede verificar off-chain. Ahora queremos poder verificar on-chain, para hacerlo, ejecuta el siguiente snippet. Esto imprimirá un contrato verificador de Solidity completamente funcional.
# Define el archivo donde se generará el ABI y el código Solidity
abi_path = 'test.abi'
sol_code_path = 'test.sol'
# Crea el código Solidity del verificador basado en el circuito previamente generado
res = await ezkl.create_evm_verifier(
vk_path,
settings_path,
sol_code_path,
abi_path,
)
assert res == True
# Imprime el código Solidity en la consola
with open(sol_code_path, 'r') as sol_file:
sol_content = sol_file.read()
print(sol_content)
El siguiente paso es lanzar este contrato en la cadena de tu elección. Ten en cuenta que deben habilitarse las optimizaciones del compilador para compilar correctamente el contrato. Esto es necesario porque el contrato generado es bastante grande y requiere optimizaciónes.
Paso 2: Genera una prueba ZK
A continuación, genera la prueba ZK y encodéala en el formato que espera el contrato verificador.
Ejecuta el siguiente snippet y la prueba será impresa la pantalla.
# Define el archivo dónde se generará el calldata en formato JSON
calldata = 'calldata.proof'
# Encodea la prueba en el formato esperado por el contrato verificador
res = ezkl.encode_evm_calldata(
proof_path,
calldata,
)
# Opcionalmente, imprime los datos JSON
# print(res)
# Convierte la prueba de formato JSON a bytes hexadecimales
calldata_hex = "0x" + ''.join(f"{byte:02x}" for byte in res)
print(calldata_hex)
EZKL genera pruebas ZK como calldata de bajo nivel que se envían al contrato verificador. Esto significa que la prueba debe enviarse utilizando el opcode CALL
en EVM en lugar de cómo normalmente llamamos a otro contrato en Solidity. Más sobre esto en el Paso 4.
Paso 3: Lanza el Token de Propinas
Lanza un token ERC20 sencillo con el que el agente de IA podrá interactuar. Despliega el siguiente contrato que te minteará algunos tokens.
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.22;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Contrato ERC20 sencillo creado con el wizzard de OpenZeppelin
// Simple ERC20 token contract created from the OpenZeppelin Wizzard
contract MyToken is ERC20 {
constructor() ERC20("Tip Token", "TIPT") {
// Mint 21M tokens to the deployer
_mint(msg.sender, 21_000_000 ether);
}
}
Paso 4: Construye el contrato Tipper
El contrato Tipper asegura que los tips, o propinas, solo se transfieran cuando se envíe una prueba ZK válida. Lanza el siguiente contrato reemplazando HALO2_VERIFIER
y TIP_TOKEN
con las direcciones de los contratos que acabas de desplegar. Luego, transfiere al menos 1000
Tip Tokens a este contrato.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Interface that contains the function that verifies the proof on
// the Halo2 verifier generated automatically by EZKL
// SPDX-License-Identifier: GPL-3.0
// Interfaz que contiene la función que verifica la prueba en el verificador Halo2 generado automáticamente por EZKL
interface Halo2Verifier {
function verifyProof(bytes calldata proof, uint256[] calldata instances) external view;
}
// En este ejemplo, solo usaremos la función ERC20 transfer del Token Tip para enviar 1000 tokens cuando se envíe una prueba de tipping valida
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
}
// El contrato Tipper actúa como barrera de seguridad, o guardrail, para el agente de IA, solo permite realizar una acción si se envía una prueba ZKML válida
contract Tipper {
// Reemplaza las siguientes dos direcciones con los contratos que acabas de lanzar
address HALO2_VERIFIER = 0x84fBBc680F4aB4240e8cF6C488aEca03bfF91d2E;
address TIP_TOKEN = 0x84fBBc680F4aB4240e8cF6C488aEca03bfF91d2E;
// Envía el tip al usuario solo si se envía una prueba ZKML válida
function verifyAndSendTip(bytes memory proofCalldata) public {
// La verificación se realiza pasando los bytes de calldata previamente generados al contrato verificador de Halo2
(bool success, bytes memory data) = HALO2_VERIFIER.call(proofCalldata);
// El verificador de EZKL devuelve 1 si la prueba es válida
if(success && data.length == 32 && uint8(data[31]) == 1)
// Transfiere 1000 tokens al usuario
// Nota que los parámetros sensibles como el destinatario deben ser generados por la prueba ZK para prevenir ataques de frontrunning: una vez que la transacción está en la mempool, si información es enviada como parametros normales, alguien más podría pagar extra gas para que se procese su transacción robando tu transacción. Por eso encodeados y decodeamos el address del destinatario en la prueba
IERC20(TIP_TOKEN).transfer(extractTipRecipientFromProof(proofCalldata), 1_000 ether);
else
revert("Could not verify proof");
}
// Las pruebas ZK tienen parámetros públicos integrados en la prueba
// En este caso, extraemos la dirección del destinatario del tip de la prueba y la convertimos al formato de dirección de Solidity
function extractTipRecipientFromProof(bytes memory proofCalldata) internal pure returns (address) {
bytes memory lastBytes = new bytes(20);
for (uint i = 0; i < 20; i++) {
lastBytes[20-1-i] = proofCalldata[proofCalldata.length - (1+i*32)];
}
return address(bytes20(abi.encodePacked(lastBytes)));
}
}
Ahora llama a verifyAndSendTip
enviando la prueba como parámetro en el formato de calldata, y una vez que la transacción se confirme, se otorgarán 1000
tokens al address que recibe la propina.
Próximos pasos
Al integrar la tecnología ZK, podemos crear agentes de IA seguros y autónomos que operen de manera transparente on-chain. Los próximos pasos implican profundizar en el aprendizaje ZK donde, dependiendo lo que construyas, ocuparás conocer sobre nullificadores, relayers y firmas de wallets de Ethereum. Además necesitarás entrenar un modelo de IA capaz de realizar tareas novedosas e interesantes. Para comenzar con ZK, te recomiendo mi curso completo de ZK, y para comenzar con el entrenamiento de modelos de ML, te recomiendo este curso con ejercicios interactivos.
Top comments (0)