Этот материал был первоначально опубликован в книге «Биткоин: работа продолжается» (Bitcoin: A Work In Progress), написанной Сьорсом Провостом. Узнать о ней больше можно на сайте https://btcwip.com/.
В этом материале рассматривается программное обеспечение с открытым исходным кодом в контексте важности использования открытого кода при создании программных продуктов для биткоина. В нем также объясняется, почему даже открытое программное обеспечение необязательно решает все проблемы доверия, связанные с конкретными программными продуктами.
В теории тот факт, что большинство узлов, кошельков и приложений биткоина имеют открытый исходный код, должен гарантировать, что разработчики не смогут включить в ПО вредоносный код, ведь любой желающий может проверить исходный код на наличие вредоносных программ. Но на практике людей, обладающих достаточным опытом и знаниями, чтобы это сделать, не так уж много, а зависимость программных продуктов в целом от внешних библиотек кода только усложняет ситуацию.
Более того, даже если открытый исходный код надежен, это не гарантирует, что двоичные файлы (компьютерный код) ему соответствуют. Первая попытка снизить этот риск в блокчейне биткоина включала процесс, известный как компилирование Gitian — несколько разработчиков Bitcoin Core подписывают двоичные файлы, если — и только если — все они создают абсолютно одинаковые двоичные файлы на базе одного исходного кода. Для этого нужна специальная программа-компилятор.
Позднее появился проект Guix, который существенно расширяет компилирование по методу Gitian. Благодаря ему удалось минимизировать уровень доверия, необходимый для превращения исходного кода в двоичные файлы — в том числе доверия к самому компилятору.
Свободный vs открытый исходный код
Прежде чем перейти к рассмотрению конкретных особенностей Gitian и Guix, в этом разделе мы кратко расскажем об исторических различиях между свободным и открытым программным обеспечением и о том, как они были объединены в FOSS (Free and Open-Source Software, свободное программное обеспечение с открытым исходным кодом).
Суть движения за свободное распространение программного обеспечения в том, что если ПО имеет закрытый исходный код, между разработчиками и пользователями устанавливаются неравноправные отношения, ведь пользователи не знают, какой код в ПО, которое они используют.
На самом деле программа, которую считывает ваш компьютер, написана на языке, который не может понять ни один человек, — двоичном коде, состоящем из единиц и нулей. Но для написания этого программного обеспечения разработчики использовали язык программирования, например C++. Это разные вещи, хотя большинство простых смертных не умеют читать ни то, ни другое. Поэтому, когда вы используете ПО с закрытым исходным кодом, вы можете узнать только этот двоичный код, но не язык программирования, который использовался для его создания. Как следствие, вы понятия не имеете, что делает ваш компьютер.
Так, например, если разработчик поместит в программу с закрытым кодом вредоносное ПО, ваш компьютер может шпионить за вами или делать что-то нежелательное для вас, и вы этого даже не заметите.
Программисту по имени Ричард Столлман не нравилось ПО с закрытым кодом, и он основал движение за свободное программное обеспечение, суть которого сводилась к следующему: исходный код должен быть общедоступным, чтобы пользователи могли узнать, что они запускают на своих компьютерах. Это, в свою очередь, лишило разработчиков власти над пользователями. Таким образом, «свободное» в этом контексте означает независимость от других, свободу; а не устранение необходимости платить, халяву.
Несколько иную, но не противоречащую приведенной выше, точку зрения высказал Эрик С. Реймонд в книге 1999 года «Собор и базар: размышления невольного революционера о Linux и открытом исходном коде» (https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar). В ней он объяснил преимущества свободного программного обеспечения и почему качество кода может только выиграть. По его словам, «чем больше глаз, тем меньше ошибок». Другими словами, чем чаще фрагмент кода просматривается и проверяется, тем выше вероятность найти все ошибки.
Эти прагматичные размышления о качестве кода убедили сотрудников Netscape Communications Corporation превратить свой внутренний браузер в проект с открытым исходным кодом, Mozilla. Сейчас мы называем его браузером с открытым исходным кодом, потому что эти ребята в свое время провели ребрендинг, и «свободное программное обеспечение» превратилось в «ПО с открытым исходным кодом» (чтобы избежать путаницы с халявой). Именно в этом заключается разница между свободным программным обеспечением и программным обеспечением с открытым исходным кодом.
Биткоин, проект с открытым исходным кодом
Напрашивается закономерный вопрос: причем здесь биткоин? Вот вам пример. Когда вы запускаете программу-кошелек, она показывает ваш адрес. Что если окажется, что этот адрес принадлежит не вам, а контролируется разработчиком программы? И каждый раз, когда вам оправляют деньги, монеты достаются не вам. Вот почему вам нужна максимальная прозрачность в отношении всего кода, который выполняется на вашем компьютере.
Если у вас есть соответствующие навыки, вы можете скомпилировать программу-кошелек самостоятельно и не загружать ненадежный двоичный файл. Но это не решит проблему для подавляющего большинства пользователей. Это также не полностью решит проблему для вас, потому что даже если вы увидите код на оригинальном языке программирования, трудно понять, что именно он сделает после запуска на компьютере. Во-первых, код слишком большой, чтобы его мог разобрать один человек.
Поэтому так важно, чтобы все программные продукты биткоина имели открытый исходный код — чтобы как можно больше людей могли его изучить и разобрать. Помните: чем больше глаз, тем меньше ошибок. Кроме того, это помогает обнаружить вредоносные фрагменты кода.
Биткоин имеет открытый исходный код, его можно найти в базе на GitHub. Это означает, что любой, кто хочет его использовать, может посмотреть его исходный код и убедиться в том, что он делает ровно то, что должен делать. Но на самом деле людей, которые могут это сделать и понять, не так уж много [количество людей, способных прочитать этот код, зависит от того, что вы подразумеваете под словом «прочитать». Сколько людей вообще владеет компьютерной грамотностью? Сколько из них могут примерно прочитать, что делает программа на языке C++? Вероятно, десятки миллионов (https://redmonk.com/jgovernor/2017/05/26/just-how-many-darned-developers-are-there-in-the-world-github-is-puzzled/). Но среди них лишь несколько тысяч когда-либо работали над криптовалютой или аналогичным программным продуктом. В каждый конкретный день код изучают десятки активных разработчиков. Но никто из них не может следить за всеми изменениями в масштабе всего проекта, так как для этого нужна специализация: один разработчик может знать все о коде пиринговых сетей и совершенно ничего о коде электронных кошельков]. Хотя иногда им даже помогают разработчики, работающие над альткоинами [например, очень серьезный баг CVE-2018-17144 был найден разработчиком Bitcoin Cash по имени Awemany (https://bitcoinops.org/en/topics/cve-2018-17144/). Многие альткоин-проекты начинались с копирования исходного кода биткоина и внесения в него некоторых изменений. Так, Dogecoin изменил график инфляции, уменьшил время между блоками и использовал другой алгоритм доказательства работы. Но при этом его кодовая база на 99% совпадает с кодом Bitcoin Core: цифровые подписи проверяются тем же способом, транзакции и блоки проверяются тем же способом, пиринговая сеть работает так же и т. д. Поэтому, когда разработчики альткоинов работают над своими проектами, они могут обнаружить ошибки в 99% кодовой базы, которую делят с Bitcoin Core. Это повышает безопасность системы биткоина.
Многие альткоин-проекты начинались с копирования исходного кода биткоина и внесения в него некоторых изменений. Так, Dogecoin изменил график инфляции, уменьшил время между блоками и использовал другой алгоритм доказательства работы. Но при этом его кодовая база на 99% совпадает с кодом Bitcoin Core: цифровые подписи проверяются тем же способом, транзакции и блоки проверяются тем же способом, пиринговая сеть работает так же и т. д. Поэтому, когда разработчики альткоинов работают над своими проектами, они могут обнаружить ошибки в 99% кодовой базы, которую делят с Bitcoin Core. Это повышает безопасность системы биткоина.
Если бы мы хотели увеличить число тех, кто может читать и понимать исходный код биткоина, его нужно было бы сделать более чистым и читаемым, потому что первоначальный код, написанный Сатоши, был очень, очень трудным для осмысления [чтобы понять, что значит «осмысление» в этом контексте, представьте, что вы смотрите на код и видите там функцию под названием «make a private key» (сделать закрытый ключ). Вы думаете: «Ладно, что делает эта функция? О, она вызывает другую функцию. Где же эта другая функция? О, она находится на 20 000 строк выше в том же файле. Так, сейчас я поднимусь на 20 000 строк и проверю этот код. Вижу, он ссылается на переменную. О, к этой переменной также обращаются в 15 различных точках кодовой базы…».
Проверка подлинности
Допустим, вы доверяете процессу разработки и выпуска ПО, поэтому загружаете двоичный файл с bitcoincore.org. Первая проблема в том, что вы не знаете, имеют ли разработчики биткоина отношение к bitcoincore.org. Но даже если вы в этом уверены, может оказаться, что сайт был взломан, или сайт не взломан, но взломан DNS-сервер. Вариантов загрузить вредоносное ПО — масса.
Чтобы этого избежать, проекты с открытым исходным кодом почти всегда публикуют контрольную сумму (checksum), которая представляет собой последовательность цифр и букв. Это означает, что если вы скачаете программу и запустите определенный скрипт, то полученная контрольная сумма должна совпасть с указанной разработчиками. Технический администратор обычно публикует контрольную сумму на странице загрузки. В теории этот метод работает. Но если сайт взломают, могут взломать и контрольную сумму, так что он надежен не на 100%.
Следующий шаг — подписать контрольную сумму. Так, например, известный сотрудник — в данном случае Владимир ван дер Лаан, главный (голландский) технический администратор [технические администраторы не так влиятельны, как некоторые считают: https://blog.lopp.net/who-controls-bitcoin-core-/ Кроме того, с недавнего времени контрольную сумму релиза подписывают несколько разработчиков] Bitcoin Core — подписывает контрольную сумму с помощью открытого PGP-ключа. Так было на протяжении 10 лет. Так что если вас не обманули в первый раз, всякий раз, когда вы загружаете обновленную версию программы, вы знаете, каким PGP-ключом должны быть подписаны контрольные суммы.
Почему ему можно доверять? Он знает, что двоичные файлы соответствуют открытому исходному коду, потому что он взял исходный код, выполнил команду и получил двоичный файл. Иначе говоря, он пропустил код через другую программу, которая создает двоичные файлы на основе открытого исходного кода.
Но как быть уверенным в том, что он действительно это сделал? Здесь ситуация усложняется. В идеале нужно выполнить ту же команду, скомпилировать ее и получить тот же результат.
Иногда это работает с конкретным проектом, но по мере роста уровня сложности проекта, этот метод часто дает сбои, потому что точное совпадение двоичного файла зависит от ряда конкретных данных вашей компьютерной системы.
Возьмем простейшую программу на C++:
int main() {
return 0;
}
Эта программа закрывается и возвращает 0. Это еще скучнее, чем «Hello, World!». (https://en.wikipedia.org/wiki/”Hello,_World!”_program).
Допустим, вы компилируете эту программу на Mac и получаете программу размером 16 536 байт. Когда вы повторяете процесс компиляции на другой машине Mac, то получаете идентичный файл, о чем свидетельствует контрольная сумма SHA-256 (https://en.wikipedia.org/wiki/SHA-2). Но когда вы компилируете на машине Ubuntu, вы получаете программу размером 15 768 байт.
Достаточно изменить одну букву в компьютерной программе или в ее скомпилированном двоичном файле — и бум, контрольная сумма больше не совпадает.
Если скомпилированная программа включает библиотеку, то конечный результат зависит от точной версии библиотеки, установленной на машине разработчика во время создания двоичного файла.
Поэтому, когда вы загружаете последнюю версию Bitcoin Core с официального сайта и сравниваете ее с тем, что скомпилировали сами, контрольная сумма будет разной. Возможно, разница обусловлена тем, что у вас более новая версия библиотеки, а возможно, она связана с каким-нибудь незначительным различием между вашей системой и системой Владимира.
Как уже говорилось выше, если вы относитесь к числу немногих счастливчиков, способных самостоятельно компилировать код, это не будет для вас особой проблемой. Но куда более вероятно, что вы надеетесь на то, что кто-то другой выполнит эту проверку за вас. И если что-то не так, этот кто-то забьет тревогу.
Но поскольку соответствие исходного кода загружаемому двоичному файлу так сложно проверить, можно ли всерьез рассчитывать на то, что кто-то будет этим заниматься?
Решение проблемы с помощью Gitian
Чтобы убедиться, что процесс преобразования исходного кода в скомпилированный двоичный код прошел без махинаций, нужны воспроизводимые или детерминированные сборки.
Детерминированность подразумевает, что при одинаковом исходном коде вы получите одинаковый двоичный файл. И если вы измените одну букву в исходном коде, вы получите другой двоичный файл, но все получат один и тот же результат, если внесут одно и то же изменение.
Но двоичные файлы могут различаться из-за незначительных отличий в конфигурации машины, что также является проблемой.
До середины 2021 года Bitcoin Core решал проблему отличий с помощью Gitian (https://gitian.org/). Если говорить в двух словах, пользователь загружал установочный DVD-диск [давным-давно CD можно было заказать по почте, а сейчас вы загружаете образ диска и сбрасываете его на флэшку. Когда вы устанавливаете Ubuntu на виртуальную машину, компьютер создает виртуальный DVD-плеер, используя образ (https://ubuntu.com/download/server)] конкретной версии Ubuntu на виртуальный или физический компьютер и запускал установку. Это гарантирует, что все находятся в одинаковом исходном положении, а поскольку Ubuntu широко используется, есть некоторая уверенность в том, что на установочном диске нет бэкдора для обхода системы защиты биткоина.
На своей машине вы создаете другую виртуальную машину, которая настроена компилировать идентичные двоичные файлы для всех, кто ее использует. Например, в ней используется фиксированное ложное время, так что если в конечный двоичный файл будет добавлена временная метка, она будет одинаковой, независимо от того, в какое время вы запустили компилятор. Эта машина использует одинаковые версии библиотек, одну и ту же версию компилятора и т.д. Затем вы компилируете Bitcoin Core внутри этой виртуальной машины и проверяете контрольную сумму — она должна совпадать с контрольной суммой файлов, которые можно загрузить с сайта bitcoincore.org.
Управляют этим «компьютером в компьютере» около дюжины разработчиков и других активистов. После выхода каждой новой версии они компилируют двоичные файлы и публикуют полученные хэши, чтобы остальные могли с ними ознакомиться. Кроме того, они подписывают эти хэши своими открытыми PGP-ключами.
В теории это кажется простым, но на практике обеспечить функционирование этой системы всегда было очень трудно. Не так много проектов с открытым исходным кодом, которые используют Gitian — насколько нам известно, только Bitcoin Core и Tor. Даже большинство, если не все, альткоиновые форки ПО Bitcoin Core не утруждают себя этим процессом.
Зависимости, зависимости, зависимости
Но это не единственная проблема.
Допустим, вы читаете условия использования Facebook и находите в них ссылку на какой-то другой документ — например, на все законы США об авторском праве. Теперь вам придется прочитать и их.
Аналогичным образом, недостаточно проверить только код Bitcoin Core, потому что, как и большинство компьютерных программ, он использует массу других данных, известных как зависимости, в основном в виде библиотек. А каждая библиотека, в свою очередь, может использовать какую-то другую библиотеку, и так далее до бесконечности. Поэтому вам нужно проверить и их.
Разработчики Bitcoin Core вынуждены соблюдать ряд ограничений, и одно из них заключается в необходимости свести к минимуму количество зависимостей и не допустить их постоянное обновление. Каждое такое обновление требует дополнительной проверки. Конечно, администраторы, которые обслуживают эти зависимости, знают, что Bitcoin Core их использует; тем более необходимо быть начеку и тщательно проверять и эти проекты тоже.
Если окажется, что в зависимости заложен вредоносный код, вы можете лишиться своих монет. И это произошло — как минимум в одном не связанном с ВС проекте, в 2018 году. Вредоносный код был заложен в зависимость зависимости зависимости в ПО кошелька Copay. К счастью, его удалось быстро обнаружить. Что произошло: часть программного обеспечения использовала открытый исходный код, который могли просмотреть все желающие. Но этот код использовал зависимости, а эти зависимости использовали зависимости, и так далее.
Разработчики использовали npm — менеджер пакетов в экосистеме Node.js, которая представляет собой большое сообщество разработчиков, использующих открытый исходный код; и это разветвленная модульная система.
Каждый пакет связан с репозиторием на GitHub и имеет отдельного технического администратора, который может выпускать обновления по своему усмотрению. Типичное ПО для электронного кошелька может косвенно включать 10 000 зависимостей. Можно начать с пяти зависимостей, и каждая из них потянет за собой 50 зависимостей, а те потянут за собой еще 50 зависимостей. Если хотя бы один из разработчиков или технических администраторов любого из этих пакетов будет скомпрометирован, он может добавить вредоносное ПО для кражи монет.
Электронный кошелек на базе JavaScript вроде Copay хранит приватные ключи пользователя в памяти браузера. К сожалению, это очень незащищенное место, доступ к которому может получить любой фрагмент JavaScript. Вот так вредоносный код, спрятанный в зависимости нижнего уровня, может похитить монеты.
Дополнительную информацию см. в этой статье: (https://www.synopsys.com/blogs/software-security/malicious-dependency-supply-chain/).
В качестве недавнего примера непроизвольного использования зависимостей, которое привело к ужасным последствиям, можно привести сагу с Log4j (https://english.ncsc.nl/topics/log4j-vulnerability).
Решение
В связи с этим возникает вопрос: как же решить эту проблему? К сожалению, только уменьшив зависимость от зависимостей. Если это неизбежно, важно использовать как можно меньше зависимостей, и особенно важно в максимальной степени исключить элементы с вложенными зависимостями нижнего уровня.
С Bitcoin Core ситуация обстоит не так уж плохо, потому что у него не так много зависимостей, и у этих зависимостей не так много вложенных зависимостей. Так что дерево зависимостей не такое уж большое, и иерархия не такая уж глубокая, поэтому для атаки придется прямо выискивать конкретные зависимости.
Кто компилирует компилятор?
Чуть выше мы объяснили, как Gitian помогает создавать детерминированные сборки. Но что если Gitian или любой из инструментов, которые он использует, будет скомпрометирован?
Например, поскольку Gitian использует Ubuntu, кто-то может сказать: «Вау, какой крутой биткоин-проект. И проект Ubuntu тоже крутой. Почему бы мне не добавить немного исходного кода в Ubuntu?». Этот «вклад» может быть незначительным изменением компилятора, который поставляется с Ubuntu — его можно изменить таким образом, что при компиляции ВТС он будет включать код для кражи монет, но при компиляции любого другого ПО будет работать нормально.
Этот пример немного притянут за уши, и любого, кто попытается это сделать, скорее всего, найдут задолго до того, как он успеет нанести сколько-нибудь серьезный ущерб; программы компиляторов и Ubuntu проверяются гораздо тщательнее, чем, например, экосистема Node.js, которая упоминалась выше. Но общая стратегия атаки будет такой же. А когда на кону триллион долларов, злоумышленники могут быть очень изобретательными и очень терпеливыми.
Теперь предположим, что все запустят свой компилятор Gitian, который включает этот гипотетически взломанный компилятор Ubuntu. Это было бы очень, очень страшно, потому что сборки все равно будут детерминированными, так как все используют для сборки одно и то же вредоносное ПО.
Существует два вида зависимостей: одна — это зависимость, которую вы активно используете; она содержится в двоичном файле, который вы отправляете своим клиентам. Но есть и другая зависимость (и это не менее опасная проблема) — это все инструменты, которые вы используете для создания двоичного файла и даже для его загрузки.
Так что если хотя бы один из инструментов, которые разработчики используют для создания Bitcoin Core, будет скомпрометирован, детерминированные сборки не помогут. Каждый разработчик, использующий процесс компиляции Gitian, будет старательно создавать одно и то же вредоносное ПО. Двоичные файлы не будут соответствовать содержанию исходного кода.
Остается надеяться, что те, кто обслуживает и поддерживает все эти компиляторы и другие инструменты разработчиков, знают свое дело и никогда не пропустят бэкдор. Это проблема не только пользователей биткоина. Весь мир зависит от этого контроля, который в основном осуществляется активистами исключительно на добровольных началах.
Можем ли мы улучшить эту ситуацию?
Представляем Guix
Главное — перевести все программные продукты ВС на открытый исходный код и сделать все сборки детерминированными. Каждую библиотеку, каждый драйвер принтера, каждый компилятор — все. Чтобы Bitcoin Core действительно был детерминированной сборкой, каждая из его зависимостей должна быть детерминированной сборкой, как и каждый инструмент, используемый для компиляции, включая компилятор. В идеале аппаратное обеспечение тоже должно быть детерминированным, но это уже совсем другая тема. (https://media.ccc.de/v/36c3-10690-open_source_is_insufficient_to_solve_trust_problems_in_hardware).
Тут на сцену вступает Guix (https://guix.gnu.org/). Этот проект ОС GNU существует уже десять лет, но несколько лет назад Карл Донг (https://twitter.com/carl_dong) из Chaincode Labs (https://chaincode.com/) начал работать над заменой Gitian на Guix, и эта замена наконец-то была реализована в Bitcoin Core версии 22. (https://bitcoin.org/en/releases/22.0/). Для этого пришлось внести изменения как в Bitcoin Core, так и в Guix.
Для чего нужен Guix: [См. также презентацию Карла Донга: https://www.youtube.com/watch?v=I2iShmUTEl8] Итак, изначально у вас есть несколько сотен байт реального машинного кода. Это двоичный код, которому вы должны доверять [Даже двоичный код можно рассматривать как исходный код. Это просто серия инструкций для процессора. И этот конкретный машинный код тщательно задокументирован: https://git.savannah.nongnu.org/cgit/stage0.git/tree/README.org]. И он просто читает исходный код и компилирует его. Но как это сделать без компилятора?
Первый двоичный блок выполняет самозагрузку (bootstrap) [Это все в теории, мы еще не дошли до этого. В 2020 году проект Guix поставлялся с бинарными файлами объемом 60 МБ, которым нужно было доверять. Это значительный прогресс по сравнению со сборкой Ubuntu весом больше 1 ГБ, которую использует Gitian: https://guix.gnu.org/en/blog/2020/guix-further-reduces-bootstrap-seed-to-25/]. Он способен прочитать машинный код, набранный человеком или введенный другим способом, и запустить программу.
Сначала через него пропускают очень простой компилятор [Компилятор превращает человекочитаемый код в двоичный, который может прочитать компьютер. Это подразумевает, что первый компилятор написан на двоичном коде, который должен быть максимально компактным и хорошо задокументированным]. Как только создан простейший компилятор, этот новый компилятор читает следующий фрагмент исходного кода, который затем создает немного более сложный компилятор. Этот чуть более сложный компилятор создает еще один компилятор. И так продолжается довольно долго, пока, в конце концов, не появляется современный компилятор на языке С, который мы все знаем и любим, и который, конечно же, имеет открытый исходный код.
Далее этот компилятор компилирует несколько инструментов на основе исходного кода и в итоге получается система, очень похожая на Gitian — то есть система сборки, в которой не используются временные метки, не используются никакие другие элементы с вашего компьютера и т. д. В принципе, так можно скомпилировать целую операционную систему. Тогда на вашей виртуальной или физической машине будет работать операционная система, которую вы создали с нуля. Но в данном случае компилятор просто собирает инструменты компилятора, и как только эти скомпилированные инструменты будут готовы, он может просто начать сборку Bitcoin Core в обычном режиме.
Это решает две проблемы. Во-первых, в Guix нет ненадежных зависимостей, он не использует непроверенные библиотеки. Во-вторых, он всегда использует одни и те же версии библиотек, а значит, все пользователи получают одинаковый результат.
Обратите внимание: Guix не решает проблему зависимостей полностью. Это значительное улучшение по сравнению с Gitian, но все равно очень важно свести количество зависимостей к минимуму. Главное достоинство Guix заключается в уменьшении проблемы доверия к системе компиляции.