Базовые навыки работы с ключами и адресами, или как не просрать крипту которая была так рядом.

  • Автор темы Admin

Admin

#1
Администратор
Регистрация
31.12.2019
Сообщения
6,977
Реакции
34
На кого ориентирована статья?
На людей которые иногда находят приватные ключи, на людей которые планируют их поискать, на тех кто хотел бы как то автоматизировать рабочие процессы связанные с ключами, на всех кто не хочет втыкать в длинные мануалы с формулами но работать с криптой все же намерен на ты, на тех кому нужны простые коды для манипуляций с адресами и ключами но искать подходящий сдк а потом выковыривать из 500 страниц те 30 строк кода что им нужно нет времени, на всех тех кого заинтересовали анонсированные мной статьи.

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

Что в статье есть?
Разбор и из чего состоит приватный ключ.
Типы приватных ключей.
Какие адреса могут соответствовать ключу.
Разбор адресов.
Создание разных типов адресов для ключей.
Односложные коды на питоне, односложные это когда код делает что то одно, коды коротенькие в стиле копипасти и пользуй, для их пользования знать питон не нужно.
Рассмотренные валюты - биткоин подобные, эфир подобные, монеро(кратко).
Так же рассмотрим закрытые паролем приватные ключи BIP38, освоим создание и брут таких ключей.

Чего в статье нет?
Скучных формул не будет, их вообще никаких не будет, и грузева которое ни к чему толком не применить тоже.
Мы не будем разбирать формулы и алгоритмы, мы будем разбирать только необходимую для _практического_ использования суть и только в необходимом для практического использования объеме, для тех кому нужны теории и формулы - лучше тоже начать с этого материала а потом прочитать анонсированные мною статьи и уже после топать на биткоин вики и тому подобные ресурсы.


Почему это важно знать?
Потому что одному приватному ключу могут соответствовать несколько адресов, более того если ты нашел ключик то имеет смысл по нему проверить не только все доступные адреса но так же проверить и адреса для его сжатой\несжатой версии но и этого мало, еще имеет смысл создать адреса по этому ключу для других коинов(к которым ваш ключик вроде бы не имеет никакого отношения).
Вы все врети! так не бывает что бы на ключе для битка лежали например лайткоины! - конечно же не бывает мой друг =), иди себе ле..., в смысле спи спокойно дорогой товарищ.

Пример.
Допустим нашли вы приват KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp что вы получите импортируя этот ключ в кошелек, например в электрум, скорее всего доступ к средствам по адресу 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9, если вы опытный импортер ключей и импортируете его 3 раза указывая префиксы то получите доступ к еще двум адресам 3PFpzMLrKWsphFtc8BesF3MGPnimKMuF4x и bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5, реально в одном только биткоине на этот ключ стоит проверить 5 адресов, вот 2 которые пропустят большинство опытных импортеров 1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a, 39MLXh2vuGyzCuNVTepwPdEwLuU1oRQ1Qp. А еще на этом привате может быть и альта хотя вроде бы ключ от биков, и на альте тоже не 1 вариант адреса стоит проверять. Вот эта статья про то как получить из приват ключа наборы адресов которые стоит проверить.

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

А теперь мой друг оставь свой скепсис и идем со мной в тот белый фургончик с надписью "Конфеты", нас ждет увлекательное путешествие, поехали...


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


Как будет подаваться материал.
Я буду стараться подавать материал в максимально разжеванной форме, считаю что лучше я буду объяснять очевидное и на очевидное же указывать чем полагать что читатель сам все поймет, а он может не понять потому что тема для него совсем новая или потому что он перегружен какими то своими заботами и не в состоянии проявить должную догадливость, моя цель дать базовое знание а не устраивать экзамены на сообразительность. И еще про подачу знаний, я не так давно взялся учить питон и заметил что лучше всего усваиваю материал написанный детьми\подростками, они пишут очень просто про то что выучили сами, при этом пишут про действительно значимую суть не углубляясь в дебри и в зачастую вообще ненужные на начальном этапе тонкости, так же они в целом настроены на строго практическую часть применения. Я решил по возможности брать с этих детей пример. Так что если решили научится кодить(и не только) обратите внимание на статьи подростков.


Часть теоретическая - Внешний вид ключей и что под крышкой.

Биткоин и ему подобные валюты.
Биткоин-подобными валютами я буду называть те криптовалюты которые генерируют пары приватный ключ -> адрес схожим образом.
Краткий список подобных валют, bitcoin, bitcoin cash, litecoin, zcash(transparent part), dogecoin, dash....и многие другие, какие? - ну например bitconnect =).

Для начала научимся понимать связь приватных ключей и адресов.

Приватные ключи и адреса от них.
Как правило приватные ключи представлены в виде WIF(wallet export format) в base58 кодировке, или в закрытой паролем bip38, но о них будет позже.

И так приватный ключ WIF -
KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp
закодирован он алгоритмом base58checked(base58 с контрольной суммой)

раскодируем его в хекс представление и резберем что там (это очень важно уметь и понимать для того что бы собирать другие версии ключей и ключи под другие коины)
под крышкой
80111111111111111111111111111111111111111111111111111111111111111101969b59a7

по составляющим
80 - код сети, определяет какой сети принадлежит данный ключ (главная, тестовая,....еще какая то... как правило нам это будет не сильно важно.)
1111111111111111111111111111111111111111111111111111111111111111 (а вот это собственно и есть сам ключ, 256бит(32байт, 64символа хекс представления) данных которые как правило случайны, именно из этих данных будет сформирован адрес)
01 - это флажок указывает что нужно сформировать сжатый публичный ключ и это _ВАЖНО_ поскольку влияет на то какой именно адрес будет сформирован
969b59a7 - а это контрольная сумма всего что было выше

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

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

вариант p2pkh(pay to pubkey hash):
base58checked
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9
под крышкой
00fc7250a211deddc70ee5a2738de5f07817351cef90dffab8

по составляющим
00 - ид код
fc7250a211deddc70ee5a2738de5f07817351cef - тело адреса
90dffab8 - контрольная сумма

вариант p2sh-segwit(pay to script hash):
base58checked
3PFpzMLrKWsphFtc8BesF3MGPnimKMuF4x
data: id 05, body ec8f3d9c2763a0997a465b968d99db47e82e69d2, checksum c5e119fd


вариант bech32 может быть создан только от сжатого приватного ключа
bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5
bc - префикс указывающий на биткоин(то что до первой еденички считается префиксом)
крайние 6 символов справа чексумма

под крышкой
fc7250a211deddc70ee5a2738de5f07817351cef
(отмечаем что это то же самое что и под крышкой у 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9) _НО!_ это не значит что адреса в чейне идентичны и если есть какая то история транзакций у 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9 то и у bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5 она будет такой же - _это разные адреса_.


Теперь не сжатый вариант того же самого ключа
5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh
под крышкой
801111111111111111111111111111111111111111111111111111111111111111e5ce7258

id 80, body 1111111111111111111111111111111111111111111111111111111111111111, compression none, checksum e5ce7258
как видим все различие со сжатым в отсутствии флага сжатия 01.

А теперь делаем адреса.

вариант p2pkh(pay to pubkey hash):
1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a
data: id 00, body e4e517ee07984a4000cd7b00cbcb545911c541c4, checksum 532576dd

вариант p2sh-segwit(pay to script hash):
39MLXh2vuGyzCuNVTepwPdEwLuU1oRQ1Qp
data: id 05, body 540837f1a3bf5871d3cb4c80c49b506d6003088e, checksum b9f7b8bd

А вариант bech32 может быть создан только от сжатого приватного ключа.

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

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


Общим планом в сравнении с альтой.

BTC:private:mainnet
KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 01 969b59a7
1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9 - p2pkh:legacy:base58check 00 fc7250a211deddc70ee5a2738de5f07817351cef compressed unknown 90dffab8
3PFpzMLrKWsphFtc8BesF3MGPnimKMuF4x - p2psh:p2sh-segwit:base58check 05 ec8f3d9c2763a0997a465b968d99db47e82e69d2 compressed unknown c5e119fd
bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5 - bech32:legacy:bech32 fc7250a211deddc70ee5a2738de5f07817351cef compressed by default
BTC:private:mainnet
5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 not compressed e5ce7258
1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a - p2pkh:legacy:base58check 00 e4e517ee07984a4000cd7b00cbcb545911c541c4 compressed unknown 532576dd
39MLXh2vuGyzCuNVTepwPdEwLuU1oRQ1Qp - p2psh:p2sh-segwit:base58check 05 540837f1a3bf5871d3cb4c80c49b506d6003088e compressed unknown b9f7b8bd

LTC:private:mainnet
T3d9oMBFZGSUVybiNUVhdn4HcqNayswbGBYcer117nRiWT8Gph6i - WIF:base58check b0 1111111111111111111111111111111111111111111111111111111111111111 01 6df78793
LiEmVJEDLtUR6EJebVbBTLBpEkTg9d3Tx3 - p2pkh:legacy:base58check 30 fc7250a211deddc70ee5a2738de5f07817351cef compressed unknown cee45770
3PFpzMLrKWsphFtc8BesF3MGPnimKMuF4x - p2psh:p2sh-segwit:base58check 05 ec8f3d9c2763a0997a465b968d99db47e82e69d2 compressed unknown c5e119fd
ltc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880mvk54y - bech32:legacy:bech32 fc7250a211deddc70ee5a2738de5f07817351cef compressed by default
LTC:private:mainnet
6uFXzdHphqp1LhvBHCDPoPtwy9hEbBdqYXwHgqiEcyWcgpVMRxT - WIF:base58check b0 1111111111111111111111111111111111111111111111111111111111111111 not compressed 9c5e6d70
Lg6EmeK1sbbfJh2PJQ2NkYwCHM2buUDWjT - p2pkh:legacy:base58check 30 e4e517ee07984a4000cd7b00cbcb545911c541c4 compressed unknown d82e64c2
39MLXh2vuGyzCuNVTepwPdEwLuU1oRQ1Qp - p2psh:p2sh-segwit:base58check 05 540837f1a3bf5871d3cb4c80c49b506d6003088e compressed unknown b9f7b8bd

BCH:private:mainnet
KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 01 969b59a7
bitcoincash:qr78y59zz80dm3cwuk388r097pupwdguauvqmahfks - p2pkh:legacy:bitcoincash 00 fc7250a211deddc70ee5a2738de5f07817351cef 0c001b1d17091610
bitcoincash:prkg70vuya36pxt6gededrvemdr7stnf6g8d8uyqtr - p2psh:p2sh-segwit:bitcoincash 08 ec8f3d9c2763a0997a465b968d99db47e82e69d2 070d071c04000b03
BCH:private:mainnet
5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 not compressed e5ce7258
bitcoincash:qrjw29lwq7vy5sqqe4aspj7t23v3r32pcszzn5dxqp - p2pkh:legacy:bitcoincash 00 e4e517ee07984a4000cd7b00cbcb545911c541c4 020213140d060001
bitcoincash:pp2qsdl35wl4suwnedxgp3ym2pkkqqcg3cvnc4nthc - p2psh:p2sh-segwit:bitcoincash 08 540837f1a3bf5871d3cb4c80c49b506d6003088e 0c131815130b1718

ZEC:private:mainnet
KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 01 969b59a7
t1gtRERLXEZ1xS4fPMnR1K8DyHCHUjAgWni - p2pkh:legacy:base58check 1cb8 fc7250a211deddc70ee5a2738de5f07817351cef compressed unknown 0a9d3117
ZEC:private:mainnet
5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh - WIF:base58check 80 1111111111111111111111111111111111111111111111111111111111111111 not compressed e5ce7258
t1ejtWmRKmG9CeXP84grCcLyMKnrQWQccdH - p2pkh:legacy:base58check 1cb8 e4e517ee07984a4000cd7b00cbcb545911c541c4 compressed unknown 4a607a6c

DASH:private:mainnet
XBroosGSTa6KnTyDrbYhvehvazzrMbXwpYzGKZhesBXePig6zouV - WIF:base58check cc 1111111111111111111111111111111111111111111111111111111111111111 01 3e818834
Xyhf4LaHDwSwzND5HEv72qoqrsg625rCMt - p2pkh:legacy:base58check 4c fc7250a211deddc70ee5a2738de5f07817351cef compressed unknown 7ceb758f
DASH:private:mainnet
7qgNw7riZkkM7gBvfRbNWNgdcLwLnuEMaRvJX2yLTKhUWBUdZ14 - WIF:base58check cc 1111111111111111111111111111111111111111111111111111111111111111 not compressed 29b7f7d3
XwZ8Lgf5keaCCpvoz9MJL4ZDuUF1msarf7 - p2pkh:legacy:base58check 4c e4e517ee07984a4000cd7b00cbcb545911c541c4 compressed unknown 83e3e64e

DOGE:private:mainnet
QPBoWSh4QVwzxfMtR7PdJeMX91kqxM4WLELGafmJHAruSVa7vey5 - WIF:base58check 9e 1111111111111111111111111111111111111111111111111111111111111111 01 a9566cb0
DU9umLs2Ze8eNRo69wbSj5HeufphJawFPh - p2pkh:legacy:base58check 1e fc7250a211deddc70ee5a2738de5f07817351cef compressed unknown 608949bc
DOGE:private:mainnet
6JG8pTDFNBdvuwTUxm3Qdd71V6cJtWU5P2fMJ9L6jBPdofeGnc6 - WIF:base58check 9e 1111111111111111111111111111111111111111111111111111111111111111 not compressed f9bf8c2f
DS1P3gwq6MFtatWprr2e2J32xGPd89n75C - p2pkh:legacy:base58check 1e e4e517ee07984a4000cd7b00cbcb545911c541c4 compressed unknown fb4c39b3

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

подозреваю что уже назрел вопрос, где брать эти ид?
попробуйте здесь - https://github.com/libbitcoin/libbi...-Mappings#bip44-altcoin-version-mapping-table


Шифрованные приватные ключи BIP38.
bip38 это это зашифрованный фразой паролем приватный ключ и как правило ключи передаются именно в таком формате, так те кто в теме именно в таком формате их хранят.

и так берем наш ключ KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp = 80 1111111111111111111111111111111111111111111111111111111111111111 01 969b59a7
шифруем фразой test
получаем
base58checked
6PYMgbeR4p9fYsZdM9awYQyXUneuFy6U5zhDwJphGEWZgEToGLffEG3nMY
под крышкой
0142e036eb8cf915e05ebcb96d421215fc9ec0a7a1f8190459d30fcb3fc273b9cf05493299fddde82c72db

для наглядного сравнения шифруем фразой test1
6PYMgbeR6JyUzUQsPAvUMDx4RAuQg5N1mZSMnz1ruZ1cEbwwWMs8Shpyav
0142e036eb8cf9db05a0296ae268b2dcfee73801742d465b2b9b9c1ab3fd5fa561393897bd4afd743be2c7

рассмотрим первый который шифровали фразой test
под крышкой (детально можно прочесть здесь https://en.bitcoin.it/wiki/BIP_0038)
0142 - алгоритм (так же может быть 0143)
e0 - флаги
36eb8cf9 - контрольная сумма для проверки верности при расшифровке
15e05ebcb96d421215fc9ec0a7a1f8190459d30fcb3fc273b9cf05493299fddd - зашифрованное тело (1111111111111111111111111111111111111111111111111111111111111111)
e82c72db контрольная сумма

Теперь вы знаете как опознать такие ключи внешне (начинаются с 6P и длина 58 символов), а в практическом разделе узнаете как их создавать и даже как брутить.


Мастер ключи.
- А вот тетя Люба сказала что самые главные это мастер ключи!

Мастер ключи это те которые такие длинные и начинаются на xprv..., zprv..., yprv..., от них генерируются рассмотренные выше приватные ключи и если вы раздобыли такой ключик то считай что раздобыли весь кошель..., но есть неприятное но, и это но такое что как я понял из практики каждый софт генерирует приваты от мастера как ему хочется. Я сравнивал биткойнКТ, электрум и биткойнлиб и получил разные приваты на один и тот же мастер, так что это как с сид фразами, их мало найти надо еще знать от чего она и иметь алг что бы получить от нее пары приват->адрес. Но я в разделе алгоритмов приведу фрагмент кода который из мастера получает приват по версии bitcoinlib.


Эфир.
Эфир-подобные валюты, мне известен эфир и эфир классик, но я думаю что есть еще что то на данном алге.

Для эфира все проще 1 приват = 1 адрес.
Адрес может быть с контрольной суммой (она определяет будет ли в адресе буква в верхнем или нижнем регистре), а может быть и без.
приват
1111111111111111111111111111111111111111111111111111111111111111
адрес с контрольной суммой
0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A
адрес без контрольной суммы
19e7e376e7c213b7e7e7e46cc70a5dd086daff2a


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

1111111111111111111111111111111111111111111111111111111111111111
XMR: inundate ridges slug inundate ridges slug inundate ridges slug inundate ridges slug inundate ridges slug inundate ridges slug inundate ridges slug inundate ridges slug inundate
secret_spend_key: 243d1bb4f6adfeb83a74196e321732fc10111111111111111111111111111101
secret_view_key: 779e4dd2c49ac3c0b2edcd1b843c795b7d6eb51457125bb9c90339b752f23700
public_spend_key: 857eed804ff087b97f87848f6493e87257a8c5203cb9f422f6e7a7d8a4d299f3
public_view_key: 0489cb98c7108372eaff2cdeddc5e76166b017a847537bf8499d61465395e942
public_address: 46gXNFZinyUY2Zk5wJNro1L8GMSDCdCiF6rCZ3JKr3VJheaRvUyBHqtLDrC6jY6TLzHHux42kSzgiiXhcuDuceP28Y2x8vA

заметим что часть основы отразилась в secret_spend_key, но наверное это не имеет никакой важности.


Часть практическая - Алгоритмы.

Получение биткоин подобного приватного WIF ключа из 256 бит hex.

Код:
import base58
id = '80' # id main net btc
compressed = '' # не сжатый!
body = '1111111111111111111111111111111111111111111111111111111111111111'
x = base58.b58encode_check(bytes.fromhex(id + body + compressed))
print(x.decode('utf8')) # >> 5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh

Получим base58checked WIF
5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh

Получение hex из base58checked WIF.

Код:
С проверкой контрольной суммы, при выдаче результата она будет _отброшена_.

import base58
xx = base58.b58decode_check('5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh')
print(xx.hex()) # >> 801111111111111111111111111111111111111111111111111111111111111111

Получим
801111111111111111111111111111111111111111111111111111111111111111


Без проверки контрольной суммы.

import base58
xx = base58.b58decode('5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh')
print(xx.hex()) # >> 801111111111111111111111111111111111111111111111111111111111111111e5ce7258

Получим
801111111111111111111111111111111111111111111111111111111111111111e5ce7258

Эту операцию можно проделывать с любым base58 например с 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9.

Делаем приват+адрес для доге, вариант для сжатого.

Код:
мы знаем что по биткоин алгоритму для приват ключа 1111111111111111111111111111111111111111111111111111111111111111 тело адреса fc7250a211deddc70ee5a2738de5f07817351cef

Делаем wif приват для доге.
import base58
id = '9e' # id main net doge
compressed = '01'
body = '1111111111111111111111111111111111111111111111111111111111111111'
x = base58.b58encode_check(bytes.fromhex(id + body + compressed))
print(x.decode('utf8'))

Получим
QPBoWSh4QVwzxfMtR7PdJeMX91kqxM4WLELGafmJHAruSVa7vey5

теперь адрес
import base58
id = '1e' # doge address id
body = 'fc7250a211deddc70ee5a2738de5f07817351cef'
x = base58.b58encode_check(bytes.fromhex(id + body))
print(x.decode('utf8'))

Получим
DU9umLs2Ze8eNRo69wbSj5HeufphJawFPh

Приведенные далее примеры потребуют установки питон библиотек

https://pypi.org/project/bitcoinlib
https://pypi.org/project/pycryptodome/
https://pypi.org/project/ecdsa/

Получение адресов от приватных ключей.

Код:
import bitcoinlib
import pprint
private_hex = '1111111111111111111111111111111111111111111111111111111111111111'
print(f"private {private_hex}")
"""
• import_key (str, bytes, int) – HD Key to import in WIF format or as byte with key (32 bytes) and chain (32 bytes)
• key (bytes) – Private or public key (length 32)
• chain (bytes) – A chain code (length 32)
• depth (int) – Level of depth in BIP32 key path
• parent_fingerprint (bytes) – 4-byte fingerprint of parent
• child_index (int) – Index number of child as integer
• is_private (bool) – True for private, False for public key. Default is True
• network (str, Network) – Network name. Derived from import_key if possible
• key_type (str) – HD BIP32 or normal Private Key. Default is ‘bip32’
• password (str) – Optional password if imported key is password protected
• compressed (bool) – Is key compressed or not, default is True
• encoding (str) – Encoding used for address, i.e.: base58 or bech32. Default is base58 or derive from witness type
• witness_type (str) – Witness type used when creating scripts: legacy, p2sh-segwit or segwit.
• multisig (bool) – Specify if key is part of multisig wallet, used when creating key representations such as WIF and addreses
"""
for compressed in [True, False]:
for encoding in ["base58", "bech32"]:
for witness_type in ["legacy", "p2sh-segwit", "segwit"]:
print(f"compressed {compressed}")
print(f"encoding {encoding}")
print(f"witness_type {witness_type}")
try:
t = bitcoinlib.keys.HDKey(import_key=private_hex, compressed=compressed, is_private=True,
encoding=encoding, witness_type=witness_type)
td = t.as_dict()
pprint.pprint(td)
except Exception as e:
print(f"not support {e}")
print("---------------")

Получим
private 1111111111111111111111111111111111111111111111111111111111111111
compressed True
encoding base58
witness_type legacy
OrderedDict([('network', 'bitcoin'),
('key_format', 'hex'),
('compressed', True),
('is_private', True),
('public_hex',
'034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa'),
('public_uncompressed_hex',
'044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1'),
('hash160', 'fc7250a211deddc70ee5a2738de5f07817351cef'),
('address', '1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9'),
('point_x',
35826991941973211494003564265461426073026284918572421206325859877044495085994),
('point_y',
25491041833361137486709012056693088297620945779048998614056404517283089805761),
('child_index', 0),
('depth', 0),
('extended_wif_public',
'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6ScvyfAttx2ok2qXKKf1VhdwGdNwkJ79r2763SzM9Xha7yBd3kk')])
compressed True
encoding base58
witness_type p2sh-segwit
OrderedDict([('network', 'bitcoin'),
('key_format', 'hex'),
('compressed', True),
('is_private', True),
('public_hex',
'034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa'),
('public_uncompressed_hex',
'044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1'),
('hash160', 'fc7250a211deddc70ee5a2738de5f07817351cef'),
('address', '3PFpzMLrKWsphFtc8BesF3MGPnimKMuF4x'), # обратите внимание что этот адрес не содержит в себе fc7250a211deddc70ee5a2738de5f07817351cef, то есть hash160 хоть и является основой для формирования этого адреса, но содержимое адреса ec8f3d9c2763a0997a465b968d99db47e82e69d2
('point_x',
35826991941973211494003564265461426073026284918572421206325859877044495085994),
('point_y',
25491041833361137486709012056693088297620945779048998614056404517283089805761),
('child_index', 0),
('depth', 0),
('extended_wif_public',
'ypub6QqdH2c5z7965qdFmUJxeajk39gR3mywuPXWvK6XLAwfmtVjbHXcckYc7eaWyZppJb9cVWS5mz1ZNzFVzKnxYXnkiMoWdMoqRFmDWXRwBbb')])
compressed True
......и так далее...

Распаковка и запаковка bech32.

Код:
import bitcoinlib
t = 'bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5'
t = bitcoinlib.encoding.addr_to_pubkeyhash(t)
print(t.hex()) # >> fc7250a211deddc70ee5a2738de5f07817351cef

t = bytes.fromhex('fc7250a211deddc70ee5a2738de5f07817351cef')
prefix = 'bc'
t = bitcoinlib.encoding.pubkeyhash_to_addr(pubkeyhash=t, prefix=prefix, encoding="bech32")
print(t) # >> bc1ql3e9pgs3mmwuwrh95fecme0s0qtn2880lsvsd5

Получаем адрес для эфира.

В то как именно работает этот код _вникать_не_обязательно_, это просто инструмент который можно взять и использовать.

Код:
import Crypto.Hash.keccak
import ecdsa
prv_key_bytes = bytes().fromhex('1111111111111111111111111111111111111111111111111111111111111111')
signing_key = ecdsa.SigningKey.from_string(prv_key_bytes, curve=ecdsa.SECP256k1)
verifying_key = signing_key.get_verifying_key()  # public key
# pub to address
keccak_hash = Crypto.Hash.keccak.new(digest_bits=256)
keccak_hash.update(verifying_key.to_string())

keccak_digest = keccak_hash.hexdigest()
# Take the last 20 bytes
address = keccak_digest[-40:]  # last 20 bytes is the address
print(f"address without checksum {address}")

checksum = '0x'
# Remove '0x' from the address
address_byte_array = address.encode('utf-8')
keccak_hash = Crypto.Hash.keccak.new(digest_bits=256)
keccak_hash.update(address_byte_array)
keccak_digest = keccak_hash.hexdigest()
for i in range(len(address)):
    address_char = address[i]
    keccak_char = keccak_digest[i]
    if int(keccak_char, 16) >= 8:
        checksum += address_char.upper()
    else:
        checksum += str(address_char)
print(f"address with checksum {checksum}")

Получим
address without checksum 19e7e376e7c213b7e7e7e46cc70a5dd086daff2a
address with checksum 0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A

Получение ключа для BitcoinCash.

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

Код:
class BitcoinCashAddr:
    _CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

    @staticmethod
    def _polymod(values):
        """Internal function that computes the cashaddr checksum."""
        c = 1
        for d in values:
            c0 = c >> 35
            c = ((c & 0x07ffffffff) << 5) ^ d
            if (c0 & 0x01):
                c ^= 0x98f2bc8e61
            if (c0 & 0x02):
                c ^= 0x79b76d99e2
            if (c0 & 0x04):
                c ^= 0xf33e5fb3c4
            if (c0 & 0x08):
                c ^= 0xae2eabe2a8
            if (c0 & 0x10):
                c ^= 0x1e4f43e470
        retval = c ^ 1
        return retval

    @staticmethod
    def _prefix_expand(prefix):
        """Expand the prefix into values for checksum computation."""
        retval = bytearray(ord(x) & 0x1f for x in prefix)
        # Append null separator
        retval.append(0)
        return retval

    @staticmethod
    def _create_checksum(prefix, data):
        """Compute the checksum values given prefix and data."""
        values = BitcoinCashAddr._prefix_expand(prefix) + data + bytes(8)
        polymod = BitcoinCashAddr._polymod(values)
        # Return the polymod expanded into eight 5-bit elements
        return bytes((polymod >> 5 * (7 - i)) & 31 for i in range(8))

    @staticmethod
    def _convertbits(data, frombits, tobits, pad=True):
        """General power-of-2 base conversion."""
        acc = 0
        bits = 0
        ret = bytearray()
        maxv = (1 << tobits) - 1
        max_acc = (1 << (frombits + tobits - 1)) - 1
        for value in data:
            acc = ((acc << frombits) | value) & max_acc
            bits += frombits
            while bits >= tobits:
                bits -= tobits
                ret.append((acc >> bits) & maxv)

        if pad and bits:
            ret.append((acc << (tobits - bits)) & maxv)

        return ret

    @staticmethod
    def _pack_addr_data(kind, addr_hash):
        """Pack addr data with version byte"""
        version_byte = kind << 3

        offset = 1
        encoded_size = 0
        if len(addr_hash) >= 40:
            offset = 2
            encoded_size |= 0x04
        encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset)

        # invalid size?
        if ((len(addr_hash) - 20 * offset) % (4 * offset) != 0
                or not 0 <= encoded_size <= 7):
            raise ValueError('invalid address hash size {}'.format(addr_hash))

        version_byte |= encoded_size

        data = bytes([version_byte]) + addr_hash
        return BitcoinCashAddr._convertbits(data, 8, 5, True)

    @staticmethod
    def _decode_payload(addr):
        """Validate a cashaddr string.

        Throws CashAddr.Error if it is invalid, otherwise returns the
        triple

           (prefix,  payload)

        without the checksum.
        """
        lower = addr.lower()
        if lower != addr and addr.upper() != addr:
            raise ValueError('mixed case in address: {}'.format(addr))

        parts = lower.split(':', 1)
        if len(parts) != 2:
            raise ValueError("address missing ':' separator: {}".format(addr))

        prefix, payload = parts
        if not prefix:
            raise ValueError('address prefix is missing: {}'.format(addr))
        if not all(33 <= ord(x) <= 126 for x in prefix):
            raise ValueError('invalid address prefix: {}'.format(prefix))
        if not (8 <= len(payload) <= 124):
            raise ValueError('address payload has invalid length: {}'
                             .format(len(addr)))
        try:
            data = bytes(BitcoinCashAddr._CHARSET.find(x) for x in payload)
        except ValueError:
            raise ValueError('invalid characters in address: {}'
                             .format(payload))

        if BitcoinCashAddr._polymod(BitcoinCashAddr._prefix_expand(prefix) + data):
            raise ValueError('invalid checksum in address: {}'.format(addr))

        if lower != addr:
            prefix = prefix.upper()

        # Drop the 40 bit checksum
        return prefix, data[:-8], data[-8:]

    #
    # External Interface
    #

    PUBKEY_TYPE = 0
    SCRIPT_TYPE = 1

    @staticmethod
    def decode(address: str):
        '''Given a cashaddr address, return a triple

              (prefix, kind, hash)
        '''
        if not isinstance(address, str):
            raise TypeError('address must be a string')

        prefix, payload, checksum = BitcoinCashAddr._decode_payload(address)

        # Ensure there isn't extra padding
        extrabits = len(payload) * 5 % 8
        if extrabits >= 5:
            raise ValueError('excess padding in address {}'.format(address))

        # Ensure extrabits are zeros
        if payload[-1] & ((1 << extrabits) - 1):
            raise ValueError('non-zero padding in address {}'.format(address))

        decoded = BitcoinCashAddr._convertbits(payload, 5, 8, False)
        version = decoded[0]
        addr_hash = bytes(decoded[1:])
        size = (version & 0x03) * 4 + 20
        # Double the size, if the 3rd bit is on.
        if version & 0x04:
            size <<= 1
        if size != len(addr_hash):
            raise ValueError('address hash has length {} but expected {}'
                             .format(len(addr_hash), size))

        kind = version >> 3
        if kind not in (BitcoinCashAddr.SCRIPT_TYPE, BitcoinCashAddr.PUBKEY_TYPE):
            raise ValueError('unrecognised address type {}'.format(kind))

        return prefix, kind, addr_hash, checksum, decoded

    @staticmethod
    def decode_ex(address: str) -> str or None:
        if address:
            if ":" not in address:
                address = f"bitcoincash:{address}"
            try:
                prefix, kind, addr_hash, checksum, decoded = BitcoinCashAddr.decode(address)
                return decoded.hex() + checksum.hex()
            except Exception as e:
                return

    @staticmethod
    def encode(prefix, kind, addr_hash):
        """Encode a cashaddr address without prefix and separator."""
        if not isinstance(prefix, str):
            raise TypeError('prefix must be a string')

        if not isinstance(addr_hash, (bytes, bytearray)):
            raise TypeError('addr_hash must be binary bytes')

        if kind not in (BitcoinCashAddr.SCRIPT_TYPE, BitcoinCashAddr.PUBKEY_TYPE):
            raise ValueError('unrecognised address type {}'.format(kind))

        payload = BitcoinCashAddr._pack_addr_data(kind, addr_hash)
        checksum = BitcoinCashAddr._create_checksum(prefix, payload)
        return ''.join([BitcoinCashAddr._CHARSET[d] for d in (payload + checksum)])

    @staticmethod
    def encode_ex(version_byte: str, addr_hash: str, prefix: str = "bitcoincash",
                  full: bool = False) -> str or None:
        try:
            b = bytes().fromhex(version_byte)
            k = int.from_bytes(b, byteorder="little")
            k = k >> 3
            if full:
                if not prefix:
                    prefix = "bitcoincash"
                return BitcoinCashAddr.encode_full(prefix, k, bytes.fromhex(addr_hash))
            else:
                return BitcoinCashAddr.encode(prefix, k, bytes.fromhex(addr_hash))
        except Exception as e:
            return

    @staticmethod
    def encode_full(prefix, kind, addr_hash):
        """Encode a full cashaddr address, with prefix and separator."""
        return ':'.join([prefix, BitcoinCashAddr.encode(prefix, kind, addr_hash)])


x = BitcoinCashAddr.decode_ex('qr78y59zz80dm3cwuk388r097pupwdguauvqmahfks')
print(x)  # 00fc7250a211deddc70ee5a2738de5f07817351cef0c001b1d17091610
y = BitcoinCashAddr.encode_ex(x[:2], x[2:42], "bitcoincash")
print(y)  # qr78y59zz80dm3cwuk388r097pupwdguauvqmahfks

Получение ключа для Monero XMR.

Создание ключа и адреса для монеро, требует достаточно много кода, более того у монеро нет такой связи как приватный ключ -> адрес, там от корневого ключа создается серия ключей, и разбор алгоритма для данной статьи будет лишним, тем более что в следующей статье посвященной инструментарию код для монеро будет представлен и все желающие смогут его себе скопипастить.

Шифруем и расшифровываем BIP38.

Код:
import bitcoinlib
p = 'test1'
ks = 'KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp'
k = bitcoinlib.keys.Key(ks)
print(k.private_hex)  # >> 1111111111111111111111111111111111111111111111111111111111111111
ek = k.bip38_encrypt(p)
print(ek)  # >> 6PYMgbeR6JyUzUQsPAvUMDx4RAuQg5N1mZSMnz1ruZ1cEbwwWMs8Shpyav
# decryptt
k2 = bitcoinlib.keys.Key(ek, password=p)
print(k2.private_hex)  # >> 1111111111111111111111111111111111111111111111111111111111111111
print(k2.wif()) # >> KwntMbt59tTsj8xqpqYqRRWufyjGunvhSyeMo3NTYpFYzZbXJ5Hp

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

Код:
import bitcoinlib
    ek = '6PYMgbeR6JyUzUQsPAvUMDx4RAuQg5N1mZSMnz1ruZ1cEbwwWMs8Shpyav'
    kd = """1
2
one more incorrect password
3
test
test1
"""
    for p in kd.splitlines():
        try:            
            k2 = bitcoinlib.keys.Key(ek, password=p)
            print(f"password: {p}, key body {k2.private_hex}")
            break
        except Exception as e:
            pass
    else:
        print("password not found")

Пример для мастер ключа.
Кто все же хочет повозится с мастер ключами вот отправная точка.
Эта ссылка будет полезна для понимания что такое "m/44'/0'/0'/0/0" -> https://en.bitcoin.it/wiki/BIP_0044

Код:
import bitcoinlib
import pprint
key = 'xprv9s21ZrQH143K4LvcS93AHEZh7gBiYND6zMoRiZQGL5wqbpCU2KJDY87Txuv9dduk9hAcsL76F8b5JKzDREf8EmXjbUwN1c4nR9GEx56QGg2'
t = bitcoinlib.keys.HDKey(import_key=key)
td = t.as_dict()
pprint.pprint(td)
# Path format: m / purpose’ / coin_type’ / account’ / change / address_index

x = t.subkey_for_path("m/44'/0'/0'/0/0")
pprint.pprint(x.wif_key())
pprint.pprint(x.address())

Ссылки.
Кому нунжна более подробная информация вам куда то сюда:
https://en.bitcoin.it/wiki/Wallet_import_format
https://en.bitcoin.it/wiki/Invoice_address
Коды валют:
https://github.com/libbitcoin/libbi...-Mappings#bip44-altcoin-version-mapping-table
https://en.bitcoin.it/wiki/List_of_address_prefixes
bip38:
https://en.bitcoin.it/wiki/BIP_0038

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

Автор: CryptoBoy
 

Members, viewing this thread

Сейчас на форуме нет ни одного пользователя.