Как я проверил более 1 триллиона мнемоник за 30 часов, чтобы выиграть биткойн
История началась с того что некий Алистер Милн написал в Твиттере, что он планирует отдать 1 биткойн в кошельке, созданном с использованием мнемоники из 12 слов тому, кто первый сможет взломать эту мнемонику.
- Известно, что для 8-ми известных слов существует 2⁴⁰ (~ 1,1 триллиона) возможных мнемоник.
- Чтобы протестировать одну мнемонику, мы должны сгенерировать семя из мнемоники, главный закрытый ключ из семени и адрес из главного закрытого ключа.
Ок. Звучит несложно. Беремся за дело.
- Я написал версию процессора на Rust для оценки производительности решателя процессора. Мой Macbook мог проверять только ~ 1250 мнемоник в секунду, что означает, что на проверку всех 2⁴⁰ возможных мнемоник потребовалось бы около 25 лет .
- Я перенес весь необходимый код для генерации и проверки мнемоники (SHA-256, SHA-512, RIPEMD-160, EC Addition, EC Multiplication) на OpenCL C, язык программирования для запуска кода на графическом процессоре.
- Версия с графическим процессором могла проверять ~ 143 000 мнемоник в секунду, что означает, что для проверки всех 2⁴⁰ возможных мнемоник потребуется ~ 83 дня.
- Я написал серверное приложение, которое будет организовывать распределение работы на пакеты по ~ 16 миллионов мнемоник для пула рабочих GPU. Каждый рабочий GPU будет запрашивать у сервера следующий пакет работы, выполнять работу и записывать результат обратно на сервер.
- Я потратил ~ 350 долларов на аренду графических процессоров у обширного.ai (плюс ~ 75 долларов бесплатно от Azure).
- Я беспокоился о том, что другие люди делают то же самое, и поэтому я включил в заранее подготовленную транзакцию огромную комиссию 0,01 BTC.
Я не думал, что даже этого будет достаточно, и подумал, что может быть «гонка до нуля», когда люди постоянно будут увеличивать комиссию, пытаясь заставить майнеров включить именно свою транзакцию в следующий блок.
А теперь подробнее как все происходило:
Алистер Милн написал в Твиттере, что планировал раздать 1 биткойн в кошельке, созданном с помощью мнемоники из 12 слов.
Я предположил что это означало, что он был сгенерирован с использованием мнемоники BIP-39 и позже это подтвердилось, когда он предоставил первые несколько начальных слов и, в конечном итоге, весь список слов BIP-39 в твите.
Мнемоника BIP-39 генерируется с использованием слов из фиксированного списка из 2048 потенциальных слов. Каждое слово представлено целым числом от 0 до 2047, соответствующим его позиции в списке. В двоичном формате вам нужно 11 бит для представления числа до 2047.
Следовательно, для представления мнемоники из 12 слов вам потребуется 12 * 11 или 132 бита. Известно что, BIP-39 использует 128 бит случайности, а затем использует SHA-256 для генерации 4-битной контрольной суммы, которая добавляется к концу исходных 128-бит, чтобы получить необходимые 132 бита для 12 слов.
Это означает, что если мы хотим повторить все возможные мнемонические символы из 12 слов, нам нужно считать от 0 до 2¹²⁸-1, где каждое число можно интерпретировать как единственную мнемонику, затем полученное преобразовать в адрес, а затем сравнить с тем самым адресом, содержащим 1 BTC. На первый взгляд это кажется довольно простым, но мы быстро понимаем, что это число слишком велико для простого перебора.
К счастью, нам не нужно на самом деле проверять все возможности 2¹²⁸, потому что Алистер собирался выпускать 1 исходное слово каждые пару дней. Каждое исходное слово, которое мы собираем, сокращает варианты, которые нам нужно проверить, в 2¹¹ или 2048 раз.
Он также упомянул, что собирался выпустить последние 3 или 4 слова сразу, чтобы предотвратить брутфорс (по крайней мере, он так думал), поэтому это означало, что мне нужно было бы иметь возможность использовать прямой перебор по крайней мере последних 4х слов чтобы мой план сработал, и я успел бы получить достаточно времени, чтобы забрать приз.
С 8ю словами это означает, что мы знаем 8 * 11 или 88 бит из 128 бит, которые мы пытаемся вычислить. Это означает, что есть «только» 2 ^ (128–88) или 2⁴⁰ возможных мнемоник, которые нам нужно будет проверить. Это 1 099 511 627 776 или примерно 1,1 триллиона возможных мнемоник.
Я не был уверен, сколько времени потребуется, чтобы опробовать 1 триллион возможностей, поэтому я написал быструю программу, чтобы получить базовый тест, чтобы у меня было некоторое представление о том, с чем я имею дело, и если бы я думал, что это вообще возможно делать.
Стратегия, которую я собирался использовать, заключалась в вычислении начального и конечного числа, которые мне нужно было перебирать, на основе набора известных входных слов. Для каждого числа я вычислял адрес, соответствующий этому числу, а затем проверял, был ли адрес тем, который содержал 1 BTC. Если бы это был адрес, я бы создал и отправил транзакцию, чтобы перевести средства в кошелек, который я контролирую.
Первую версию, которую я написал на Rust, использовал существующие библиотеки (rust-bitcoin, rust-wallet и кольцо) для обработки всех хешей и математики эллиптических кривых. Проведение некоторых быстрых тестов показало, что использование ЦП для этого нецелесообразно.
Мой ноутбук (двухъядерный Intel Core i7 с тактовой частотой 2,5 ГГц) мог выполнять только ~ 1250 мнемоник для проверки адреса в секунду или около 108000000 в день. Это означает, что моему процессору потребуется около 25 лет, чтобы сгенерировать и проверить 1 триллион возможностей, необходимых для взлома мнемоники, зная только 8 слов.
Чтобы достичь этого за 1 день, мне нужно было бы улучшить производительность примерно в 9000 раз по сравнению с текущей скоростью.
Моя следующая попытка заключалась в том, чтобы арендовать более мощную машину, чтобы посмотреть, насколько быстро может работать та же версия только для ЦП. Я арендовал у Digital Ocean 32-ядерный компьютер, оптимизированный для ЦП, и смог записать эталонный тест ~ 8000 в секунду, что всего в ~ 6 раз лучше, чем у моего ноутбука.
Для этого мне все равно потребуется увеличение производительности примерно в 1000 раз. Я не думал, что смогу приблизиться к этому, пытаясь оптимизировать код ЦП, поскольку он, вероятно, уже был довольно хорошо оптимизирован в существующих библиотеках, которые я использовал.
Вводим графический процессор
За последнее десятилетие наблюдается рост использования графических процессоров для выполнения программирования общего назначения. В Биткойне мы увидели это относительно рано, когда люди начали использовать графические процессоры для выполнения операций, необходимых для майнинга.
Так как же именно графический процессор помогает нам быстрее решить эту проблему? Оказывается, одно ядро графического процессора на самом деле медленнее, чем его аналог ЦП, когда оно используется для программирования общего назначения. Прирост производительности обычно наблюдается, когда вы можете эффективно распараллелить программу. Это связано с тем, что одно устройство с графическим процессором обычно имеет тысячи ядер, которые вы можете использовать для своих вычислений.
К счастью для нас, наша задача очень хорошо распараллеливается. Каждое из 2⁴⁰ чисел, которые мы хотим проверить, выполняет одно и то же вычисление (число -> мнемоника -> начальное число -> главный закрытый ключ -> адрес). Это означает, что мы можем дать каждому ядру графического процессора 1 номер для попытки и можем запустить тысячи попыток параллельно.
Я быстро узнал об OpenCL , стандартном языке программирования с открытым исходным кодом для написания программного обеспечения, которое будет работать практически на любом устройстве с графическим процессором. OpenCL C очень похож на язык программирования C с некоторыми отличиями. Одно из этих отличий заключается в том, как работает память. В графическом процессоре вам доступны четыре основных типа памяти (глобальная, постоянная, локальная и частная). Глобальная память распределяется между всеми ядрами графического процессора и очень медленная для доступа, вы хотите максимально сократить ее использование. Постоянная и частная память работают очень быстро, но ограничены в пространстве. Я считаю, что большинство устройств поддерживают только 64 КБ постоянной памяти. Локальная память используется «группой» рабочих, и ее скорость находится где-то между Global и Constant.
Моя цель состояла в том, чтобы вместить все, что мне нужно, в 64 КБ постоянной памяти и никогда не читать из глобальной или локальной памяти, чтобы максимизировать скорость программы. Это оказалось немного сложно, потому что стандартная предварительно вычисленная таблица умножения secp256k1 сама по себе занимала ровно 64 КБ. К счастью, мне удалось предварительно вычислить меньшую таблицу, которая занимала всего 32 КБ, но работала примерно на 75% медленнее, чем полная таблица. Список слов BIP-39 занял еще ~ 20 КБ, а хэши SHA2 заняли еще ~ 6 КБ, так что я уже использовал ~ 58 КБ из 64 КБ, доступных мне с самого начала. Это оставило мне около 6 КБ пространства для маневра, с которым можно было поработать.
В идеале я мог бы выполнять все вычисления на графическом процессоре. Это означало число -> мнемоника -> начальное число -> главный закрытый ключ -> адрес, рассчитанный графическим процессором и записанный в OpenCL.
Что именно необходимо реализовать для обработки каждого из этих шагов? Давайте углубимся в каждый шаг, который нам нужно сделать, чтобы пройти путь от числа до адреса биткойна. Если вас не интересуют эти детали, просто пропустите статью, чтобы перейти к разделу « Реализация в OpenCL ».
Преобразование числа в мнемонику BIP-39 из 12 слов
Давайте посмотрим на пример того, как можно преобразовать число в семя из 12 слов.
Сначала давайте начнем с действительно большого числа:
34,267,283,446,455,273,173,114,040,093,663,453,845
Отсюда нам нужно преобразовать это число в 128-битное число в двоичном формате.
00011001110001111010001110000011110100110001011100011001001000100111110101001000101010000011111100000111001010100110001010010101
Мы можем получить последние 4 бита (контрольную сумму), вычислив хэш SHA-256 этого значения и взяв первые 4 бита результата. В этом случае мы получаем контрольную сумму 0101.
Теперь добавляем контрольную сумму в конец и разбиваем наши 132 бита на группы по 11 бит:
| 00011001110 | 00111101000 | 11100000111 | 10100110001 | 01110001100 | 10010001001 |
11110101001 | 00010101000 | 00111111000 | 00111001010 | 10011000101 | 00101010101 |
Затем мы конвертируем каждую группу из 11 бит в число, представляющее индекс:
| 206 | 488 | 1799 | 1329 | 908 | 1161 | 1961 | 168 | 504 | 458 | 1221 | 341 |
Наконец, мы используем их как индексы в английском списке слов BIP-39, чтобы найти каждое соответствующее слово:
граница циферблат мысль пластик огромный булочка яркий скамейка болезнь олень очевидный щелчок
Вот как мы можем отобразить любое число в мнемонику из 12 слов. Этот шаг стоит нам всего 1 вычисление SHA-256.
Мнемонику в Seed
Следующий шаг - взять эту 132-битную мнемоническую строку и использовать ее для генерации 64-байтового двоичного начального числа. Как расширить 132-битную строку до 64 байтов?
BIP-39 делает это, используя функцию деривации ключа на основе пароля с HMAC-SHA512 в качестве хэш-функции, строку «мнемоника» в качестве соли и мнемонику из 12 слов в качестве пароля. Он также использует 2048 итераций, и каждая итерация требует двух вычислений SHA512. Это означает, что этот шаг будет стоить в общей сложности ~ 4096 вычислений SHA-512.
Это похоже на то, как многие веб-сайты хранят хешированные пароли в своей базе данных. Основная идея состоит в том, чтобы замедлить процесс угадывания большого количества паролей при попытке перебора хеш-кода чьего-либо пароля. Вы можете контролировать время, необходимое для проверки одного пароля, увеличивая количество итераций или используя более медленную хеш-функцию, такую как scrypt или bcrypt.
Seed в главный закрытый ключ BIP-32
Как только у нас есть сид, нам нужно преобразовать его в кошелек BIP-32 (HD). Вы можете прочитать полный BIP для получения всех подробностей, но на высоком уровне BIP-32 определяет способ создания главного закрытого ключа из начального числа, а затем использовать эту главную пару ключей для генерации до 2⁵¹² дочерних пар ключей.
Это отличное решение для создания программного обеспечения кошелька, поскольку оно позволяет пользователю легко создавать резервные копии одного секрета (их мнемоники), но при этом иметь возможность генерировать почти бесконечные адреса (для всех практических целей). У него есть и другие приятные преимущества, когда вы можете генерировать дочерние открытые ключи без необходимости наличия закрытых ключей (отлично подходит для предприятий, которым необходимо генерировать адреса приема без необходимости иметь закрытые ключи на своем сервере).
Чтобы преобразовать наше семя в главный закрытый ключ в соответствии с BIP-32, нам нужно вычислить HMAC-SHA512 («биткойн-семя», mnemonic_seed). HMAC-SHA512 выдает 64-байтовый вывод. Мы берем первые 32 байта в качестве главного закрытого ключа, а остальные 32 байта используются позже как «цепной код» для «расширения» ключа при генерации дочерних пар ключей.
Для вычисления HMAC-SHA512 из 132-байтовых входных данных потребуется всего два вычисления SHA-512.
Главный закрытый ключ для адреса
Этот шаг включает в себя получение нашего главного закрытого ключа BIP-32 и получение дочерних пар ключей на основе пути получения, необходимого для доступа к адресу, который содержит биткойны. Если мы посмотрим на адрес в проводнике, мы увидим, что он был сгенерирован с использованием BIP-49 P2WPKH-nested-in-P2SH .
Это означает, что путь деривации имеет формат m / 49 '/ coin_type' / account '/ change / address_index.
Определение пути деривации было огромным риском для этого проекта. Я предположил, что Алистер просто сгенерировал новый кошелек, и единственная сделка заключалась в внесении 1 BTC. При таком предположении это означает, что путь деривации для первого адреса будет m / 49 '/ 0' / 0 '/ 0/0.
Это означает, что нам нужно взять главный закрытый ключ и сгенерировать три защищенных закрытых ключа, а затем два обычных закрытых ключа.
Каждый защищенный закрытый ключ требует вычисления HMAC-SHA-512 (2 хэша SHA-512) и одного скалярного добавления secp256k1.
Каждый нормальный закрытый ключ имеет те же требования, что и усиленный ключ, плюс необходимость вычислять связанный открытый ключ из закрытого ключа. Чтобы вычислить открытый ключ, нам нужно выполнить умножение эллиптической кривой скаляра, представленного закрытым ключом, на точку G генератора группы secp256k1.
Последний шаг - взять рассчитанный открытый ключ и преобразовать его в адрес P2SHWPKH. Это включает в себя создание правильного сценария и последующее использование hash160 (RIPEMD-160, за которым следует SHA-256) для получения адреса, а затем SHA256 (дважды) для вычисления 4-байтовой контрольной суммы.
В общей сложности этот шаг стоит 10 хешей SHA-512, 3 SHA-256 и 1 RIPEMD-160. Это также стоит нам 5 скалярных сложений EC и 3 умножения EC.
Общая стоимость
Мы должны проделать все эти шаги для КАЖДОЙ мнемоники, которую хотим попробовать:
Число в мнемонику - 1 SHA-256
Мнемоника в семя - 4096 SHA-512
От начального до закрытого ключа - 2 SHA-512
Закрытый ключ для адреса - 10 SHA-512, 3 SHA-256, 1 RIPEMD-160, 5 добавлений EC, 3 умножения EC
На первый взгляд кажется, что этап генерации начального числа будет самым медленным, хотя без некоторых тестов сложно понять, как сравнить хэш SHA-512 с операциями EC с точки зрения затрат. Оказывается, оба они относительно медленны по сравнению с другими этапами, но генерация семян, по крайней мере, на порядок дороже, чем другие.
Реализация в OpenCL
Итак, чтобы реализовать весь этот поток в OpenCL, мне понадобится способ выполнения SHA-256, SHA-512, RIPEMD-160, сложения EC и умножения EC. Мне также нужно было бы организовать все это вместе, чтобы решить мою проблему.
Моя стратегия заключалась в том, чтобы найти реализации всех алгоритмов на C с открытым исходным кодом и перенести их на OpenCL C. Я начал с SHA-256 и SHA-512, потому что только они позволили мне вычислить число -> мнемоника -> начальное число, и я знал, что создание семени было самым медленным этапом.
В конце концов, я получил генерацию семян, работающую в OpenCL, и мой первый тест с использованием nVidia 2080Ti показал мне, что я могу генерировать 142 857 семян в секунду. Ух ты! Теперь мы куда-то шли.
Это означало, что одна nVidia 2080Ti могла генерировать ~ 12 миллиардов семян в день. Это по-прежнему означало, что для создания всего триллиона семян потребуется 83 дня . Это было намного лучше, чем 25 лет, которые собирался занять мой процессор. Однако это все еще генерировало только семена, и семя было недостаточно, чтобы знать, верна ли мнемоника или нет. Мне все равно нужно завершить процесс преобразования начального числа в адрес, чтобы иметь возможность проверить, правильная ли это мнемоника.
Затем я вернулся и протестировал версию начального числа для своего процессора, чтобы адресовать генерацию, чтобы увидеть, сколько времени это займет. 32-ядерная машина Digital Ocean могла обрабатывать около 52 000 семян в секунду. Это было довольно прилично, но после улучшений генерации семян OpenCL это стало узким местом, и на его завершение все равно уйдет более 221 дня. Кроме того, мне нужно было бы скоординировать перемещение семян, сгенерированных из GPU, обратно в CPU, чтобы завершить их обработку. Эта координация потребует времени и более сложной программы для написания.
Моей целью было перенести весь расчет в GPU.
Это означало, что мне нужно было уметь выполнять математику EC (сложение и умножение) в OpenCL. Я взял реализацию libsecp256k1 с открытым исходным кодом, которую использует Биткойн, и перенес ее на OpenCL C. Для этого мне потребовалось сначала понять, как была структурирована библиотека libsecp256k1 и что именно мне нужно было портировать, чтобы сгенерировать адрес из главного закрытого ключа.
Оказалось, что мне нужно было портировать около 2000 строк кода. К счастью, OpenCL C и стандартный C достаточно похожи, поэтому особых изменений не потребовалось. Изменения касались того, что я реализовал некоторые функции управления памятью, такие как memcpy, memset и memzero, которые OpenCL не поддерживает. Это также вовлекло меня в удаление ослепления, которое выполняется при выполнении умножения EC и предварительное вычисление таблицы размером 32 КБ вместо таблицы 64 КБ по умолчанию.
После того, как все сказано и сделано, я провел несколько тестов на работоспособность и с удивлением обнаружил, что моя реализация OpenCL действительно работает правильно.
Затем я повторно провел тесты с использованием 2080Ti и увидел, что время, добавленное для вычисления адреса из начального числа с использованием графического процессора, было незначительным. Это добавляло всего несколько сотен миллисекунд на 1 миллион семян.
Теперь я смог запустить весь процесс на 100% на графическом процессоре, но все равно потребовалось ~ 80 дней, чтобы перечислить 1 триллион возможных мнемоник с использованием 2080Ti.
Затем я попытался запустить его на нескольких разных видеокартах (1080, 1080Ti, 2070, 2070Ti, Tesla K80, Tesla P100 и Tesla V100). К моему удивлению, производительность графических процессоров не сильно изменилась. Даже самая лучшая модель Tesla V100 была всего на 15% быстрее, но ее аренда стоила почти в 4 раза дороже.
Что касается нижнего предела, то 1080/1070 были примерно в 3-4 раза медленнее, чем 2080Ti, но стоили лишь примерно вдвое дешевле. 2080Ti казалась наиболее экономичной картой для решения этой проблемы. Сколько я смогу получить и как все это организовать?
Если бы я решил это за 24 часа, мне потребовалась бы мощность около 80 2080 Ti.
Оркестровка пула графических процессоров
Если бы я хотел распределить эту проблему между несколькими графическими процессорами, простой ответ - просто разбить 2⁴⁰ числа, которые нам нужно выполнить, на 80 равных частей и запустить каждую часть на одном графическом процессоре. К сожалению, все будет не так просто.
Сначала мне нужно было выяснить, как я собираюсь получить доступ ко всем этим машинам. Моя первая мысль заключалась в том, чтобы использовать основных облачных провайдеров (AWS, Google Cloud и Microsoft Azure). Я быстро узнал, что у этих компаний есть строгие квоты на количество графических процессоров, которые вы можете предоставить (некоторые из них НУЛЬ!) С новой учетной записью.
К счастью, я наткнулся на рынок графических процессоров (Vast.ai), который позволял людям, у которых были неиспользуемые графические процессоры, сдавать их в аренду любому, кто хотел получить доступ к графическим процессорам. У них был большой запас 1080 и 1080 Ti, но не так много 2080 Ti, как мне было нужно. Запасы постоянно колебались в зависимости от того, сколько людей их арендовало, сколько они были готовы заплатить и сколько провайдеров были онлайн в то время.
Это означало, что я не смогу легко выделить ровно 80 2080Ti и равномерно распределить свою рабочую нагрузку между ними.
В итоге я создал простой централизованный сервер, который будет распространять работу. Это очень похоже на то, как работает пул для майнинга. Каждый работник GPU будет делать запрос на централизованный сервер для выполнения пакета работы, выполнять работу, а затем записывать работу (и решение, если оно найдено) обратно на сервер. Каждый рабочий будет продолжать делать это в цикле до тех пор, пока больше не останется работы.
Это означало, что я мог легко раскручивать столько карт, сколько хотел, так быстро, как хотел, а центральный сервер мог отслеживать, какой будет следующий пакет работы, когда один будет запрошен. Каждому экземпляру worker не нужно было ничего знать о том, какую часть работы ему необходимо выполнить, он мог просто вслепую запросить у сервера следующий пакет для работы.
Это решение действительно увеличивает время задержки в сети. Мне нужно было сделать размер пакета достаточно большим, чтобы дополнительная сетевая задержка составляла небольшой процент от общего времени. В итоге я использовал размер пакета 16 777 216, что означает 65 536 пакетов работы для вычисления всех 2⁴⁰ возможных мнемоник.
Для расчета пакета из 16 777 216 2080Ti потребовалось чуть меньше 2 минут. Это означает, что задержка сети менее 1 секунды добавляла менее 1% дополнительного времени вычислений.
Тестирование системы
В итоге это оказалась более сложная система, чем я изначально предполагал, и я беспокоился, что может быть ошибка или что она не будет работать должным образом, когда придет время. В итоге я прошел полный тест, чтобы убедиться, что все действительно работает.
Я создал кошелек с новой мнемоникой BIP-39 и перевел в него 0,0001 BTC. Затем я инициировал свою систему с 9 известными начальными словами (так что это не заняло бы столько времени и не обошлось бы мне так дорого). Я арендовал пару 2080Ti на обширных территориях и позволил им разорваться. В течение 20 минут он нашел решение и переместил 0,0001 BTC на кошелек Trezor, который я контролировал. Я чувствовал себя готовым, хотя и не был уверен, что смогу вовремя получить достаточно вычислительной мощности, чтобы выполнить задачу, когда она понадобится.
Я чувствовал, что все еще есть способ оптимизировать код SHA-512, который я использовал, пытаясь перенести версию SHA-512, которую использовал hashcat, поскольку у них есть чрезвычайно оптимизированная версия. Поскольку SHA-512 был наиболее часто используемым методом и текущим узким местом, любые улучшения, внесенные здесь, могут резко сократить количество необходимых графических процессоров. Я был примерно на полпути к реализации, когда Алистер выпустил 8-е слово .
Важный день
Я немедленно выбросил оптимизированный SHA-512, над которым работал, и вернулся к той версии, с которой, как я знал, работал ранее. Я начал брать в аренду столько 2080Ti, 2080, 2070, 2070Ti, 1080Ti, сколько смог, на сайте huge.ai. Тем временем мне удалось увеличить свою квоту графического процессора в Azure (+ бесплатный кредит в размере 200 долларов для новых учетных записей), чтобы арендовать там до 40 графических процессоров. К сожалению, машины, к которым у меня был доступ, были примерно на 50% мощнее 2080Ti. Однако это означало, что я смог получить примерно 20 2080Ti бесплатно только от Azure.
На пике я тестировал около 40 миллиардов мнемоник в час. Это означает, что на проверку 1 триллиона мнемоник потребовалось около 25 часов. Я знал, что в среднем это займет только 50% времени (в зависимости от того, что на самом деле было девятым словом). Если слово начинается с A, то оно закончится через 1 час, а если начинается с Z, то это займет полных 25 часов. Конечно, скорость моего тестирования не была стабильной и составляла 40 миллиардов в час, поскольку машины на обширном.ai приходят и уходят, когда люди меня перебивают или отключаются. Мне приходилось постоянно просматривать список доступных машин и арендовать больше по мере их появления.
В течение дня, когда решение не было найдено, я забеспокоился, что оно не сработает, потому что мой подход мог потерпеть неудачу по многим причинам:
- Я предположил, что слова, которые он выпускал, были в правильном порядке. Если бы они не были в порядке, их было бы 8! (факториал) больше возможностей (что делает 8 слов практически невозможными для грубой силы), и мой код все равно не пробовал разные перестановки.
- Я предположил, что правильно сказал все 8 слов. Хотя большинство из них было очевидным, была пара, где я чувствовал, что могут быть другие варианты. Я не пробовал ни один из этих вариантов, только 8, которые я считал правильными.
- Я предположил, что он использовал первый адрес первой учетной записи кошелька HD, полученный по адресу m / 49 '/ 0' / 0 '/ 0/0. Если бы он использовал любой другой путь происхождения (второй или более поздний адрес), я бы его не нашел. Я ДЕЙСТВИТЕЛЬНО нервничал по этому поводу, потому что я не мог точно знать, какой путь деривации он использовал. К счастью, он использовал совершенно новый кошелек, не создавая дополнительных адресов, прежде чем внести 1 BTC.
После целого дня работы состояние моего рабочего сервера показало, что примерно на 85% пройдено тестирование всех возможностей, и я в значительной степени оставил надежду, что это сработает. Я буквально почти выключил его на этом этапе, чтобы реализовать версию, которая тестировала больше, чем просто первый адрес, потому что я был убежден, что предположение неверно.
Я не мог заставить себя действительно остановить это в тот момент, поскольку я зашел так далеко, поэтому я просто позволил этому продолжаться. К моему удивлению, немного позже в тот вечер (91%), и спустя почти 30 часов и ровно 1 триллион чеков (1 000 710 602 752) он нашел решение!
Я не мог поверить, что это сработало. Я нервно подключил свой Trezor, чтобы проверить баланс, чтобы убедиться, что я не испортил код, который сгенерировал и подписал транзакцию для очистки BTC. К моему облегчению, там было 0,99 BTC!
Затем я нервно ждал подтверждения. Я волновался, что другой человек (или Ева Алистер) попытается украсть мой биткойн, постоянно увеличивая комиссию майнера, чтобы майнер включил свою транзакцию в свою. Это одна из причин, по которой я начал с относительно высокой комиссии (0,01 BTC). У меня не было времени реализовать инструмент, который следил за мемпулом на предмет конкурирующих транзакций и автоматически увеличивал мою комиссию, но я подумал, что в будущем это будет хорошей идеей.
Однако через несколько минут моя транзакция была включена в блок. Вау, это действительно сработало!
Последние мысли
Я получил много удовольствия и многому научился, работая над этой проблемой. Я думал о способах проведения подобных розыгрышей, избегая при этом возможности того, что кто-то напишет программное обеспечение, чтобы выиграть. На самом деле это не так просто.
Даже если бы он решил выпустить последние 5 слов сразу, чтобы предотвратить грубое принуждение, у меня все равно могло бы быть запущено программное обеспечение, которое ждало, когда я введу каждое слово, когда я их разгадываю, предполагая, что это была какая-то головоломка, и автоматически подметал монеты быстрее, чем любой человек.
Я думал, что он мог бы использовать более глубокий путь деривации, и хотя моя первоначальная версия программного обеспечения пропустила бы это, было бы тривиально и не намного медленнее проверить множество путей деривации для используемого адреса. Я думаю, что проблема здесь даже для людей, программное обеспечение кошелька, которое обрабатывает процесс восстановления, сканирует только небольшое количество неиспользуемых адресов перед остановкой, поэтому он не мог углубиться в это, если бы хотел, чтобы пользователи могли использовать обычный кошелек, чтобы восстановить его.
Другая идея состоит в том, чтобы разбить слова по порядку, что сделало бы невозможным грубую силу на раннем этапе, но все же не мешает мне использовать программное обеспечение для перечисления всех возможных порядков и подметания btc быстрее, чем человек мог бы когда-либо надеяться сделать это однажды. было выпущено достаточно слов.
В конце концов, программное обеспечение всегда будет быстрее людей в выполнении такого рода задач. Я думаю, что единственный реальный способ - сделать обнаружение настоящих слов трудной частью. Это своего рода головоломка, решение которой дает вам все слова сразу, поэтому гонка больше связана с решением головоломки, чем с тем, насколько быстро вы можете вводить слова.
источник @johncantrell97