Биткоин: транзакции, пластичность, SegWit и масштабируемость
Итак, сообщество биткоина добилось своего: майнеры согласились на SegWit и он таки активировался сегодня в сети. Паника немного улеглась, цена подросла… Пришла пора разобраться, что же такое этот SegWit, почему все о нем говорят, и зачем он нужен.
Дисклеймер. Я сознательно опускаю/упрощаю некоторые технические моменты. А еще я не эксперт, и могу банально ошибаться.
Segregated Witness (SegWit) — это собирательное название для нескольких изменений в протоколе биткоин. Многие из этих изменений заслуживают отдельного внимания, но я буду фокусироваться на первом и главном: изменение того, как считается идентификатор транзакции.
Хотя в последнее время все говорят о SegWit только в контексте споров о масштабировании, изначальное предложение решало совсем другую проблему: transaction malleability, или, как принято это переводить на русский, пластичность транзакций.
Но обо всем по порядку: что такое транзакция?
Транзакция
Для простоты будем рассматривать транзакцию с адреса A на адрес B, с одним входом и одним выходом. Пример такой транзакции:
Input:
Previous tx: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6
Index: 0
scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501Output:
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 транзакций, у которых ноль входов, но это тоже детали).
Чтобы проверить, что мы можем тратить выход предыдущей транзакции, мы должны:
- Соединить
scriptSig
новой транзакции соscriptPubKey
предыдущей транзакции, - Проверить, что получившийся скрипт (на специальном языке программирования 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.