Árbol de Merkle en Go: Implementación con Genéricos y Prueba de Inclusión
En sistemas con millones de registros, como las blockchains, verificar la presencia de un elemento específico normalmente requiere transmitir todo el conjunto de datos. El Árbol de Merkle resuelve este problema: en lugar de un millón de transacciones, solo se necesitan O(log N) hashes—aproximadamente 20 para un millón de elementos. La estructura se construye como un árbol binario, donde cada nodo aplica una función hash a sus hijos, y la raíz resume todo el conjunto.
Ejemplo: transacciones A, B, C, D. Aplicar hash directamente a todo el conjunto requiere que el cliente tenga todos los datos para la verificación. En el árbol, el cliente recibe los hashes vecinos: H(B), H(CD)—y reconstruye de forma independiente el hash raíz desde la hoja A hasta la raíz.
Interfaces Básicas y Tipos de Nodo
Comienza con una interfaz para todos los nodos del árbol, compatible con genéricos:
type Node[T any] interface {
String() string
StringIndent(level int) string
AddChild(Node[T])
GetBytes() []byte
GetChildren() []Node[T]
}
Las hojas almacenan el valor y su hash. Para serializar valores de cualquier tipo, usa CBOR—un formato determinista (RFC 8949), a diferencia de JSON que tiene un orden de claves inestable en los mapas.
type Leaf[T any] struct {
Value T
ValueHash []byte
}
func NewLeaf[T any](value T, hash hash.Hash) (*Leaf[T], error) {
hashedValue, err := valueToHash(value, hash)
if err != nil {
return nil, err
}
return &Leaf[T]{Value: value, ValueHash: hashedValue}, nil
}
func valueToHash(value any, hash hash.Hash) ([]byte, error) {
encoded, err := cbor.Marshal(value)
if err != nil {
return nil, err
}
hash.Reset()
hash.Write(encoded)
return hash.Sum(nil), nil
}
Los nodos binarios agregan los hijos izquierdo y derecho:
type BinaryNode[T any] struct {
Value []byte
Right Node[T]
Left Node[T]
}
Construcción del Árbol con una Fábrica de Funciones Hash
El árbol es gestionado por una estructura con una fábrica para crear instancias limpias de hash—hash.Hash tiene estado, así que pasa una función:
type BinaryTree[T any] struct {
newHash func() hash.Hash
}
func NewBinaryTree[T any](newHash func() hash.Hash) *BinaryTree[T] {
return &BinaryTree[T]{newHash: newHash}
}
// Uso
tree := NewBinaryTree[string]
Construcción: las hojas aplican hash a los valores, los nodos internos aplican hash a la concatenación de los hashes de los hijos. H(AB) = SHA256(H(A) + H(B)). La raíz es el hash final de todo el árbol.
Prueba de Merkle: Generación y Verificación
La prueba de inclusión es un mecanismo clave. El cliente conoce el hash objetivo (H(A)) y el hash raíz. El servidor proporciona los hashes vecinos a lo largo de la ruta desde la hoja hasta la raíz.
Algoritmo de Generación
- Desde la raíz, desciende hasta la hoja objetivo.
- En cada nivel, guarda el hash del nodo hermano (izquierdo o derecho, que no forma parte de la ruta).
- La prueba es una lista de estos hashes.
Verificación de abajo hacia arriba:
- Comienza con H(A).
- Aplica hash secuencialmente con los vecinos, alternando lados (izquierdo/derecho).
- Llega a la raíz—una coincidencia confirma la inclusión.
Para 2^20 elementos (un millón), la prueba son 20 hashes de 32 bytes cada uno, ~640 bytes.
Ventajas y Aplicaciones
- Complejidad logarítmica: O(log N) para la prueba, ideal para clientes SPV.
- Tráfico mínimo: Bitcoin SPV verifica transacciones sin el bloque completo (500+ GB).
Aplicaciones:
- Bitcoin: Raíz de transacciones en la cabecera del bloque.
- Ethereum: Trie de estado, trie de transacciones, trie de recibos.
- Git: Objetos de árbol en los commits.
- IPFS: Hash de contenido como raíz de fragmentos.
- Certificate Transparency: Auditoría de registros de certificados SSL.
Conclusiones Clave
- El Árbol de Merkle prueba la inclusión en O(log N) sin revelar otros datos.
- Usa genéricos en Go para seguridad de tipos; CBOR para aplicar hash determinista a cualquier tipo.
- La fábrica de funciones hash evita condiciones de carrera del estado de hash.Hash.
- La prueba se genera a partir de los hashes hermanos a lo largo de la ruta desde la hoja hasta la raíz.
- Eficiente para sistemas distribuidos: blockchains, almacenamiento P2P.
— Editorial Team
Aún no hay comentarios.