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 при подписи мгновенно раскрывает приватный ключ.

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

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

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

Доска запросов