Web3 и блокчейн Как устроен блокчейн: блоки, хеш-цепочка, дерево Меркла, распределённый реестр
0%

Как устроен блокчейн: блоки, хеш-цепочка, дерево Меркла, распределённый реестр

Как устроен блокчейн: блоки, хеш-цепочка, дерево Меркла, распределённый реестр

Слово «блокчейн» обросло таким количеством маркетинга, что за ним почти не видно инженерии. А инженерия там простая и красивая: это реплицированный append-only журнал, целостность которого удостоверяется хешами, а порядок записей согласуется между взаимно недоверяющими узлами. Ни одной магии, и каждый кирпич старше биткоина на десятилетия: хеш-цепочка — Хабер и Сторнетта, 1991; деревья Меркла — 1979; репликация конечного автомата — Лэмпорт, 1978.

Здесь мы разберём заголовок блока Bitcoin по байтам и посчитаем его хеш кодом, который можно запустить; построим дерево Меркла и доказательство включения; посмотрим, как блок расходится по сети и что происходит, когда два блока находятся одновременно. Подписи и адреса — тема следующей статьи, консенсус — третьей. И сразу оговорка о жанре: мы разбираем технологию, а не рынок. Прогнозов курсов и советов, что покупать, здесь не будет — зато будет честный разговор о том, чего блокчейн принципиально не умеет.

Задача, которую решает блокчейн

Начнём с постановки. Есть множество участников, которые хотят вести общий журнал фактов (переводы, записи о владении, вызовы контрактов), при этом не доверяют друг другу и не готовы назначить администратора, решающего, что записано, — и вдобавок могут врать, уходить оффлайн, переприсылать старые сообщения и раскалываться на изолированные сегменты сети.

Уберите второе условие — и задача решается банальным Postgres с ролями и аудит-логом; это почти всегда лучший выбор. Блокчейн осмыслен ровно тогда, когда доверенного центра нет и его нельзя создать — политически, юридически или из-за конфликта интересов. Это единственный критерий применимости, остальное — производные.

Ядро проблемы формулируется как двойная трата: цифровой объект копируется бесплатно, поэтому один и тот же «рубль» можно отправить двум получателям, и оба увидят корректно подписанное сообщение. Подпись доказывает авторство, но не отвечает, какая транзакция была первой. Порядок нельзя вывести локально — о нём нужно договориться. Отсюда весь дизайн:

Первая ветка — статья «Криптография Web3», третья — «Механизмы консенсуса». Здесь мы закрываем вторую и разбираем, как из неё собирается сеть.

Идея, которую стоит зафиксировать сразу: блокчейн — не база данных, а протокол репликации детерминированного конечного автомата. Узлы обмениваются не состоянием («у Алисы 5 монет»), а упорядоченным логом переходов. Каждый применяет один и тот же лог одной и той же функцией перехода и обязан получить то же состояние. Если два узла получили разное — это либо расхождение логов, либо консенсусный баг реализации, самая страшная категория багов в отрасли.

Кирпич первый: криптографический хеш

Всё держится на функции H(x), которая берёт байты произвольной длины и выдаёт фиксированные 32. От неё требуется: детерминированность (одинаковый вход — одинаковый выход всегда и везде); лавинный эффект (смена одного бита меняет примерно половину битов выхода); стойкость к прообразу (по H(x) не восстановить x дешевле перебора); стойкость к коллизиям (не найти x != y с одинаковым хешем дешевле, чем за 2^128 операций для 256-битного выхода — парадокс дней рождения).

Bitcoin использует SHA256d(x) = SHA-256(SHA-256(x)); двойное хеширование — историческая перестраховка от атак удлинением сообщения на конструкцию Меркла — Дамгора. Ethereum использует keccak256 — Keccak с оригинальным паддингом, не совпадающий со стандартом SHA3-256 NIST. Разница в один байт паддинга, но хеши разные, и это регулярный источник багов у тех, кто берёт первую попавшуюся библиотеку «sha3».

Практический смысл: хеш — это имя данных. Не указатель на место, где они лежат, а имя, вычисляемое из самого содержимого. Отсюда контентная адресация, на которой целиком построены git и IPFS (см. «Децентрализованные хранилища»), и её главное следствие: имя нельзя сохранить, поменяв данные. Детали самих функций — в «Криптографии Web3».

Кирпич второй: блок

Блок состоит из заголовка и тела. Разделение не косметическое: заголовок мал и фиксирован, поэтому его можно скачать и проверить, не скачивая тело. На этом стоят все лёгкие клиенты.

Раскладка 80-байтового заголовка блока Bitcoin

Заголовок Bitcoin — ровно 80 байт, все поля little-endian:

Смещение Размер Поле Смысл
0 4 version версия правил и биты сигнализации софт-форков
4 32 hashPrevBlock хеш заголовка предыдущего блока
36 32 hashMerkleRoot корень дерева Меркла транзакций тела
68 4 time Unix-время, заявленное майнером
72 4 bits компактная запись целевого порога сложности
76 4 nonce счётчик, который перебирает майнер

Два поля — «швы» всей конструкции: hashPrevBlock сшивает блоки в цепь, hashMerkleRoot сшивает заголовок с телом, коммитя к списку транзакций любого размера 32 байтами. Соберём заголовок генезис-блока руками и проверим, что получится всем известный хеш — код самодостаточен, запустите и сверьте:

import hashlib
import struct

def sha256d(b: bytes) -> bytes:
    """Двойной SHA-256 — базовая хеш-функция Bitcoin."""
    return hashlib.sha256(hashlib.sha256(b).digest()).digest()

# Хеши внутри заголовка хранятся little-endian, а показываются пользователю big-endian.
# Отсюда развороты [::-1] — источник половины ошибок у новичков.
merkle_root_display = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"

header = (
    struct.pack("<I", 1)                        # version = 1
    + bytes(32)                                 # hashPrevBlock: у генезиса его нет
    + bytes.fromhex(merkle_root_display)[::-1]  # merkle root во внутреннем порядке
    + struct.pack("<I", 1231006505)             # time: 2009-01-03 18:15:05 UTC
    + struct.pack("<I", 0x1D00FFFF)             # bits: стартовая сложность
    + struct.pack("<I", 2083236893)             # nonce, найденный майнером
)
assert len(header) == 80

block_hash = sha256d(header)[::-1]              # разворачиваем для отображения
print(block_hash.hex())
# 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f

# Проверим, что блок «намайнен»: хеш как число меньше целевого порога из bits
exponent, mantissa = 0x1D00FFFF >> 24, 0x1D00FFFF & 0xFFFFFF
target = mantissa * 256 ** (exponent - 3)
print(int.from_bytes(block_hash, "big") < target)   # True

Ведущие нули не «встроены» в формат — они следствие неравенства hash < target. Майнинг и есть перебор nonce до его выполнения; механика — в статье про консенсус.

Заголовок Ethereum устроен богаче: он содержит не один корень, а три — stateRoot, transactionsRoot, receiptsRoot, — плюс parentHash, number, gasLimit, gasUsed, timestamp, logsBloom, baseFeePerGas (EIP-1559), withdrawalsRoot и другие поля. Зачем столько корней — ниже и подробно в «Ethereum и EVM».

Кирпич третий: хеш-цепочка

Каждый блок содержит хеш предыдущего, поэтому хеш блока N транзитивно зависит от всего содержимого вплоть до генезиса. Изменение одного байта в старом блоке меняет его хеш — и hashPrevBlock следующего блока перестаёт совпадать. Разрыв видит любой, кто идёт по цепочке.

Минимальная реализация, на которой это видно руками:

import hashlib, json
from dataclasses import dataclass, asdict
from typing import List

def sha256d(b: bytes) -> bytes:
    return hashlib.sha256(hashlib.sha256(b).digest()).digest()

@dataclass
class Block:
    height: int
    prev_hash: str
    merkle_root: str
    timestamp: int
    nonce: int = 0

    def hash(self) -> str:
        # Каноническая сериализация обязана быть детерминированной: sort_keys и без пробелов,
        # иначе два честных узла получат разные байты и разные хеши одного блока.
        raw = json.dumps(asdict(self), sort_keys=True, separators=(",", ":")).encode()
        return sha256d(raw).hex()

def mine(blocks: List[Block], merkle_root: str, ts: int, difficulty_bits: int = 16) -> Block:
    prev = blocks[-1].hash() if blocks else "00" * 32
    block = Block(len(blocks), prev, merkle_root, ts)
    target = 1 << (256 - difficulty_bits)          # чем больше bits, тем дороже блок
    while int(block.hash(), 16) >= target:         # честный перебор nonce
        block.nonce += 1
    blocks.append(block)
    return block

def validate(blocks: List[Block]) -> bool:
    for i, block in enumerate(blocks):
        expected = "00" * 32 if i == 0 else blocks[i - 1].hash()
        if block.prev_hash != expected:
            print(f"разрыв хеш-цепочки на высоте {i}")
            return False
    return True

chain: List[Block] = []
for i in range(4):
    mine(chain, merkle_root=f"root{i}", ts=1_700_000_000 + i * 600)

print(validate(chain))            # True
chain[1].merkle_root = "подмена"
print(validate(chain))            # разрыв хеш-цепочки на высоте 2 → False

Разрыв обнаружился на высоте 2, хотя подменили блок 1: ломается связь у следующего блока. Это и есть механика «переписать историю можно только вместе со всем хвостом». Проверка стоит O(n) по числу блоков и O(1) по памяти при потоковом обходе; подделка блока на глубине k требует заново выполнить работу для k+1 блоков, обогнав при этом честную сеть, которая продолжает наращивать цепь.

Здесь важно не переоценить хеш-цепочку. Она даёт tamper-evidence — «подмена заметна», — но не даёт tamper-resistance. Контролируя все копии реестра, я пересчитаю цепочку с нуля, и она будет идеально консистентной. Неподделываемость возникает только из комбинации трёх вещей: цепочка хешей + дороговизна создания блока + независимые наблюдатели, у которых уже есть старая версия истории. Уберите любую — и «блокчейн» в приватной сети из трёх узлов одного владельца превращается в дорогой и медленный audit log.

Кирпич четвёртый: дерево Меркла

Заголовок мог бы коммитить к телу простым H(tx0 || tx1 || ... || txN) — целостность обеспечена. Но тогда для доказательства «tx5 лежит в этом блоке» пришлось бы передавать все транзакции. Дерево Меркла делает то же самое с логарифмическим доказательством: транзакции — листья, соседние узлы попарно хешируются, повторяем до единственного корня. При нечётном числе узлов на уровне Bitcoin дублирует последний (в других системах паддинг устроен иначе — и это важно, см. ниже).

Доказательство включения в дерево Меркла: три хеша вместо восьми транзакций

Merkle proof (audit path) — список узлов-братьев на пути от листа к корню и сторона приклеивания каждого. Проверяющий берёт свою транзакцию, последовательно склеивает с братьями, хеширует — и должен получить корень из заголовка.

import hashlib
from typing import List, Tuple

Proof = List[Tuple[bytes, str]]   # (хеш брата, с какой стороны он стоит)

def h(b: bytes) -> bytes:
    return hashlib.sha256(hashlib.sha256(b).digest()).digest()

def build_levels(leaves: List[bytes]) -> List[List[bytes]]:
    """Строит уровни дерева снизу вверх. O(n) хеширований, O(n) памяти."""
    if not leaves:
        raise ValueError("дерево Меркла не определено для пустого списка")
    levels = [list(leaves)]
    while len(levels[-1]) > 1:
        cur = levels[-1]
        if len(cur) % 2 == 1:
            cur = cur + [cur[-1]]        # дублируем последний — правило Bitcoin
            levels[-1] = cur
        levels.append([h(cur[i] + cur[i + 1]) for i in range(0, len(cur), 2)])
    return levels

def make_proof(levels: List[List[bytes]], index: int) -> Proof:
    """Путь доказательства для листа index: O(log n) элементов по 32 байта."""
    proof: Proof = []
    for level in levels[:-1]:            # все уровни, кроме корня
        sibling = index ^ 1              # брат — сосед по младшему биту индекса
        side = "right" if index % 2 == 0 else "left"
        proof.append((level[sibling], side))
        index //= 2
    return proof

def verify(leaf: bytes, proof: Proof, root: bytes) -> bool:
    """Проверка не требует ни блока, ни сети — только proof и доверенный корень."""
    acc = leaf
    for sibling, side in proof:
        acc = h(acc + sibling) if side == "right" else h(sibling + acc)
    return acc == root

leaves = [h(f"tx{i}".encode()) for i in range(7)]
levels = build_levels(leaves)
root = levels[-1][0]

proof = make_proof(levels, 5)
print(len(proof))                            # 3 хеша на 7 транзакций
print(verify(leaves[5], proof, root))        # True
print(verify(h(b"tx-fake"), proof, root))    # False
Операция Время Память / трафик
Построение дерева O(n) хеширований O(n)
Генерация proof O(log n) O(log n)
Проверка proof O(log n) хеширований 32 байта на уровень
Обновление одного листа O(log n) —

Для блока из 4096 транзакций доказательство — 12 хешей, 384 байта. Именно это делает возможными SPV-клиенты (раздел 8 white paper Сатоши): кошелёк качает только заголовки — 80 байт на блок, порядка 4 МБ за десять лет десятиминутных блоков — и запрашивает proof для интересующих транзакций.

Три инженерных предупреждения, каждое из которых кому-то стоило денег.

1. Дублирование нечётного узла не бесплатно. В Bitcoin оно породило CVE-2012-2459: списки [A, B, C] и [A, B, C, C] дают одинаковый merkle root. Атакующий брал валидный блок, дублировал в нём хвостовые транзакции и рассылал; узлы принимали блок с тем же хешем заголовка, обнаруживали дубли, помечали блок невалидным навсегда и потом отказывались принимать настоящий. Бесплатный DoS с расколом сети. Лечится явной проверкой на дубликаты идентификаторов транзакций.

2. Атака второго прообраза на «плоское» дерево. Если листья и внутренние узлы хешируются одинаково, внутренний узел можно выдать за лист: пара (H(0,1), H(2,3)) неотличима от 64-байтной «транзакции». В Bitcoin спасает лишь то, что валидная транзакция не бывает 64-байтной. Правильное решение — разделение доменов: RFC 6962 (Certificate Transparency) хеширует листья как H(0x00 || data), а узлы как H(0x01 || left || right). Проектируете своё дерево — делайте так.

3. Корень фиксирует порядок по позиции, а не «по смыслу». Дерево удостоверяет конкретную последовательность, поэтому любая нормализация (сортировка, дедупликация) обязана быть частью консенсусных правил — иначе два честных узла построят разные корни из одного набора данных.

Изменяемое состояние: почему в Ethereum дерево другое

Дерево Меркла отлично коммитит список. Но состояние Ethereum — словарь: адрес → (nonce, баланс, хранилище, код). Нужны операции, которых у простого дерева нет: доказать, что по ключу лежит значение; доказать, что по ключу ничего нет; обновлять тысячи ключей за блок, не перестраивая всё дерево. Ответ — Merkle Patricia Trie: префиксное дерево, где каждый узел адресуется своим keccak256 и лежит в key-value базе, ключ — keccak256(адрес), разбитый на полубайты, а узлы бывают трёх видов: лист, расширение (сжимает длинную цепочку одинаковых полубайтов) и ветвление на 16 детей.

Следствия. Состояние вложено: корень блока коммитит к аккаунтам, аккаунт — к своему хранилищу, так что один 32-байтный stateRoot удостоверяет каждый слот каждого контракта. Trie персистентен: изменённые узлы дописываются, старые остаются, поэтому по историческому stateRoot читается состояние на любом блоке — за это archive-ноды и платят терабайтами. И он даёт stateless-верификацию: eth_getProof возвращает доказательство, проверяемое клиентом, который знает только заголовок. Плата — амплификация чтений: одно обращение к аккаунту разворачивается в несколько случайных чтений из базы, отчего синхронизация узла Ethereum становится дисковой, а не сетевой задачей. Подробности — в «Ethereum и EVM».

Реестр становится распределённым

До сих пор речь шла о структуре данных на одной машине. Распределённой она становится, когда одинаковую копию независимо строят тысячи узлов:

Никто никому не верит на слово. Узел B принимает блок не потому, что его прислал уважаемый майнер: он пересчитывает хеш заголовка, пересчитывает merkle root из тела, исполняет каждую транзакцию и сверяет получившийся stateRoot с заявленным. Это дублирование работы — фундаментальная цена децентрализации и главная причина, почему L1 медленный. Не «плохо написан», а устроен так по смыслу.

Транзакция в mempool — это не запись в реестре. Её могут вытеснить по комиссии, заменить (RBF), она может протухнуть или проиграть конкуренту. Продукт, который показывает пользователю «платёж принят» по факту попадания в mempool, рано или поздно получит инцидент.

Пропускная способность упирается в распространение. Блок должен дойти до большинства узлов раньше следующего, иначе растёт доля осиротевших блоков. Отсюда оптимизации вроде compact blocks (BIP 152): вместо тела передаются короткие идентификаторы транзакций, которые получатель уже видел в mempool, и типичный блок сжимается до единиц килобайт.

Порядки величин: Bitcoin — блок раз в ~10 минут, предел веса ~4 млн единиц, единицы транзакций в секунду. Ethereum — слот 12 секунд, лимит газа блока порядка нескольких десятков миллионов, десятки транзакций в секунду. Это на два-три порядка меньше централизованной платёжной системы, и никакая «оптимизация базы» этого не меняет: платим за то, что каждый узел независимо перепроверяет всё. Обходные пути — вынос исполнения на второй уровень, см. «Масштабирование».

Форки и реорганизации: цепочка на самом деле дерево

Два узла могут одновременно создать валидный блок на одной высоте. Сеть временно расходится, и «последний блок» у разных узлов разный. Это не сбой, а штатный режим.

Правило выбора ветки (fork-choice rule) — часть протокола, а не «здравый смысл». В Bitcoin выигрывает цепочка с наибольшей суммарной работой (не длиннейшая — именно самая тяжёлая). В Ethereum на PoS работает LMD-GHOST поверх голосований валидаторов, а сверху — финальность Casper FFG, объявляющая блоки необратимыми примерно через две эпохи (около 13 минут). Разбор — в «Механизмах консенсуса».

Практическое следствие для всех, кто пишет код поверх блокчейна: реорганизация — обычное событие, и индексатор обязан её переживать. Транзакция из осиротевшего блока в реестре отсутствует. Записали в свою БД «заказ оплачен» после одного подтверждения — обработали платёж, которого нет. Отсюда правило k подтверждений, где k — не магия, а функция цены ошибки: для мелкого платежа хватает единиц, для крупного ждут десятки блоков или, в сетях с явной финальностью, статуса finalized.

Отдельно: транзакция, попавшая в блок и зафейлившаяся при исполнении (контракт сделал revert), всё равно остаётся в реестре и всё равно списывает комиссию. «Не выполнилась» и «не записана» — разные вещи, и путать их дорого.

Родственники: git, Certificate Transparency, аутентифицированные логи

Блокчейн — частный случай семейства аутентифицированных структур данных. Ближайший родственник лежит у каждого на диске:

git Блокчейн
Объекты контентная адресация по SHA-1/SHA-256 контентная адресация по SHA-256/keccak256
Связь коммит ссылается на родителя блок ссылается на предыдущий
Структура Merkle DAG, родителей может быть несколько цепь, родитель ровно один
Расхождения человек делает merge автоматический fork-choice rule
Кто решает истину владелец репозитория правило консенсуса, владельца нет

Вся суть — в последней строке. Git прекрасно доказывает, что история не менялась незаметно, но не отвечает на вопрос «чья история настоящая» — это решает человек с правами на origin/main. Блокчейн убирает эту роль и заменяет её протоколом, платя дороговизной записи и медленным подтверждением. Если вам нужен только журнал, который нельзя незаметно переписать, но администратор допустим, — дешевле взять git-подобную схему или Certificate Transparency, реально работающий Merkle-лог всех выпущенных TLS-сертификатов.

Цена: сколько стоит вести реестр

Компромиссы честнее всего видны в цифрах хранения. Полная цепочка Bitcoin — сотни гигабайт с предсказуемо линейным ростом (блоки ограничены по весу). Полный узел Ethereum со snap-синхронизацией — порядка терабайта; архивный, хранящий состояние на каждом блоке, — единицы-десятки терабайт в зависимости от клиента. Pruning позволяет узлу проверить всю историю, а хранить только недавние блоки и текущее состояние: важно, что обрезанный узел историю проверил сам, он просто не может её раздавать. Отдельная головная боль Ethereum — state bloat: растёт не история, а состояние, потому что удаление данных экономически почти не поощряется (возврат газа за очистку слотов урезали как раз из-за побочных эффектов, EIP-3529).

Отсюда правило для проектировщика: on-chain хранят не данные, а обязательства о данных. Писать в блокчейн килобайты пользовательского контента — архитектурная ошибка: дорого, навсегда, публично и неудаляемо (привет, GDPR). Правильный паттерн — контент в контентно-адресуемое хранилище, хеш в цепочку; ровно об этом статья «Децентрализованные хранилища».

Чего блокчейн не умеет

Не проверяет правду о внешнем мире. Протокол гарантирует, что запись подписана нужным ключом и не переписана задним числом. Он не гарантирует, что записанное соответствует реальности: напишет оператор, что коробка погружена в фуру, а коробка пуста — в реестре навсегда останется заверенная ложь. Это проблема оракула, и внутри цепочки она не решается в принципе; см. «Безопасность контрактов».

Не даёт приватности. Адреса псевдонимны, а не анонимны, анализ графа транзакций — зрелая индустрия. Любая связка адреса с личностью (биржа с KYC, IP, повторное использование адреса) ретроспективно раскрывает всю историю.

Не прощает ошибок. Нет кнопки «отменить перевод» и нет «восстановить пароль». Потерянный seed — потерянные средства навсегда, и это массовое явление, а не редкость (см. «Кошельки и ключи»).

Не делает код правильным. Неизменяемость означает, что баг в контракте тоже неизменяем и лежит в открытом доступе вместе с деньгами, которые он охраняет. The DAO (2016) и десятки более поздних взломов — это не «блокчейн взломали», это обычные ошибки в обычном коде, у которых цена ошибки выше обычной.

Не защищает от мошенничества. Технология не отличает добросовестный проект от скама. «Децентрализация» и «неизменяемость» в маркетинговых материалах не являются свойствами продукта — их проверяют по коду и по тому, у кого лежат админские ключи контракта.

И самое частое: если у системы есть владелец, которому все и так доверяют, блокчейн ей не нужен. «Приватный блокчейн» из пяти узлов одной компании — это реплицированная БД с необычно плохой производительностью.

Типичные заблуждения

  • «Данные в блокчейне зашифрованы» — нет, они открыты, но подписаны и захешированы; шифрование и хеширование — разные операции.
  • «Блок содержит балансы» — в Bitcoin блок содержит транзакции, а балансы выводятся из набора непотраченных выходов (UTXO); в Ethereum балансы лежат в состоянии, а блок хранит лишь его корень и переходы.
  • «Хеш блока хранится в блоке» — нет, он вычисляется из заголовка; в блоке лежит хеш предыдущего.
  • «Транзакция не прошла, значит комиссии нет» — если она в блоке, комиссия списана.
  • «Нужно скачать всю цепочку, чтобы что-то проверить» — заголовков и merkle proof достаточно для проверки включения.
  • «Больше блоков в секунду — лучше» — уменьшение интервала повышает долю осиротевших блоков и снижает эффективную безопасность; это компромисс, а не свободный параметр.

Как посмотреть своими руками

Ничего написанного не нужно принимать на веру. Публичных RPC хватит для чтения; для серьёзной работы поднимают свой узел (см. «Архитектуру dApp»).

# Ethereum: заголовок блока по номеру (hex), без тела транзакций.
# В ответе видны parentHash, stateRoot, transactionsRoot, receiptsRoot — те самые корни.
curl -s -X POST https://ethereum-rpc.publicnode.com \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x1312D00",false]}' \
  | python3 -m json.tool | head -30

# Bitcoin, если поднят свой узел: сырой заголовок 80 байт в hex
bitcoin-cli getblockheader <blockhash> false

Полезное упражнение: возьмите сырой заголовок Bitcoin из последней команды, прогоните через sha256d кодом из этой статьи, разверните байты — и убедитесь, что получили ровно тот хеш, по которому блок запрашивали. После этого «блок нельзя изменить незаметно» перестаёт быть лозунгом и становится арифметикой.

Мини-итог

  • Блокчейн — реплицированный append-only лог плюс правило согласования порядка записей; осмыслен там и только там, где доверенный центр невозможен.
  • Блок = заголовок (маленький, фиксированный, достаточный для проверки цепочки) + тело с транзакциями. У Bitcoin заголовок — ровно 80 байт.
  • Хеш-цепочка делает подмену истории заметной; неподделываемой её делает сочетание с дорогим консенсусом и независимыми наблюдателями.
  • Дерево Меркла сжимает список до 32 байт и даёт доказательство включения за O(log n); строите своё — разделяйте домены листьев и узлов и проверяйте дубликаты.
  • Изменяемому состоянию нужен trie, а не список: отсюда Merkle Patricia Trie и stateRoot.
  • Цепочка — это дерево: форки и реорганизации штатны, приложение обязано их переживать; в цепочке хранят хеши, а не данные.

Источники

Что дальше

Мы разобрали, как данные складываются в блоки и как блоки сшиваются хешами. Осталась вторая опора конструкции: кто и чем доказывает, что запись сделал именно владелец средств. Дальше — Криптография Web3: хеши, пары ключей, подписи, адреса: эллиптические кривые, ECDSA и его подводные камни, как из публичного ключа получается адрес и почему повторное использование одного nonce при подписи мгновенно раскрывает приватный ключ.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше