Powrót do strony głównej

Merkle Tree w Go: generics i proof

Artykuł opisuje implementację Merkle Tree w Go z użyciem generics. Omówiono interfejsy węzłów, CBOR do hashowania, generowanie proof of inclusion. Zastosowanie w Bitcoin, Ethereum, Git.

Zbuduj Merkle Tree w Go od zera: kod i proof
Advertisement 728x90

Drzewo Merkle w Go: implementacja z generykami i dowód przynależności

W systemach z milionami rekordów, takich jak blockchainy, weryfikacja obecności konkretnego elementu wymagałaby przesłania całego zbioru danych. Drzewo Merkle rozwiązuje ten problem: zamiast miliona transakcji wystarczy O(log N) skrótów — około 20 dla miliona elementów. Struktura jest budowana jako drzewo binarne, gdzie każdy węzeł skrótuje swoich potomków, a korzeń sumuje cały zbiór.

Przykład: transakcje A, B, C, D. Bezpośredni skrót całego zbioru wymagałby od klienta wszystkich danych do weryfikacji. W drzewie klient otrzymuje sąsiednie skróty: H(B), H(CD) — i samodzielnie odtwarza skrót korzenia od liścia A do korzenia.

Podstawowe interfejsy i typy węzłów

Zacznij od interfejsu dla wszystkich węzłów drzewa, wspierającego generyki:

Google AdInline article slot
type Node[T any] interface {
    String() string
    StringIndent(level int) string
    AddChild(Node[T])
    GetBytes() []byte
    GetChildren() []Node[T]
}

Liście przechowują wartość i jej skrót. Do serializacji typu any użyj CBOR — format deterministyczny (RFC 8949), w przeciwieństwie do JSON z niestabilnym porządkiem kluczy w mapie.

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
}

Węzły binarne agregują lewe i prawe potomki:

type BinaryNode[T any] struct {
    Value []byte
    Right Node[T]
    Left  Node[T]
}

Budowanie drzewa z fabryką funkcji skrótu

Drzewem zarządza struktura z fabryką do tworzenia czystych instancji skrótu — hash.Hash ma stan, więc przekazuj funkcję:

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}
}

// Użycie
tree := NewBinaryTree[string](sha256.New)

Budowanie: liście skrótują wartości, wewnętrzne węzły — konkatenację skrótów potomków. H(AB) = SHA256(H(A) + H(B)). Korzeń — finalny skrót całego drzewa.

Dowód Merkle: generacja i weryfikacja

Dowód przynależności — kluczowy mechanizm. Klient zna skrót celu (H(A)) i skrót korzenia. Serwer dostarcza skróty sąsiadów na ścieżce od liścia do korzenia.

Algorytm generacji

  • Od korzenia zejdź do docelowego liścia.
  • Na każdym poziomie zapisz skrót węzła-sąsiada (lewego lub prawego, niebędącego na ścieżce).
  • Dowód — lista tych skrótów.

Weryfikacja od dołu do góry:

Google AdInline article slot
  • Zacznij od H(A).
  • Sekwencyjnie skrótuj z sąsiadami, naprzemiennie strony (lewy/prawy).
  • Osiągnij korzeń — zgodność potwierdza przynależność.

Dla 2^20 elementów (milion) dowód — 20 skrótów po 32 bajty, ~640 bajtów.

Zalety i zastosowania

  • Złożoność logarytmiczna: O(log N) dla dowodu, idealnie dla klientów SPV.
  • Minimalny ruch sieciowy: Bitcoin SPV weryfikuje transakcje bez pełnego bloku (500+ GB).

Zastosowania:

  • Bitcoin: Transaction root w nagłówku bloku.
  • Ethereum: State trie, transaction trie, receipt trie.
  • Git: Tree objects w commitach.
  • IPFS: Skrót treści jako korzeń fragmentów.
  • Certificate Transparency: Audyt logów certyfikatów SSL.

Co jest ważne

  • Drzewo Merkle dowodzi przynależności za O(log N) bez ujawniania innych danych.
  • Używaj generyków w Go dla bezpieczeństwa typów; CBOR dla deterministycznego skrótowania any.
  • Fabryka funkcji skrótu zapobiega race conditions od stanu hash.Hash.
  • Dowód jest generowany na podstawie skrótów-sąsiadów ścieżki od liścia do korzenia.
  • Skuteczne dla systemów rozproszonych: blockchainy, magazyny P2P.

— Editorial Team

Advertisement 728x90

Czytaj dalej