Inicio / Blockchain / Blockchain: De Cero a Desarrollador / Criptografía Fundamental

Criptografía Fundamental

SHA-256, Keccak-256, árboles Merkle, ECDSA y firmas digitales.

Principiante

Criptografía Fundamental

La criptografía es el pilar sobre el que se construye toda la tecnología blockchain. Sin ella, no sería posible garantizar la integridad de los datos, la autenticidad de las transacciones ni la seguridad de los fondos. En esta lección exploraremos los conceptos criptográficos esenciales que hacen funcionar blockchain.

Funciones Hash

Una función hash toma una entrada de cualquier tamaño y produce una salida de tamaño fijo llamada hash o digest. Las funciones hash criptográficas tienen propiedades especiales:

  • Determinista: la misma entrada siempre produce la misma salida.
  • Rápida: calcular el hash es computacionalmente eficiente.
  • Efecto avalancha: un cambio mínimo en la entrada produce un hash completamente diferente.
  • Resistencia a preimagen: dado un hash, es inviable encontrar la entrada original.
  • Resistencia a colisiones: es inviable encontrar dos entradas con el mismo hash.
Entrada: "Hola Mundo"
SHA-256: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

Entrada: "Hola Mundo!"  (solo agregamos !)
SHA-256: 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069

→ ¡Completamente diferente!

SHA-256

SHA-256 (Secure Hash Algorithm 256-bit) es la función hash utilizada por Bitcoin. Produce un hash de 256 bits (32 bytes), representado como 64 caracteres hexadecimales:

// Ejemplo conceptual de hashing
const crypto = require('crypto');

function sha256(data) {
    return crypto.createHash('sha256').update(data).digest('hex');
}

console.log(sha256("bloque 1")); 
// → "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b"

console.log(sha256("bloque 2"));
// → completamente diferente

Keccak-256

Ethereum utiliza Keccak-256 (a veces llamado SHA-3) en lugar de SHA-256. Es la función hash usada para generar direcciones, firmar transacciones y en los smart contracts:

// En Solidity
bytes32 hash = keccak256(abi.encodePacked("Hola Blockchain"));

Árboles Merkle

Un árbol Merkle es una estructura de datos que permite verificar eficientemente la integridad de grandes conjuntos de datos. Cada nodo hoja es el hash de un dato, y cada nodo interno es el hash de la concatenación de sus hijos:

          Merkle Root
         /           \
    Hash(AB)       Hash(CD)
    /     \        /     \
 Hash(A) Hash(B) Hash(C) Hash(D)
    |       |       |       |
   Tx A    Tx B    Tx C    Tx D

¿Por qué son importantes?

  • Verificación eficiente: para probar que una transacción está en un bloque, solo necesitas O(log n) hashes, no todas las transacciones.
  • Merkle Proofs: los nodos ligeros pueden verificar transacciones sin descargar el bloque completo.
  • Detección de tampering: cualquier modificación en una transacción cambia todo el camino hasta la raíz.
Para verificar Tx B en un bloque con 4 transacciones:
Solo necesitas: Hash(A), Hash(CD) y Merkle Root

1. Calculas Hash(B)
2. Combinas con Hash(A) → Hash(AB)
3. Combinas con Hash(CD) → Merkle Root
4. Comparas con el Merkle Root del bloque

Criptografía de Clave Pública

La criptografía asimétrica o de clave pública utiliza un par de claves matemáticamente relacionadas:

  • Clave privada: un número secreto aleatorio de 256 bits. Solo tú lo conoces.
  • Clave pública: derivada matemáticamente de la clave privada. Se puede compartir libremente.
Clave Privada (256 bits) → Curva Elíptica → Clave Pública → Hash → Dirección

Ejemplo:
Privada: 0x4c0883a69102937d6231471b5dbb6204fe512961708279...
Pública: 0x04bfcab25a8e012c56d1bc...
Dirección: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD09

Curvas Elípticas (ECDSA)

Blockchain utiliza criptografía de curvas elípticas, específicamente la curva secp256k1. La operación clave es la multiplicación escalar de un punto en la curva:

Clave Pública = Clave Privada × G (punto generador)

Propiedades:
✓ Fácil: calcular la pública desde la privada
✗ Imposible: deducir la privada desde la pública

Es computacionalmente fácil multiplicar un punto por un escalar (generar la clave pública), pero es prácticamente imposible hacer la operación inversa (obtener la clave privada a partir de la pública). Esta es la base de la seguridad en blockchain.

Firmas Digitales

Las firmas digitales permiten demostrar que una transacción fue autorizada por el dueño de la clave privada, sin revelar dicha clave:

Firmar:
1. Crear el hash de la transacción
2. Firmar el hash con la clave privada
3. Se produce una firma (r, s, v)

Verificar:
1. Tomar la transacción y la firma
2. Recuperar la clave pública del firmante
3. Verificar que corresponde al remitente

Proceso de firma en blockchain

Transacción: {from: 0xA, to: 0xB, value: 1 ETH}
         │
    Hash de la tx
         │
  Firma con clave privada
         │
    Firma (r, s, v)
         │
  Cualquiera puede verificar
  usando la clave pública

Encoding y Hashing de Direcciones

Las direcciones en Ethereum se derivan de la clave pública:

1. Generar clave privada (256 bits aleatorios)
2. Calcular clave pública con secp256k1 (punto en la curva)
3. Tomar el hash Keccak-256 de la clave pública
4. Tomar los últimos 20 bytes (40 caracteres hex)
5. Agregar prefijo 0x
6. Aplicar checksum EIP-55 (mayúsculas/minúsculas)

Resultado: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD09

Resumen

La criptografía proporciona las tres garantías fundamentales de blockchain: integridad (funciones hash), verificabilidad (árboles Merkle) y autenticidad (firmas digitales con curvas elípticas). Comprender estos conceptos es esencial para desarrollar aplicaciones seguras en blockchain.

Ejercicio de práctica

Hashing y verificación criptográfica

Implementa funciones de hashing y verificación usando la librería crypto de Node.js.

// sha256(data) → retornar el hash SHA-256 en hexadecimal
// sha256("hola") → string hexadecimal de 64 caracteres

// verifyHash(data, expectedHash) → comparar hash calculado con esperado
// Retorna true si coinciden, false si no

// simpleMerkleRoot(transactions) → calcular raíz Merkle simplificada
// Recibe array de strings, hashea cada uno, y combina en pares
// hasta obtener un solo hash (la raíz)
// Si hay número impar, duplicar el último