Метка: адрес

Майним красивый Bitcoin адрес на Python

КДПВ: чувак долбит скалу киркой в поисках монет

Сегодня покажу, как найти (намайнить) себе красивый адрес Bitcoin кошелька. Красивый адрес, значит, что его первые несколько символов будут заданым нами словом.

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

Итак, я хочу, чтобы мой биткоин адрес начинался с какого-то слова, например, с названия моего канала PyWay. Нулевой символ, увы, мы изменить не можем, так как он связан с идентификатором сети биткоин (главная, segwit или тестовая). Поэтому оставим его в покое. Часть строки с первого символа должна начинаться с заданного слова:

PATTERN = 'PyWay'

# ф-ция проверки
def predicate(addr: str):
    return addr[1:].startswith(PATTERN)

Адреса всегда получаются случайными, поэтому нам нужен метод грубой силы (брутфорс) – многократная генерация адресов, пока не найдем подходящий:

from bit import Key

while True:
    k = Key()  #  новый случайны ключ
    if predicate(k.segwit_address):
        print(f'{k.segwit_address} with WIF private key {k.to_wif()}')
        break

Время поиска зависит от длины шаблона экспоненциально!

Короткие последовательности ищутся быстро, а длинные – экстремально долго.

Так 2-3 символьные слова ищутся почти моментально. 4 символа заставляют задуматься, а 5 – ждать минут 10-20. 6 символов – могут уйти часы. И так далее! Это криптография!

Чтобы ускорить поиск, запустим несколько процессов поиска с помощью модуля multiprocessing:

from bit import Key
from multiprocessing import Process, Value


PATTERN = 'PyWay'

# сколько процессов?
N_PROCESSES = 10


# предикат проверки адреса на наше желание
def predicate(addr: str):
    return addr[1:].startswith(PATTERN)


# рабочая функция
def worker(predicate: callable, stop: Value, counter: Value):
    # пока нам не посигналили о завершении из другого процесса
    while not stop.value:
        # новый ключ
        k = Key()
        
        # проверяем
        if predicate(k.segwit_address):
            print('done!')
            print(f'{k.segwit_address} with WIF private key {k.to_wif()}')
            
            # сигналим другим и выходим
            stop.value = True 
            break
            
        # каждые 10_000 итераций – точку рисуем
        counter.value += 1
        if counter.value % 10_000 == 0:
            print('.', end='')


if __name__ == '__main__':
    # эти переменные необычные - они позволяют обмениваться инфой между процессами
    stop = Value('b', False)
    counter = Value('i', 0)

    procs = []
    for worker_id in range(N_PROCESSES):
        # создадим процесс, передав рабочего и аргументы
        proc = Process(target=worker, args=(predicate, stop, counter))
        proc.start()
        procs.append(proc)

    # будем ждать пока все процессы не завершаться
    for proc in procs:
        proc.join()  # ждет процесс

FAQ

Почему у меня долго не находит даже простое слово?

Возможно, проблема в вашем слове. В нем есть символы, которых не может быть в адресе: o, O, 0, l, I. Они похожи по написанию, и были исключены, чтобы избежать лишних ошибок при передаче адресов. Естественно, в адресе не может быть русских букв и знаков пунктуации. Только английские буквы (кроме тех, что привел выше) и цифры кроме 0!

Почему бы не искать последовательно?

Если начать проверять все адреса подряд, это будет немного быстрее. Главное не начинать с 0 и с круглых чисел, иначе ключ будет легко подобрать! Пример последовательного поиска со случайной начальной точки seed:

seed = random.randint(0, 1_000_000_000_000)

while not stop.value:
    k = Key.from_int(seed)
    seed += 1

Почему процессы, а не потоки?

Из-за GIL. Потоки будут тормозить друг друга, а процессы работают независимо.

Если хочу слово в конце адреса или в середине?

Поменяйте условие:

return addr.endswith(PATTERN)  # в конце

return PATTERN in addr  # можно в середине 

PATTERN = PATTERN.lower()
return PATTERN in addr.lower()  # в любом месте без учета регистра - самый быстрый поиск

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Генерируем Bitcoin-адрес на Python

Тема криптовалют снова начинает будоражить интернет. Супер, что вам не надо идти в отделение банка с паспортом и выстаивать очередь, чтобы открыть счет. Сгенерировать кошелек Bitcoin — дело нескольких строк кода на Python.

Нам понадобятся библиотеки base58 и ecdsa. base58 – это кодирование бинарных данных 58-ю печатными символами (цифрами и латинскими буквами, кроме 0, O, I, l, которые похожи друг на друга). ecdsa – библиотека криптографии на эллиптических кривых.

pip install base58 ecdsa

Импортируем то, что нужно:

import hashlib
import ecdsa
from binascii import hexlify
from base58 import b58encode

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

private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)

Вычислим этой же библиотекой публичный ключ и добавим спереди байт 0x4 (это признак «несжатого» публичного ключа; есть и другие форматы).

public_key = b'\04' + private_key.get_verifying_key().to_string()

Теперь нужно из публичного ключа сделать привычный число-буквенный адрес Bitcoin. Взглянем на схему:

Схема генерации адреса BTC из публичного ключа.

Для получения адреса из публичного ключа вычисляем сначала RIPEMD160(SHA256(public-key)):

ripemd160 = hashlib.new('ripemd160')
ripemd160.update(hashlib.sha256(public_key).digest())

Дополняем его префиксом 0x0 (главная сеть Bitcoin):

r = b'\0' + ripemd160.digest()

Вычисляем контрольную сумму (нужна, чтобы наши денюжки не пропадали, если мы ошибемся в каком-то символе адреса). Контрольная сумма это первые 4 байта от SHA256(SHA256(r)):

checksum = hashlib.sha256(hashlib.sha256(r).digest()).digest()[0:4]

Получаем адрес кошелька, закодировав в base58 сложенные r и checksum:

address = b58encode(r + checksum)

Выведем результат:

print(f'private key: {hexlify(private_key.to_string())}')
print(f'public key uncompressed: {hexlify(public_key)}')
print(f'btc address: {address}')

Генерация приватного ключа из своего источника случайностей, например, os.urandom:

def random_secret_exponent(curve_order):
    while True:
        bytes = os.urandom(32)
        random_hex = hexlify(bytes)
        random_int = int(random_hex, 16)
        if random_int >= 1 and random_int < curve_order:
            return random_int


def generate_private_key():
    curve = ecdsa.curves.SECP256k1
    se = random_secret_exponent(curve.order)
    from_secret_exponent = ecdsa.keys.SigningKey.from_secret_exponent
    return from_secret_exponent(se, curve, hashlib.sha256).to_string()

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

Полный пример кода генерации кошельков.

Проверить ключи и адрес можно здесь. (Нажимаем Skip, дальше Enter my own…)

Подробнее по теме можно почитать здесь.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈