August 22, 2022

Биткоин: работа продолжается. Глава 3

Технические инновации из окопов

Фото автора

Перевод NADO Book by Sjors Provoost.

Проект перевода организован HypeCoinNews.

SegWit

Segregated Witness (разделённое свидетельство), также известный как SegWit - это софт-форк, активированный в сети Биткоина летом 2017 года. Это был последний софт-форк перед активацией Taproot осенью 2021 года, и, возможно, это все еще крупнейшее обновление протокола Биткоина на сегодняшний день.

Вкратце, SegWit позволил разделить данные о транзакциях и о подписях в блоках Биткоина. В этой главе объясняется, как это работает, и подробно рассказывается о том, что это даёт.

Зачем разделять свидетельства?

До появления SegWit существовала проблема с "пластичностью" транзакций.

Примечание редактора: "пластичность" является буквальным переводом на русский термина malleability.

Каждая транзакция имеет уникальный идентификатор. Когда кто-то отправляет вам монеты, а вы отправляете их кому-то еще, ваша транзакция (B) использует идентификатор (ID) транзакции (A) для ссылки на нее. Теперь, если обе транзакции не подтверждены, может возникнуть проблема, когда злоумышленник получит доступ к транзакции А и поменяет ее. Эта манипуляция изменяет идентификатор A. В результате транзакция B теперь использует устаревший идентификатор для ссылки на транзакцию A, что означает, что она ссылается на пустоту. Транзакция, которая пытается потратить из пустоты, недействительна и никогда не попадет в блок. В лучшем случае это создает серьезное неудобство.

Хорошо известный пример пластичности транзакций - то, что произошло с Mt.Gox, биткоин-биржей из Японии.

Чтобы получить более глубокое представление о кончине Mt.Gox, послушайте https://www.whatbitcoindid.com/mtgox-interviews

Согласно некоторым источникам, Mt.Gox вела свой внутренний учет на основе идентификаторов транзакций. Клиент снимал средства, использовал пластичность, чтобы немного изменить транзакцию снятия и получить деньги, потому что транзакция все еще оставалась действительной, но затем заявлял: «Я вывел деньги, но так и не получил их». В ответ Mt.Gox использовала идентификатор транзакции и смотрела, есть ли она в блокчейне. Видя, что в блокчейне нет совпадающего идентификатора транзакции, биржа считала, что клиент прав, и повторно отправляла монеты.

Если более точно, то манипулированию поддавалась та часть транзакции, где содержалась подпись: каждая транзакция подписывается криптографической подписью. До SegWit существовало множество способов настроить эту подпись так, чтобы она выглядела по-другому, но оставалась действительной, с неизменной суммой и получателем. Менялся только идентификатор транзакции.

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

Это было исправлено с помощью BIP 66: https://en.bitcoin.it/wiki/BIP_0066

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

Можно, однако, спросить себя: «А в чем проблема-то?» Проблема, однако, может случится не на только что описанном этапе, когда вы получаете монеты и отправляете их дальше. Если в блок попадает измененная версия транзакции A, вы просто создаете новую транзакцию (B), которая ссылается на новый идентификатор транзакции A.

Но представьте, что вы отправили транзакцию (А) в сверхзащищенное хранилище в Арктике, расположенное в тысячах метров под землей. А затем вы отправились в Арктику и создали транзакцию возврата (B) обратно в свой горячий кошелек, подписали ее, но еще не транслировали. Затем, как только вы транслируете первую транзакцию (A) для отправки денег в хранилище, и кто-то ее подпортит, внезапно вторая транзакция (B) становится недействительной, поскольку она относится к неизмененной (A). Теперь вам нужно вернуться в Арктику, чтобы создать новую транзакцию (B), которая ссылается на измененную версию (A) — и этот сценарий в лучшем случае сильно осложнит вам жизнь.

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

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

Lightning не описывается в этой книге, но можно глянуть приложение А.

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

Устранение пластичности транзакций

Так что легко понять, насколько это было проблемной темой.

Транзакция состоит из всех данных транзакции, а также подписи. Она идентифицируется по идентификатору транзакции, который до SegWit был хешем от этих двух вещей. Например, транзакция на 10 BTC от Сатоши Хэлу Финни выглядит следующим образом: f4184fc5…

f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16 https://bitcointalk.org/index.php?topic=155054.0

Однако, поскольку подпись может быть подправлена, хэш (идентификатор транзакции) также будет при этом подправлен, и вы получите в целом ту же транзакцию, но с другим идентификатором. Вот эту проблему и нужно было решить: либо сделать так, чтобы подпись нельзя было изменить, либо так, чтобы такая модификация не изменяла хэш транзакции. Первый подход кажется очень сложным, если не невозможным, поэтому SegWit использует второй подход.

Решение состояло в том, чтобы добавить подпись в конец транзакции. Эта новая часть транзакции не учитывается при вычислении хэша идентификатора. Старые узлы также ее не получают. С их точки зрения подпись оказывается пуста, и транзакцию может провести кто угодно.

Точнее: scriptSig пуст, тогда как раньше он поместил бы в стек публичный ключ и подпись. В свою очередь, scriptPubKey представляет собой 0, за которым следует хэш открытого ключа. Для старых узлов эта комбинация приводит к ненулевому элементу в стеке, т. е. «True», что означает корректную трату. С другой стороны, узлы с поддержкой SegWit будут интерпретировать scriptPubKey как программу SegWit v0 и использовать при валидации транзакции новое поле witness. См. https://en.bitcoin.it/wiki/BIP_0141.

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

Короче говоря, SegWit решил проблему пластичности транзакций, когда идентификаторы транзакций можно было изменить, не аннулируя сами транзакции. В свою очередь, решение проблемы пластичности транзакций позволило использовать протоколы второго уровня, такие как сеть Lightning.

SegWit как софтфорк

Как можно развернуть SegWit в качестве софтфорка (обновление с обратной совместимостью)? Мы более подробно рассмотрим, как работают софтфорки, в главе 12, но основная идея заключается в том, что обновленные узлы знают о новых правилах, в то время как необновленные узлы не воспринимают происходящее как нарушение правил.

С помощью SegWit это достигается путем добавления данных в конец блока, этаким подблоком, а не отправки этих дополнительных данных на устаревшие узлы. Хэш этих данных добавляется в транзакцию coinbase в операторе OP_RETURN.

Компания Coinbase была названа в честь той самой первой транзакции в блоке, которая создает монеты из ниоткуда и выплачивает вознаграждение майнеру.

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

Ограничение размера блока

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

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

Однако увеличение не безгранично. Узлы SegWit используют новый способ вычисления того, как рассчитывается цена транзакции, давая 75-процентную скидку на данные о подписях. Процент несколько произвольный — достаточный, чтобы сделать транзакции SegWit дешевле, чем их аналоги до SegWit, но не настолько, чтобы стимулировать злоупотребления.

Будущие версии SegWit, например, Taproot

Тема Taproot подробно рассматривается в главе 11. Но здесь нам важно знать, что управление версиями скриптов SegWit упрощает обновление до новых типов транзакций, и недавнее обновление Taproot - это первый пример использования такой возможности.

Управление версиями работает следующим образом (и также затрагивается в главе 1, посвященной адресам). Вывод каждой транзакции содержит сумму и то, что называется scriptPubKey. Последний является частью биткоин-скрипта, который ограничивает, как тратить эту монету, о чем мы кратко упомянули в главе 1 и объясним более подробно в главе 10. В SegWit scriptPubKey всегда начинается с числа, которое интерпретируется как версия SegWit. Правила интерпретации SegWit версии 0 высечены в камне, как и правила интерпретации SegWit версии 1, также известной как Taproot. Но все, что следует за 2 или выше, открыто для захвата: эти правила могут быть написаны позже.

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

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

Аппаратные кошельки

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

Аппаратный кошелек — это внешнее устройство, которое хранит ваши личные ключи и может подписывать биткоин-транзакции. Поскольку это специализированное устройство, которое в остальном очень простое, вероятность наличия вредоносных программ на нем меньше, чем на обычном компьютере. Обычно он показывает вам сводные данные, основанные на его понимании транзакции, а затем просит вас утвердить транзакцию, прежде чем она будет подписана.

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

Устройство также проверяет, соответствует ли сумма входов сумме выходов плюс комиссия. Это защитит вас от сценария, когда злоумышленник заставит вас заплатить абсурдную сумму (возможно, в сговоре с майнером).

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

Для ясности уточним, что такие проверки должен выполнять любой кошелек, а не только аппаратные кошельки. У вас всегда есть входы, где находятся монеты в вашем владении. Также у вас есть выходы, где представлены монеты, которые вы отправляете, в том числе обычно вывод сдачи самому себе. Разница между суммой входов и выходов - это комиссия, которую удерживает майнер, сама она не упоминается в транзакции, поэтому кошелек рассчитывает ее за вас.

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

Следовательно, аппаратный кошелек рискует отправить 10 миллионов монет в качестве комиссии, не осознавая этого. И если кто-то вступает в сговор с майнером или просто хочет каким-то странным образом захватить ваши монеты в заложники, это нехорошо. Собственно, SegWit именно это и делает - фиксирует суммы на входах транзакции.

К сожалению, подход, используемый SegWit, по-прежнему оставляет открытыми некоторые потенциальные атаки, но Taproot устранил и их.

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

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

Резюме

Основное преимущество SegWit заключается в том, что он устраняет пластичность, это позволяет использовать такие вещи, как сеть Lightning, что приводит к довольно большому увеличению потенциальной пропускной способности транзакций.

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

Война за размер блока

Если вышеизложенное звучит великолепно и бесспорно, то это потому, что, на мой взгляд, так оно и есть. Однако в годы, когда готовился этот софтфорк, вокруг него разворачивалась целая драма. Хороший рассказ об этом можно прочитать в книге Джонатана Бира «Война за размер блока: битва за то, кто контролирует правила протокола Биткоина».

Перевод книги Джонатана Бира:

Глава 1 - Первый удар

Остальные главы вы также можете найти в этом блоге.

Поддержите проект!

Шлите ваши сатоши, используя эту LNURL ссылку с сообщением "НАДО"/"NADO"

Используя страничку,просто пользователю @c3p0rs через @lntxbot, либо [email protected] если ваш кошелёк поддерживает LN адреса.