Метка: pyway

Майним красивый 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

КДПВ

Чтобы создать биткоин транзакцию в наше время не нужно прилагать много усилий. Есть специальные доверенные онлайн сервисы, которые отправят вашу транзакцию в сеть бесплатно (без учета комиссии сети) и безопасно. Вам даже не нужно устанавливать ноду биткоина локально и выкачивать весь блокчейн. Еще лучше то, что под Python есть супер-удобные библиотеки, чтобы пользоваться этими сервисами.

Давайте поставим библиотеку bit:

pip install bit

Ключи

Ключ – центральное понятие в мире Биткоина. Приватный ключ – это ваш кошелек. Вы храните его в секрете от всех. Публичный ключ получается из приватного. А адрес кошелька, получается из публичного ключа. Это преобразование одностороннее: нужно затратить колоссальный объем вычислительной мощности и миллиарды лет времени, чтобы к адресу подобрать приватный ключ и получить контроль над средствами. Я уже рассказывал о генерации ключей биткоин вручную. Но библиотека bit прекрасно делает это за вас.

У биткоина две сети – главная и тестовая. В каждой свои ключи и свои виды адресов. Генерация нового ключа для основной сети, где адреса обычно начинаются с цифры:

from bit import Key

key = Key()
print(k.address)  # 1C8REeQUnXE3HtLCYXVG21AryDxRXnnyar

Класс Key – псевдоним для PrivateKey:

from bit import Key, PrivateKey
print(PrivateKey is Key)  # true

Для демонстрационных целей, я буду использовать тестовую сеть. В ней монеты ничего не стоят и их легко получить. Адреса тестовой сети обычно начинаются с буквы m или n! Ключ тестовой сети описан классом PrivateKeyTestnet:

from bit import PrivateKeyTestnet

k = PrivateKeyTestnet()
print(k.address)  # mrzdZkt4GfGMBDpZnyaX3yXqG2UePQJxpM

Если мы в конструкторе класса ключа не указали параметров, то каждый раз создается новый (почти наверняка пустой – без баланса) адрес. Генерация происходит локально (без обращения к онлайн сервисам) и очень быстро. Но, если приватный ключ не сохранен, то после завершения программы доступ будет утерян. Поэтому сгенерируем приватный ключ и запишем его в блокноте. Адрес получается по свойству k.address, а приватный ключ можно получить в разных форматах, самый удобный из них – WIF (Wallet Export Format) – получаем строку методом k.to_wif():

from bit import PrivateKeyTestnet as Key

k = Key()

print('Private key:', k.to_wif())
print('Public address:', k.address)

# Private key: cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV
# Public address: mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe

Также по приватному ключу можно получить еще SegWit адрес. Если очень кратко, то этот адрес будет работать быстрее, чем традиционный.

print(k.segwit_address)  # 2MsWNuzx8EfgEeGLesLmkMM6q3kajEjVnVh

Воспользуемся биткоин краном, чтобы получить немного тестовых монет бесплатно:

Биткоин кран

Транзакция займет некоторое время (минут 10-20). Так что наберитесь терпения!

А пока она идет, создадим класс ключа уже из сохраненной секретной строки:

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k.address)  # mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe ура тот же!

Приватный ключ может быть представлен, как число, байты, HEX-строка, в WIF, DER и PEM форматах:

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')

print('Int:', k.to_int(), end='\n\n')
print('Hex:', k.to_hex(), end='\n\n')
print('Bytes:', k.to_bytes(), end='\n\n')
print('WIF:', k.to_wif(), end='\n\n')
print('DER:', k.to_der(), end='\n\n')
print('PEM:', k.to_pem(), end='\n\n')

Вывод:

Int: 4397583691621789343100573085...453641742227689755261559235

Hex: 6139710fb66e82b7384b868bda1ce59a0bd216e89b8808ae503c5767e4d461c3

Bytes: b'a9q\x0f\xb6n\x82\xb78K\x86\x8b\xd...d4a\xc3'

WIF: cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV

DER: b'0\x81\x84\x02\...xb3b\x8e\x1ar\xc6'

PEM: b'-----BEGIN PRIVATE KEY-----\nMIGEA.....O\nrRnD/Ls2KOGnLG\n-----END PRIVATE KEY-----\n'

Также, удобно создавать класс ключа из WIF формата функцией wif_to_key, она сама определит тип сети и создаст нужный класс:

from bit import wif_to_key

k = wif_to_key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k)  # <PrivateKeyTestnet: mhnmzFN5gr6gvmEr1t8UcRh6rdTh6JxuDe>

Надеюсь монеты с крана вам уже дошли, и мы продолжим.

Баланс

Узнаем баланс нашего кошелька. Для этого внутри bit используются онлайн сервисы (https://insight.bitpay.com, https://blockchain.info, https://smartbit.com.au). Поэтому операция не моментальная.

from bit import PrivateKeyTestnet as Key

k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
print(k.get_balance())  # 1391025

Как видите, на тот момент на адресе лежало 1391025 сатоши. 1 сатоши = одна стомиллионная целого биткоина (10-8) – самая маленькая неделимая частичка. Библиотека bit удобна еще тем, что содержит встроенный конвертер валют, поэтому баланс можно получить в любой поддерживаемой валюте: хоть в милибиткоинах, хоть в долларах, хоть в рублях. Просто передайте название валюты аргументом:

print(k.get_balance('mbtc'), 'MBTC')  # 13.91025 MBTC
print(k.get_balance('usd'), 'USD')  # 129.84 USD
print(k.get_balance('rub'), 'RUB')  # 8087.35 RUB

Как послать монеты?

Очень просто: методом send. Создадим еще один ключ (dest_k) и пошлем ему часть биткоинов от source_k:

from bit import PrivateKeyTestnet as Key

source_k = Key('cQqh9xFys2KJyWhHMaBwG2kFLCNBCmTgxVqnPTXK6Vng4vU6igoV')
dest_k = Key('cP2Z27v1ZaBz3VQRRSTQRhgYt2x8BtcmAL9zi2JsKaDBHobxj5rx')

print(f'Send from {source_k.address} to {dest_k.address}')

r = source_k.send([
    (dest_k.address, 0.0042, 'btc')
])

print(r)  # ID транзакции

Как вы помните, у транзакции может быть много выходов, поэтому первый аргумент функции send – список – кому и сколько мы посылаем (кортеж: адрес, количество, валюта). В данном случае адресат у нас один ‘n2R8xiqs6BqdgtqpXRDLKrN4BLo9VD171z’, а второй неявный выход – обратно наш же адрес, чтобы получить сдачу. Вот эта транзакция выглядит так:

Через 5 минут я уже получил первое подтверждение перевода! Проверим список транзакций:

transactions = source_k.get_transactions()
print(transactions)

# ['a101ad526e9fb131b90aac220b8b6e8bf11b9b9848ab8ea6d4384dc5b4ccece0', '0770f10a7b130852e38d9af44e050c9188664c12f2d31a56a62d6648a73e1264']

# Непотраченные входы:
unspents = source_k.get_unspents()
print(unspents)

# [Unspent(amount=967861, confirmations=4, script='76a91418ee4d98c345db083114990baa17d02e988cfedb88ac', txid='a101ad526e9fb131b90aac220b8b6e8bf11b9b9848ab8ea6d4384dc5b4ccece0', txindex=1, segwit=False)]

Пример для нескольких адресатов (каждая валюта будет пересчитана по курсу в биткоин):

my_key.send([
    ('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', 0.0035, 'btc'),
    ('1Archive1n2C579dMsAu3iC6tWzuQJz8dN', 190, 'jpy'),
    ('129TQVAroeehD9fZpzK51NdZGQT4TqifbG', 3, 'eur'),
    ('14Tr4HaKkKuC1Lmpr2YMAuYVZRWqAdRTcr', 2.5, 'cad')
])

Если вернуть сдачу не себе, а на другой адрес – аргумент leftover:

key.send(..., leftover='адрес_для_сдачи')

Если нужно прикрепить к транзакции сообщение (до 40 байт в кодировке UTF-8) – аргумент message:

key.send(..., message='За кофе и пончик')

Функция create_transaction только создает транзакцию и подписывает ее ключом, но не посылает ее в сеть. Аргументы те же, что у send.

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

В худшем случает транзакция не дойдет до сети и не исполнится.

Комиссии

Если комиссия не указана явно, то она рассчитывается по средним значениям с учетом длины транзакции. Средняя комиссия берется из онлайн-сервиса. Но можно указать комиссию самостоятельно:

# комиссия за байт (будет умножена на кол-во байт)
source_k.create_transaction(..., fee=72)  

# комиссия за всю транзакцию целиком
source_k.create_transaction(..., fee=200, absolute_fee=True)  

Полезные константы комиссий:

from bit.network import fees
fees.DEFAULT_FEE_FAST   # 10 мин
fees.DEFAULT_FEE_HOUR   # 1 час

Советы

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

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

from bit.network import NetworkAPI

# тестовая нода
NetworkAPI.connect_to_node(user='user', password='password', host='localhost', port='18443', use_https=False, testnet=True)

# подключение к ноде главной сети
NetworkAPI.connect_to_node(user='user', password='password', host='domain', port='8332', use_https=True, testnet=False)

# на выбор или вместе

Храните надежно ваши приватные ключи!

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

Библиотека bit умеет еще работать с мульти-адресами, которые требует 2 и более подписей для выполнения транзакции.

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

«Сломанный» set

Вопрос: может ли set содержать два одинаковых объекта?

Ответ: да, запросто!

Делаем класс:

class Foo:
    def __init__(self, x):
        self.x = x
    def __hash__(self):
        return self.x
    def __eq__(self, other):
        return self.x == other.x
    def __repr__(self):
        return f'Foo({self.x})'

# создаем set из трех разных объектов
hacker = Foo(20)
s = {Foo(10), hacker, Foo(30)}

print(s)  # {Foo(10), Foo(20), Foo(30)}

hacker.x = 30  # взлом системы
print(s)  # {Foo(10), Foo(30), Foo(30)}

from collections import Counter
c = Counter(s)
print(c)  # Counter({Foo(30): 2, Foo(10): 1})

Как это? set запоминает хэш объекта при вставке, а потом не следит за тем, меняется ли как-то объект или нет, это было бы очень накладно. Изначально мы вставляли 20, но потом уже поменяли его на 30, тем самым сломав set.

«Починить» такой set можно, сделав из него список, а потом новый set, тогда хэши будут заново пересчитаны. Лучше до такого не доводить!

s2 = set(list(s))
print(s2)  # {Foo(10), Foo(30)}

Примечание: а метод s.copy() не сработает, потому что он копирует уже вычисленные хэши.

Мораль: если вы помещаете свои объекты в set, вы должны самостоятельно обеспечить их логическую иммутабельность. Иными словами обеспечить неизменяемость именно тех атрибутов, которые участвуют в сравнении и хэшировании: не менять их самому и сокрыть от внешних изменений. Те же правила относятся к объектам, которые вы хотите сделать ключами словаря dict.

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

Библиотека schedule – CRON на Python

Вам приходилось работать с CRON? Это такой сервис в nix-системах, который позволяет регулярно в определенные моменты времени запускать скрипты или программы. Штука с долгой историей, в наследство которой достался странный синтаксис для описания правил:

0 * * * * my_script

Что если бы мы хотели иметь свой CRON внутри программы Python, чтобы в нужные моменты времени вызывать функции? Да еще, чтобы у него был человеческий синтаксис? Такая библиотека есть и называется schedule.

pip install schedule

Рассмотрим пример:

import schedule
import time

def job():
    print("Работаю")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every(5).to(10).minutes.do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job)

# нужно иметь свой цикл для запуска планировщика с периодом в 1 секунду:
while True:
    schedule.run_pending()
    time.sleep(1)

Как видите, правила для задания временных интервалов прекрасно читаются, словно они предложения на английском языке. Перевод пары примеров:

# спланируй.каждые(10).минут.сделать(работу)
schedule.every(10).minutes.do(job)

# спланируй.каждый().день.в(10:30).сделать(работу)
schedule.every().day.at("10:30").do(job)

В задания можно передавать параметры вот так:

def greet(name):
    print('Hello', name)

schedule.every(2).seconds.do(greet, name='Alice')

Если по какой-то причине нужно отменить задание, это делается так:

def job1():
    # возвращаем такой токен, и это задание снимается с выполнения в будущем
    return schedule.CancelJob

schedule.every().day.at('22:30').do(job1)

Если нужно отменить группу заданий, то к ним добавляют тэги:

schedule.every().day.do(greet, 'Monica').tag('daily-tasks')
schedule.every().day.do(greet, 'Derek').tag('daily-tasks')

schedule.clear('daily-tasks')  # массовая отмена по тэгу

Метод to позволяет задать случайный интервал для выполнения задания, например от 5 до 10 секунд:

schedule.every(5).to(10).seconds.do(my_job)

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

import functools

# декоратор для ловли исключений
def catch_exceptions(cancel_on_failure=False):
    def catch_exceptions_decorator(job_func):
        @functools.wraps(job_func)
        def wrapper(*args, **kwargs):
            try:
                return job_func(*args, **kwargs)
            except:
                import traceback
                print(traceback.format_exc())
                if cancel_on_failure:
                    return schedule.CancelJob
        return wrapper
    return catch_exceptions_decorator

@catch_exceptions(cancel_on_failure=True)
def bad_task():
    # даст исключение, но декоратор просто отменит эту задачу
    return 1 / 0  

schedule.every(5).minutes.do(bad_task)

Если задания занимают продолжительное время или должны выполняться параллельно, то вам самостоятельно придется организовать их выполнение в отдельных потоках.

import threading
import time
import schedule

# код задания
def job():
    print("Выполняюсь в отдельном потоке")


def run_threaded(job_func):
    job_thread = threading.Thread(target=job_func)
    job_thread.start()


schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)
schedule.every(10).seconds.do(run_threaded, job)


# бесконечный цикл, проверяющий каждую секунду, не пора ли запустить задание 
while 1:
    schedule.run_pending()
    time.sleep(1)

Celery

Если вы используете в проекте Celery, то, вероятно, вам не нужен schedule. В Celery и так есть отличный CRON:

from celery import Celery
from celery.schedules import crontab

app = Celery()

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    # эта функция выполнится при запуске - настроим вызовы задачи test

    sender.add_periodic_task(10.0, test.s('hello'), name='add every 10')
    sender.add_periodic_task(30.0, test.s('world'), expires=10)

    # в 7 30 по понедельникам
    sender.add_periodic_task(
        crontab(hour=7, minute=30, day_of_week=1),
        test.s('Happy Mondays!'),
    )

@app.task
def test(arg):
    print(arg)

Подробнее тут.

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

Munch – вседозволенный объект

КДПВ

Привет. Хочу познакомить вас библиотекой Munch, которая является форком более старой библиотеки Bunch. Рассмотрим суть проблемы, которую она решает. Задать атрибуты объекта, не описывая их по одному в конструкторе. Легче понять на примере:

>>> f = object()
>>> f.x = 10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'x'

Видно, что object нам не поможет. Но в Python 3 можно сделать пустой класс, это не вызовет ошибки:

class Bunch: ...

foo = Bunch()
foo.x = 10
foo.y = 20

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

Установка:

pip install munch

Объект Munch – это наследник словаря dict, с ним можно работать как со словарем, но можно также произвольно работать с его атрибутами:

from munch import *

# пустой
b = Munch()

# задаем атрибуты
b.hello = 'world'
print(b.hello)  # world

b['hello'] += "!"
print(b.hello)  # world!

print(b.hello is b['hello'])  # True

# атрибут может быть тоже Munch
b.bar = Munch()
b.bar.baz = 100
print(b.bar.baz)  # 100

Т.е. мы может обращаться к данным как через точку (как атрибут), так и через квадратные скобки (как с обычным словарем) – это будут одни и те же данные, при условии равных имен.

Очень удобная фишка – создание Munch через конструктор, просто перечисляем ключевые слова, и они станут атрибутами:

# задать через конструктор
c = Munch(x=10, y=20, z=30)
print(c.x, c.y, c.z)  # 10 20 30

С Munch можно работать, как с обычным dict, например:

c = Munch(x=10, y=20, z=30)
print(list(c.keys()))  # список атрибутов

c.update({
    'w': 10,
    'name': 'ganesh'
})
print(c)  # Munch({'x': 10, 'y': 20, 'z': 30, 'w': 10, 'name': 'ganesh'})

print([(k, v) for k, v in c.items()])
# [('x', 10), ('y', 20), ('z', 30), ('w', 10), ('name', 'ganesh')]

Удобно сеарилизовтать такие объекты:

# JSON

b = Munch(foo=Munch(lol=True), hello=42, ponies='are pretty!')
import json
print(json.dumps(b))
#  {"foo": {"lol": true}, "hello": 42, "ponies": "are pretty!"}

# YAML - если есть.
import yaml
print(yaml.dump(b))  # или
print(yaml.safe_dump(b))

Замечание

В библиотеку collections Python 3 уже включен объект UserDict со схожей функциональностью:

from collections import UserDict

a = UserDict()
a.p = 10

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