Volver al inicio

Árbol Merkle en Go: genéricos y prueba

El artículo describe la implementación de Árbol Merkle en Go usando genéricos. Cubre interfaces de nodos, CBOR para hashing, generación de prueba de inclusión. Aplicación en Bitcoin, Ethereum, Git.

Construye Árbol Merkle en Go desde cero: código y prueba
Advertisement 728x90

Á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:

Google AdInline article slot
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:

Google AdInline article slot
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:

Google AdInline article slot
  • 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

Advertisement 728x90

Leer después