Биткоин: транзакции, пластичность, SegWit и масштабируемость

Dmitry Laptev
Aug 24, 2017 · 6 min read

This article in English.

Итак, сообщество биткоина добилось своего: майнеры согласились на SegWit и он таки активировался сегодня в сети. Паника немного улеглась, цена подросла… Пришла пора разобраться, что же такое этот SegWit, почему все о нем говорят, и зачем он нужен.

Дисклеймер. Я сознательно опускаю/упрощаю некоторые технические моменты. А еще я не эксперт, и могу банально ошибаться.

Segregated Witness (SegWit) — это собирательное название для нескольких изменений в протоколе биткоин. Многие из этих изменений заслуживают отдельного внимания, но я буду фокусироваться на первом и главном: изменение того, как считается идентификатор транзакции.

Хотя в последнее время все говорят о SegWit только в контексте споров о масштабировании, изначальное предложение решало совсем другую проблему: transaction malleability, или, как принято это переводить на русский, пластичность транзакций.

Но обо всем по порядку: что такое транзакция?

Транзакция

Для простоты будем рассматривать транзакцию с адреса A на адрес B, с одним входом и одним выходом. Пример такой транзакции:

Input:
Previous tx: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6
Index: 0
scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501
Output:
Value: 5000000000
scriptPubKey: OP_DUP OP_HASH160 404371705fa9bd789a2fcd52d2c580b65d35549d OP_EQUALVERIFY OP_CHECKSIG
  • Previous tx — идентификатор предыдущей транзакции на адрес A;
  • Index — номер входа (в этом примере один вход с номером 0);
  • scriptSig — первая часть проверяющего скрипта (об этом ниже);
  • Value — количество биткоинов, указанное в сатоши (один биткоин = 100 миллионов сатоши) — 50 биткоинов в примере;
  • scriptPubKey — вторая часть проверяющего скрипта, которая также содержит адрес получателя B.

Транзакции всегда выстраиваются в цепочку (на самом деле в дерево, но это детали). Чтобы потратить биткоины с адреса A, нужно потратить выход каких-то транзакций на адрес A (за исключением coinbase транзакций, у которых ноль входов, но это тоже детали).

Чтобы проверить, что мы можем тратить выход предыдущей транзакции, мы должны:

  1. Соединить scriptSig новой транзакции со scriptPubKey предыдущей транзакции,
  2. Проверить, что получившийся скрипт (на специальном языке программирования Script) выполняется и возвращает True.

В самом простом случае (как в примере выше) scriptSig содержит подпись транзакции приватным ключем, а scriptPubKey просто проверяет совпадение публичного ключа и валидность подписи для данного адреса (основная операция: OP_CHECKSIG).

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

Пластичность транзакций

Что же конкретно содержится в поле Previous tx, спросите вы? Что это за идентификатор транзакции (transaction identifier, txid)?

txid — это sha256d хэш от всех полей данных транзакции.

Ключевым моментом является зависимость от scriptSig. Потому что scriptSig сама по себе как правило содержит данные о транзакции подписанные приватным ключем, включая txid. scriptSig зависит от txid, поэтому txid не может зависеть от scriptSig.

Любой держатель полной ноды биткоин (не только майнер) помогает собирать и распространять по сети данные о транзакциях. В процессе он может изменить scriptSig таким образом, чтобы скрипт подписи оставался корректным, результат транзакции оставался тем же, но txid поменялся.

Например, к любой подписи можно добавить операцию OP_NOP (не делает ничего), или для изощренности добавить две операции OP_DUP OP_DROP (первая дублирует подпись на стеке, а вторая удаляет верхний элемент стека). Подпись останется корректной, но txid поменяется.

И вот тут-то кроется проблема. Точнее две.

1. txid — плохой идентификатор

Такая изменяемость идентификатора не позволяет использовать txid по назначению: как идентификатор. Злоумышленник может перехватить нормальную транзакцию и распространить модифицированную версию по сети. С некоторой вероятностью майнеры могут включить в блок модифицированную транзакцию вместо изначальной.

Зачем это нужно злоумышленнику, спросите вы? В идеальном мире — особо незачем. Но в реальном мире люди любят использовать идентификаторы для уникальной идентификации. Это и произошло (по одной из версий) с биржей MtGox.

Злоумышленник выводил биткоины с этой печально известной биржи, перехватывал транзакцию, менял ей txid. Транзакция оставалась валидной, злоумышленник получал свои биткоины. Но биржа видела, что изначальный txid не вошел в блок и не уменьшала баланс злоумышленника.

2. Цепочки транзакций в одном блоке

Есть еще и вторая проблема. А точнее упущенная возможность.

Теоретически биткоин позволяет тратить выход транзакций, которые еще не были включены в блок. Я могу перевести средства с адреса A на адрес B. А потом, не дожидаясь подтверждения первой транзакции, перевести биткоины с адреса B на адрес C. И обе эти транзакции могли бы быть проверены и одновременно включены в блок, если бы не пластичность.

Если txid первой транзакции может измениться, значит Previous tx второй транзакции может измениться, а значит и подпись может измениться. А значит вторую транзакцию проверить нельзя, пока первая не будет включена в блок (тогда ее txid будет зафиксирован).

И это проблема больше, чем кажется на первый взгляд, но об этом в самом конце.

Segregated Witness

На самом деле теперь все становится очень просто.

SegWit предлагает: давайте вынесем всю пластичную информацию из транзакции отдельно (witness data), а txid будем считать без нее. Идентификатор станет уникальным, проблемы будут решены.

Элементарное решение, не правда ли? Но оно меняет определение транзакции (которое почти не менялось с самого начала), меняет механизмы валидации, требует переписать огромное количество очень стабильного кода. А еще неплохо было бы подумать про сохранение совместимости.

Поэтому (а также из-за политических игр, про которые я не буду здесь рассказывать) принятие SegWit заняло так долго.

При чем здесь масштабирование?

Оказывается, этот фикс имеет огромное влияние на масштабирование биткоина. По двум причинам.

1. Больше транзакций в блоке

Посмотрите еще раз на пример транзакции выше по тексту. Больше половины информации в ней — это scriptSig. Вынося эту информацию отдельно, мы по сути снижаем размер транзакции. И эффективно увеличиваем размер блока.

Конечно, это читерство, потому что подпись мы тоже будем хранить, хоть и отдельно. Но при вычислении размера блока эти данные не учитываются. Почему не учитываются? Не вдаваясь в детали, потому что только так можно было реализовать SegWit через soft fork. Чуть больше про форки.

Теоретически, в блок можно будет впихнуть в четыре раза больше транзакций. Практически — примерно в два раза больше. Вполне себе ощутимое масштабирование.

2. Транзакции вне блокчейна и lightning network

На данный момент абсолютно все транзакции хранятся в блокчейне. Тысячи компьютеров по всему миру хранят информацию о том, что я купил кофе за 0.0015 биткоина. Это дорого для узлов сети. Это дорого и долго для меня: каждый раз мне нужно ждать 10 минут подтверждения транзакции за кофе.

Один из вариантов решения этой проблемы — проводить небольшие транзакции вне основного блокчейна. И время от времени синхронизировать баланс на главном блокчейне. Общее название этого подхода — second layer networks (сети второго уровня).

Одним из рабочих решений в этом стиле на данный момент считается Lightning network. Работает это примерно так:

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

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

Причем тут SegWit, спросите вы? Он действительно не обязателен, но делает имплементацию намного удобнее.

Не углубляясь в детали… Для работы канала микроплатежей необходимо создать транзакцию подписанную двумя сторонами (double-signed transaction). В самом начале на адрес с двумя ключами отправляются средства. Но чтобы предотвратить мошенничество, double-signed transaction должна быть подписана до того, как туда переведут деньги.

Но чтобы провернуться такое, необходимо собрать выходы транзакций, которые еще не были подтверждены на главном блокчейне. А это как раз тот сценарий, который на данный момент невозможен из-за пластичности транзакций (смотрите пункт “2. Цепочки транзакций в одном блоке” выше по тексту). И тут-то SegWit приходит на помощь.

Заключение

TL;DR. SegWit активирован. На самом деле он решает проблему пластичности транзакций, но еще оказывается полезен для масштабирования. Краткосрочно он позволит включать больше транзакций в блоки. Долгосрочно — открывает новые возможности масштабирования через off-chain транзакции. С нетерпением ждем, что из этого получится.

Комментируйте, критикуйте, задавайте вопросы, подписывайтесь! Больше интересных статей в телеграме: https://t.me/cryptohodl. Больше обо мне: http://laptev.ch.

)
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade