Verificar firmas en Solidity
El proceso de firmas y verificación de estas firmas se usa mucho en web3. Cada vez que realizas una transacción, estás firmando esa transacción con tu llave privada. Todo esto funciona gracias al "algoritmo de firma digital de curva elíptica". No necesitas ser un experto en criptografía, pero es muy útil entender estos conceptos porque todas las plataformas donde construimos los smart contracts se basan en la criptografía.
¿Qué es una firma digital?
Una firma digital es una manera de demostrar la autenticidad y la integridad de un mensaje o documento digital. En el contexto de blockchain, una firma digital asegura que una transacción fue enviada por el propietario de una dirección específica y que el mensaje no ha sido alterado. El proceso se basa en dos claves: una clave pública que puede ser compartida, y una clave privada, que debe mantenerse secreta.
Proceso de Firma
Preparación del mensaje: se toma el mensaje o la transacción que se desea firmar.
Hash del mensaje: el mensaje se convierte en un hash utilizando una función hash criptográfica (en Ethereum, comúnmente
keccak256
).Firma del hash: el hash del mensaje se firma utilizando la clave privada del firmante. Esto produce una firma que consiste en tres componentes:
r
,s
yv
.Verificación: la firma y el mensaje original pueden ser utilizados para recuperar la clave pública del firmante mediante el hash del mensaje y los componentes:
r
,s
yv
.
Por qué se firma off-chain
A pesar de que puedes escribir en un smart contract el código para firmar mensajes, no tiene sentido hacerlo y nadie lo hace de esta manera. Este proceso se realiza off-chain (fuera de la cadena de bloques) por varias razones:
Seguridad: la firma requiere la llave privada, que debe mantenerse secreta y segura. Firmar mensajes on-chain implicaría exponer la llave privada, lo que comprometería la integridad del usuario o del proyecto.
Eficiencia: firmar mensajes off-chain es más eficiente y más rápido, ya que no requiere el procesamiento y almacenamiento en la blockchain.
Distintas librerías como web3.js
o ethers.js
te permiten realizar la firma interactuando directamente con la wallet del usuario, asegurando que la clave privada nunca salga de la wallet.
Proceso de Verificación
La verificación de una firma digital es crucial para asegurar la autenticidad de un mensaje. Este proceso se realiza con el mensaje y la clave pública (address) proporcionada por el usuario. Aquí se detallan los pasos para verificar una firma en Ethereum o cualquier blockchain EVM compatible:
Recuperar el Hash del mensaje: se calcula el hash del mensaje original.
Obtener el Hash del mensaje firmado: Ethereum añade un prefijo específico al hash del mensaje para evitar ataques de colisión y luego vuelve a hashear.
Recuperar el address del firmante: utilizando los componentes
r
,s
, yv
de la firma, junto con el hash del mensaje firmado se puede recuperar la dirección del firmante utilizando la funciónecrecover
en solidity.Comparar la dirección: finalmente, se compara la dirección recuperada con la dirección pública (address) proporcionada por el usuario. Si coinciden, se confirma que la dirección pública ha firmado el mensaje.
Casos de Uso
Las firmas digitales tienen numerosos casos de uso en el ecosistema blockchain. Aquí aprenderás algunos ejemplos:
Marketplace de NFTs
En un marketplace de NFTs, los usuarios pueden firmar ofertas para comprar o vender NFTs. El proceso típico es el siguiente:
El usuario crea una oferta: el usuario que desea comprar o vender una oferta ue incluye detalles como el precio, el NFT en cuestión, y la duración de la oferta.
Firma de la oferta: el usuario firma esta oferta utilizando su clave privada. Esta firma asegura que la oferta es genuina y proviene del usuario.
Verificación y Ejecución: cuando otro usuario acepta la oferta, el contrato inteligente del marketplace verifica la firma para asegurarse e que la oferta es válida y no ha sido alterada. Si la firma es válida, el contrato inteligente puede ejecutar la transacción, transfiriendo el NFT y los fondos de acuerdo con los términos de la oferta.
Contratos Inteligentes de Gobernanza
En los contratos inteligentes de gobernanza, los participantes pueden firmar votos o propuestas. La firma digital asegura que los votos son auténticos y provienen de las partes autorizadas.
Explicación código
Ahora que hemos cubierto los fundamentos, vamos a desglosar el código que verifica las firmas en Solidity. Este contrato permite verificar si un mensaje fue firmado por un determinado firmante al calcular y comparar los hashes y la firma proporcionada.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract Signature {
function verify(address _signer, string memory _message, bytes memory _sig) external pure returns(bool){
bytes32 messageHash = getMessageHash(_message);
bytes32 ethSignedMessageHash = getETHSignedMessageHash(messageHash);
return recover(ethSignedMessageHash, _sig) == _signer;
}
function getMessageHash(string memory _message) public pure returns(bytes32){
return keccak256(abi.encodePacked(_message));
}
function getETHSignedMessageHash(bytes32 _messageHash) public pure returns(bytes32){
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash
));
}
function recover(bytes32 _ethSignedMessageHash, bytes memory _sig) public pure returns (address){
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_sig);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
require(sig.length == 65, "invalid signature length");
r = bytesToBytes32(slice(sig, 0, 32));
s = bytesToBytes32(slice(sig, 32, 32));
v = uint8(sig[64]);
return (r, s, v);
}
function slice(bytes memory data, uint start, uint len) internal pure returns (bytes memory) {
bytes memory b = new bytes(len);
for (uint i = 0; i < len; i++) {
b[i] = data[i + start];
}
return b;
}
function bytesToBytes32(bytes memory b) internal pure returns (bytes32) {
bytes32 out;
for (uint i = 0; i < 32; i++) {
out |= bytes32(b[i] & 0xFF) >> (i * 8);
}
return out;
}
}
Desglose del Código
verify
: Esta función verifica si una firma fue hecha por un determinado firmante. Calcula el hash del mensaje, el hash del mensaje firmado por Ethereum y recupera la dirección del firmante a partir del hash del mensaje firmado y la firma.getMessageHash
: Esta función devuelve el hash del mensaje utilizando la funciónkeccak256
.getETHSignedMessageHash
: Devuelve el hash del mensaje firmado por Ethereum, que es el hash del prefijo y del hash del mensaje.recover
: Recupera la dirección del firmante a partir del hash del mensaje firmado y la firma.splitSignature
: Divide la firma en sus componentesr
,s
yv
sin usar ensamblador.
Nota: cuando leas mensaje firmado por Ethereum
me refiero a que Ethereum agrega un prefijo a las firmas como mencioné anteriormente.
Este contrato permite verificar firmas de manera segura y eficiente en solidity, asegurando que las transacciones y mensajes sean auténticos.
Video
El video donde aprenderás todo esto de manera práctica y sencilla estará en mi canal de youtube, no olvides seguirme.
Subscribe to my newsletter
Read articles from Gilberts Ahumada directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Gilberts Ahumada
Gilberts Ahumada
Blockchain Full-Stack Developer | Consultor Blockchain | Creador de contenido youtube.com/@gilbertsahumada