├── .gitignore ├── Procfile ├── README.md ├── app.py ├── bitcoin ├── __init__.py ├── bci.py ├── blocks.py ├── composite.py ├── deterministic.py ├── english.txt ├── main.py ├── mnemonic.py ├── py2specials.py ├── py3specials.py ├── ripemd.py ├── stealth.py └── transaction.py ├── bot.py ├── config ├── init.json ├── keyboards.json └── messages.json ├── main_wallet.py ├── modules ├── buy_money.py ├── get_money.py ├── menu.py ├── send_money.py └── wallet_info.py ├── requirements.txt ├── runtime.txt └── wallet.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | dump.rdb 3 | bitcoin/__pycache__ 4 | __pycache__ 5 | modules/__pycache__ -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn app:app -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fastcoin-Бот 2 | Это краткая документация по боту. 3 | 4 | ## Логика работы бота 5 | ``` 6 | Меню 7 | ┌─────┼────────────────┬────────────────┐ 8 | │ │ │ │ 9 | │ │ │ │ 10 | │ │ │ │ 11 | КОШЕЛЕК │ ПОЛУЧЕНИЕ │ 12 | баланс │ показ BTC-Адреса │ 13 | кошелька │ и QR-кода │ 14 | │ │ 15 | ОТПРАВКА КУПИТЬ 16 | Ввод BTC-адреса Ввод BTC или RUB 17 | и отправка Информации о пльзователе 18 | средств Отправка средств 19 | ``` 20 | 21 | ## Как запустить 22 | ### Настройка окружения 23 | Создайте `.env` файл в папке с ботом и откройте его в текстовом редакторе. 24 | Заполните его следующим образом: 25 | ``` 26 | export BOT_TOKEN=172195613:BBFxbrBuVxPFj6ckKIqPraLv81c19Rad34Q 27 | export WEBHOOK_URL=webhook.example.com 28 | export PRIVATE_KEY=0e37e5feb349ce0c8е03963ddd4163b19k171b0be9ad1c7a7fe266edaedcf3 29 | export ADMIN=205279061 30 | export CARD_NUMBER="1231 213 1233 1323" 31 | ``` 32 | #### Описание 33 | 1) Токен бота, который вы болучается от BotFather 34 | 2) URL вебхука на который будут приходить сообщения [см. документацию BOT API](https://core.telegram.org/bots/api#setwebhook) 35 | 3) Приватный ключ от главного Bitcoin-кошелька, [см. документация Bitcoin](https://en.bitcoin.it/Private_key) 36 | 4) Telegram-Id администратора, можно получить в [@myidbot](https://t.me/myidbot) 37 | 5) Номер Сбербанк-карты для перевода 38 | 39 | ### Установка зависимостей 40 | Для работы бота необходимо установить следующие зависимости: 41 | * Python 3.x 42 | * Redis-server 43 | * Произвести установки библиотек с помощью PIP: `pip3 install -r requirements.txt` 44 | 45 | ### Запуск 46 | 1) Обновите окружение, добавив переменные из файла `.env` с помощью команды `source .env` 47 | 2) Запустите redis-server: `redis-server` 48 | 3) Для тестового запуска бота, можете воспользоваться стандартным интепритатором Python: `python3 app.py` 49 | 4) Для продакшена советую воспользоваться [Gunicorn](http://gunicorn.org): `gunicorn app:app` 50 | 51 | ## Структура бота 52 | ### Bot-Core 53 | Ядро бота состоит из двух модулей в корне проекта: `app.py` и `bot.py` 54 | 55 | Первый модуль представляет из себя стартовый модуль, который запускает и инициализирует бота, он отвечает за создание `Flask` веб-приложения, создание объекта бота, инициализацию бота и вебхуков, получение и обработку данных с вебхуков. 56 | 57 | `Bot.py` отвечает за класс `Bot`, который как представляет из центральную часть прилоежения, которая отвечает за работу бота. В нем описаны методы работы с: базой данных, Telegram API, обработку сообщений пользователя, загрузку модулей бота, работу с пользовательской сессией, генерирование пользовательских клавиатур и сообщений из шаблонов. 58 | 59 | ### Modules 60 | Модули представляют из себя некоторые скрипты, сценарии, которым бот передает сообщения от пользователя, а они в свою очередь решают что делать в той или иной ситуации. 61 | Модули состоят из некоторых функций обработчиков **handler**'ов. Самое первое сообщение пользователя обрабатывается стандартным хендлером, который указан в конфиге в дальнейшей работе бота хендлеры могут не только получать данные, но и указывать какой хендлер будет обрабатывать следующее сообщение. Таким образом строится неявный граф обработки сообщений пользователя. 62 | 63 | Внутри модулей хендлеры могут оперировать любой информацией: 64 | * Получать и записывать данные в базу данных 65 | * Отправлять сообщение через Telegram API 66 | * Запрашивать или отправлять средства по средствам Bitcoin API 67 | * Отрисовывать пользовательские клавиатуры 68 | * И многое другое.. 69 | 70 | ### Config 71 | Конфиги представляют из себя статические JSON-файлы, которые хранят необходимую боту информацию. На данный момент в боте существует три конфиг-файлов: `init.json`, `keyboards.json`, `messages.json`. 72 | 73 | Первый файл отвечает за основные настройки бота: 74 | * **default-handler** - стандартный обработчик сообщения, когда пользователь пишет первый раз или не указан обработчик который будет обрабатывать следующее собщение 75 | * **menu-button** - сообщение при получении которого, бот всегда будет возвращаться в главное меню 76 | * **comission** - Bitcoin комиссия для совершения транзакции, указвается в **сатоши!** 77 | 78 | **keyboards.json** - отвечает за хранение шаблонов клавиатур, о работе с которыми вы можете прочитать далее 79 | **messages.json** - отвечает за хранение шаблонов сообщений, о работе с которыми вы можете прочитать далее 80 | 81 | ### Bitcoin API 82 | Для работы с **Bitcoin** используется библиотека [pybitcointools](https://github.com/vbuterin/pybitcointools), которая была склонирована в папку с проектом и дописана для совместимости с третьим питоном. 83 | 84 | За работу кошельков отвечают два модуля: `wallet.py` и `main_wallet.py`. Первый - отвечает за работу с кошельками пользователей, второй - за работу с главным кошельком. 85 | 86 | 87 | --- 88 | 89 | 90 | # Bot 91 | Здесь описаны все публичные методы и переменные класса **Bot**. 92 | 93 | ## Методы 94 | 95 | ### Работа с базой данных 96 | Методы отвечающие за хранение, получение и удаление информации о пользователе. 97 | 98 | 99 | #### user_set(user_id, field, value) 100 | Метод отвечает за запись некоторой информации для определенного пользователя, по определенному полю. Значение **value** автоматически сериализуется в **json** и записывается в базу, в нашем случае **redis**. 101 | 102 | В качестве значения **u\_id**, передается **telegram-id** пользователя, который можно получить из объекта сообщения: **message.u\_id**. По договоренности **нулевой id** используется для хранения системных данных. 103 | 104 | 105 | 106 | #### user_get(user_id, field, default=None) 107 | Метод отвечает за получение некоторой информации для определенного пользователя, по определенному полю. Значение **default** отвечает за значение, которое будет возвращаться в случае если значения по данному полю не существует. 108 | 109 | В качестве значения **u\_id**, передается **telegram-id** пользователя, который можно получить из объекта сообщения: **message.u\_id**. По договоренности **нулевой id** используется для хранения системных данных. 110 | 111 | 112 | 113 | #### user_delete(user_id, field) 114 | Метод отвечает за удаление некоторой информации для определенного пользователя, по определенному полю. 115 | 116 | ### Работа с API Telegram 117 | Для работы с Telegram API используется библиотека pytelegrambotapi. Для того, что бы взаимодействовать с telegram api обратитесь к объекту *телеграм бота*: **bot.telegram** и вызовите необходимый метод. Например, отправка сообщений: **bot.telegram.send_message(u\_id, message)**. 118 | Подробную документацию о библиотеке читайте [здесь](https://github.com/eternnoir/pyTelegramBotAPI). 119 | 120 | ### Работа с сообщениями и калавиатурами 121 | #### Сообщения 122 | Сообщения в telegram предстваляют из себя обычный текст, который мы передаем параметром в функцию. 123 | В данном боте встроена система шаблонизации сообщений, шаблоны хранятся в файле `/config/messages.json`. Сообщения используют шаблонизатор **Jinja2**, документацию читать можно [здесь](http://jinja.pocoo.org). 124 |
125 | Так же шаблоны сообщений могут содержать в себе разметку [Markdown](https://core.telegram.org/bots/api#markdown-style), но для этого в метод **send_message** должен быть передан параметр `parse_mode="Markdown"`. 126 | 127 | #### Клавиатуры 128 | Телеграм имеет два вида клавиатур: инлайн и обычные. 129 | 130 | ##### Обычные клавиатуры 131 | Данный тип клавиатур появляется вместо клавиатуры набора текста и обычно представляют готовые варианты для набора. При нажатии на любую кнопку данной клавиатуры отправляется сообщение. 132 | 133 | Шаблоны клавиатур распологаются в файле `config/keyboards.json' и представляют из себя следующую структуру. 134 | ``` 135 | [ 136 | [["Заголовок-Кнопки1", "возвращаемое значение"], ["Заголовок-Кнопки1", "возвращаемое значение"]], //строка клавиатуры 137 | [["Заголовок-Кнопки1", "возвращаемое значение"], ["Заголовок-Кнопки1", "возвращаемое значение"]] 138 | ] 139 | ``` 140 | 141 | 142 | Для отрисовки клавиатуры используется метод `bot.get_keyboard("имя-клавиатуры")`, после чего полученый объект передается в метод `bot.telegram.send_message` парметром `reply_markup=...`. 143 | 144 | 145 | Хендлер который получит сообщение с вашей кнопкой должен вызвать метод `bot.get_key("название-клавиатуры", "текст-полученного-сообщения")`, после чего будет возвращена строка с значением кнопки, или `None` если такой кнопки нет. 146 | 147 | ##### Инлайн клавиатуры 148 | Инлайн клавиатуры отличаются тем, что данная клавиатура прикреплена к сообщению и при нажжатии на нее не происходит отправка сообщения, а вызывается **callback функция**. 149 | 150 | 151 | Формат хранения и место хранения не отличается от обычных клавиатур, однако вместо "значения" кнопки указывается имя вызываемой **callback функции** в ответ на нажатие кнопки, о том как создать подобную функцию читайте далее. 152 | 153 | #### Хендлеры 154 | Хендлеры представляют из себя некоторые функции, которые обрабатывают различные типы сообщений. Существует два вида хендлеров: callback и стандартные. 155 | 156 | ##### Callback-Handler 157 | Callback хендлеры - функции обрабатывающие запросы от инлайн клавиатур. Для создания callback-handler, просто создайте функцию с двумя входными параметрами: **bot** и **query**. 158 | 159 | 160 | Первый парметр - объект бота, с помощью которого вы можете обращаться к базе данных, отправлять сообщения и многое другое. 161 | 162 | 163 | Второй параметр - объект типа [inlineQuery](https://core.telegram.org/bots/api#inlinequery) который несет всю необходимую информацию о запросе. 164 | 165 | После создания функции, просто добавьте ее в список callback хендлеров бота уазав ее имя: `bot.callback_handlers["имя-коллбека"] = функция` 166 | 167 | 168 | Работа с этим типом хендлеров описана в главе "Клавиатуры". 169 | 170 | ##### Стандартные хендлеры 171 | Данный тип хендлеров отличается от предыдущих тем, что отвечает за обработку обычных сообщений пользователя, в связи с чем вместо объекта **query** используется объект типа [message](https://core.telegram.org/bots/api#message) 172 | 173 | 174 | Добавление функции в список выглядит следующим образом: `bot.handlers["имя-хенлера"] = функция` 175 | 176 | 177 | Для установки хендлера, который будет обрабатывать следующее сообщение пользователя воспользуйтесь методом: `bot.set_next-handler(u\_id, "название-хендлера")` 178 | 179 | 180 | Для вынужденного вызова хендлера из текущего хендлера воспользуйтесь методом: `bot.call_handler("название-хендлера", message, forward_flag=True)` 181 | 182 | Параметр **message** - объект сообщения, который пришел в текущий хендлер, параметр **forward_flag** - указывает, ставить ли флаг в сообшении о том, что оно было переадресовано из другого хендлера, получить значение этого флага можно следующим образом: `message.forward`. 183 | 184 | 185 | ## Написание своих модулей 186 | Для того, что бы создать свой модуль просто создайте файл с расширением *.py* в папке `/modules`. В файле обязательно должен присутсвовать метод `init(bot)`. Данный метов вызывается при инициализации модуля и в него передается объект бота. Данный метод необходим для установки начальных настроект модуля, например в этом методе инициализируются все хендлеры (добавляются в бота), о том как это сделать читайте в главе "Хендлеры". 187 | 188 | 189 | 190 | # Bitcoin 191 | Для работы с Bitcoin используется библиотека **pybitcointools**, вся работа с ней реализована в модулях **main_wallet** и **wallet**. 192 | 193 | 194 | **Wallet** - класс кошелька пользователя, при создании объекта в него передается *user id*, по которому в базе ищется приватный ключ пользователя, если его нет (пользователь впервые использует бота) - он создается. 195 | 196 | 197 | Класс содержит следующие методы: 198 | * `get_curency` - Запрос текущего курса BTC/RUB 199 | * `get_balance` - Получение текущего баланса кошелька пользователя 200 | * `send_money` - Отправка средств на кошелек другого пользователя 201 | 202 | **MainWallet** - класс главного кошелька, на котором хранится запас BTC для совершения операций покупки. Класс наследует все методы **Walllet** и имеет некоторые особенности. 203 | 204 | 205 | При создании передается приватный ключ с имеющимися на счету BTC. 206 | Пользователь может передать в кошелек свою транзакцию для перевода денег на его кошелек (запрос на покупку), транзакция представляет из себя следующий объект: 207 | ``` 208 | { 209 | "id": "uGFDJK", 210 | "username": "@alxmamaev", 211 | "time": 2918481740974890827190, 212 | "address": "aosg9uUFBuoiqwijmdihwiNPIDnfj=FWJoj", 213 | "btc_value": 10, 214 | "rub_value": 2500000, 215 | } 216 | ``` 217 | Администратор в свою очередь может подтвердить прохождение транзакции или отменить ее. 218 | 219 | кошелек имеет следующие дополнительные методы: 220 | * `add_tx` - добавить транзакцию в список транзакций 221 | * `get_tx` - получить транзакцию по ее *id* 222 | * `confirm_tx` - подтвердить транзакцию по ее id 223 | * `not_confirm_tx` - отменить транзакцию по ее Id -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*-? 2 | import os 3 | import flask 4 | import telebot 5 | import logging 6 | from bot import Bot 7 | 8 | WEBHOOK_URL = os.environ["WEBHOOK_URL"]+"/bot/"+os.environ["BOT_TOKEN"] 9 | WEBHOOK_PATH = "/"+os.environ["BOT_TOKEN"] 10 | BOT_TOKEN = os.environ["BOT_TOKEN"] 11 | 12 | app = flask.Flask(__name__) 13 | app.comission = 32000 14 | 15 | @app.route("/bot/", methods=['POST']) 16 | def getMessage(token): 17 | if token == BOT_TOKEN: 18 | update = telebot.types.Update.de_json(flask.request.stream.read().decode("utf-8")) 19 | 20 | if update.message is not None: 21 | bot._process_message(update.message) 22 | 23 | if update.callback_query is not None: 24 | bot._process_callback(update.callback_query) 25 | 26 | return "", 200 27 | else: 28 | return "", 404 29 | 30 | @app.route("/") 31 | def webhook(): 32 | return "Hola!", 200 33 | 34 | @app.route("/set") 35 | def set_webhook(): 36 | bot.telegram.remove_webhook() 37 | bot.telegram.set_webhook(url=WEBHOOK_URL) 38 | bot.logger.info("Webhook set") 39 | return "Webhook setted", 200 40 | 41 | bot = Bot(debug=False) 42 | 43 | 44 | if __name__=="__main__": app.run(port=8080) -------------------------------------------------------------------------------- /bitcoin/__init__.py: -------------------------------------------------------------------------------- 1 | from bitcoin.py2specials import * 2 | from bitcoin.py3specials import * 3 | from bitcoin.main import * 4 | from bitcoin.transaction import * 5 | from bitcoin.deterministic import * 6 | from bitcoin.bci import * 7 | from bitcoin.composite import * 8 | from bitcoin.stealth import * 9 | from bitcoin.blocks import * 10 | from bitcoin.mnemonic import * 11 | -------------------------------------------------------------------------------- /bitcoin/bci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import json, re 3 | import random 4 | import sys 5 | try: 6 | from urllib.request import build_opener 7 | except: 8 | from urllib2 import build_opener 9 | 10 | 11 | # Makes a request to a given URL (first arg) and optional params (second arg) 12 | def make_request(*args): 13 | opener = build_opener() 14 | opener.addheaders = [('User-agent', 15 | 'Mozilla/5.0'+str(random.randrange(1000000)))] 16 | try: 17 | return opener.open(*args).read().strip() 18 | except Exception as e: 19 | try: 20 | p = e.read().strip() 21 | except: 22 | p = e 23 | raise Exception(p) 24 | 25 | 26 | def is_testnet(inp): 27 | '''Checks if inp is a testnet address or if UTXO is a known testnet TxID''' 28 | if isinstance(inp, (list, tuple)) and len(inp) >= 1: 29 | return any([is_testnet(x) for x in inp]) 30 | elif not isinstance(inp, str): # sanity check 31 | raise TypeError("Input must be str/unicode, not type %s" % str(type(inp))) 32 | 33 | if not inp or (inp.lower() in ("btc", "testnet")): 34 | pass 35 | 36 | ## ADDRESSES 37 | if inp[0] in "123mn": 38 | if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): 39 | return True 40 | elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp): 41 | return False 42 | else: 43 | #sys.stderr.write("Bad address format %s") 44 | return None 45 | 46 | ## TXID 47 | elif re.match('^[0-9a-fA-F]{64}$', inp): 48 | base_url = "http://api.blockcypher.com/v1/btc/{network}/txs/{txid}?includesHex=false" 49 | try: 50 | # try testnet fetchtx 51 | make_request(base_url.format(network="test3", txid=inp.lower())) 52 | return True 53 | except: 54 | # try mainnet fetchtx 55 | make_request(base_url.format(network="main", txid=inp.lower())) 56 | return False 57 | sys.stderr.write("TxID %s has no match for testnet or mainnet (Bad TxID)") 58 | return None 59 | else: 60 | raise TypeError("{0} is unknown input".format(inp)) 61 | 62 | 63 | def set_network(*args): 64 | '''Decides if args for unspent/fetchtx/pushtx are mainnet or testnet''' 65 | r = [] 66 | for arg in args: 67 | if not arg: 68 | pass 69 | if isinstance(arg, str): 70 | r.append(is_testnet(arg)) 71 | elif isinstance(arg, (list, tuple)): 72 | return set_network(*arg) 73 | if any(r) and not all(r): 74 | raise Exception("Mixed Testnet/Mainnet queries") 75 | return "testnet" if any(r) else "btc" 76 | 77 | 78 | def parse_addr_args(*args): 79 | # Valid input formats: unspent([addr1, addr2, addr3]) 80 | # unspent([addr1, addr2, addr3], network) 81 | # unspent(addr1, addr2, addr3) 82 | # unspent(addr1, addr2, addr3, network) 83 | addr_args = args 84 | network = "btc" 85 | if len(args) == 0: 86 | return [], 'btc' 87 | if len(args) >= 1 and args[-1] in ('testnet', 'btc'): 88 | network = args[-1] 89 | addr_args = args[:-1] 90 | if len(addr_args) == 1 and isinstance(addr_args, list): 91 | network = set_network(*addr_args[0]) 92 | addr_args = addr_args[0] 93 | if addr_args and isinstance(addr_args, tuple) and isinstance(addr_args[0], list): 94 | addr_args = addr_args[0] 95 | network = set_network(addr_args) 96 | return network, addr_args 97 | 98 | 99 | # Gets the unspent outputs of one or more addresses 100 | def bci_unspent(*args): 101 | network, addrs = parse_addr_args(*args) 102 | u = [] 103 | for a in addrs: 104 | try: 105 | data = make_request('https://blockchain.info/unspent?active='+a) 106 | except Exception as e: 107 | if str(e) == 'No free outputs to spend': 108 | continue 109 | else: 110 | raise Exception(e) 111 | try: 112 | jsonobj = json.loads(data.decode("utf-8")) 113 | for o in jsonobj["unspent_outputs"]: 114 | tx_hash = o['tx_hash'] 115 | h = "".join([tx_hash[i]+tx_hash[i+1] for i in range(0,len(tx_hash), 2)][::-1]) 116 | u.append({ 117 | "output": h+':'+str(o['tx_output_n']), 118 | "value": o['value'] 119 | }) 120 | except: 121 | raise Exception("Failed to decode data: "+data) 122 | return u 123 | 124 | 125 | def blockr_unspent(*args): 126 | # Valid input formats: blockr_unspent([addr1, addr2,addr3]) 127 | # blockr_unspent(addr1, addr2, addr3) 128 | # blockr_unspent([addr1, addr2, addr3], network) 129 | # blockr_unspent(addr1, addr2, addr3, network) 130 | # Where network is 'btc' or 'testnet' 131 | network, addr_args = parse_addr_args(*args) 132 | 133 | if network == 'testnet': 134 | blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/' 135 | elif network == 'btc': 136 | blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/' 137 | else: 138 | raise Exception( 139 | 'Unsupported network {0} for blockr_unspent'.format(network)) 140 | 141 | if len(addr_args) == 0: 142 | return [] 143 | elif isinstance(addr_args[0], list): 144 | addrs = addr_args[0] 145 | else: 146 | addrs = addr_args 147 | res = make_request(blockr_url+','.join(addrs)) 148 | data = json.loads(res.decode("utf-8"))['data'] 149 | o = [] 150 | if 'unspent' in data: 151 | data = [data] 152 | for dat in data: 153 | for u in dat['unspent']: 154 | o.append({ 155 | "output": u['tx']+':'+str(u['n']), 156 | "value": int(u['amount'].replace('.', '')) 157 | }) 158 | return o 159 | 160 | 161 | def helloblock_unspent(*args): 162 | addrs, network = parse_addr_args(*args) 163 | if network == 'testnet': 164 | url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' 165 | elif network == 'btc': 166 | url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s' 167 | o = [] 168 | for addr in addrs: 169 | for offset in xrange(0, 10**9, 500): 170 | res = make_request(url % (addr, offset)) 171 | data = json.loads(res.decode("utf-8"))["data"] 172 | if not len(data["unspents"]): 173 | break 174 | elif offset: 175 | sys.stderr.write("Getting more unspents: %d\n" % offset) 176 | for dat in data["unspents"]: 177 | o.append({ 178 | "output": dat["txHash"]+':'+str(dat["index"]), 179 | "value": dat["value"], 180 | }) 181 | return o 182 | 183 | 184 | unspent_getters = { 185 | 'bci': bci_unspent, 186 | 'blockr': blockr_unspent, 187 | 'helloblock': helloblock_unspent 188 | } 189 | 190 | 191 | def unspent(*args, **kwargs): 192 | f = unspent_getters.get(kwargs.get('source', ''), bci_unspent) 193 | return f(*args) 194 | 195 | 196 | # Gets the transaction output history of a given set of addresses, 197 | # including whether or not they have been spent 198 | def history(*args): 199 | # Valid input formats: history([addr1, addr2,addr3]) 200 | # history(addr1, addr2, addr3) 201 | if len(args) == 0: 202 | return [] 203 | elif isinstance(args[0], list): 204 | addrs = args[0] 205 | else: 206 | addrs = args 207 | 208 | txs = [] 209 | for addr in addrs: 210 | offset = 0 211 | while 1: 212 | gathered = False 213 | while not gathered: 214 | try: 215 | data = make_request( 216 | 'https://blockchain.info/address/%s?format=json&offset=%s' % 217 | (addr, offset)) 218 | gathered = True 219 | except Exception as e: 220 | try: 221 | sys.stderr.write(e.read().strip()) 222 | except: 223 | sys.stderr.write(str(e)) 224 | gathered = False 225 | try: 226 | jsonobj = json.loads(data.decode("utf-8")) 227 | except: 228 | raise Exception("Failed to decode data: "+data) 229 | txs.extend(jsonobj["txs"]) 230 | if len(jsonobj["txs"]) < 50: 231 | break 232 | offset += 50 233 | sys.stderr.write("Fetching more transactions... "+str(offset)+'\n') 234 | outs = {} 235 | for tx in txs: 236 | for o in tx["out"]: 237 | if o.get('addr', None) in addrs: 238 | key = str(tx["tx_index"])+':'+str(o["n"]) 239 | outs[key] = { 240 | "address": o["addr"], 241 | "value": o["value"], 242 | "output": tx["hash"]+':'+str(o["n"]), 243 | "block_height": tx.get("block_height", None) 244 | } 245 | for tx in txs: 246 | for i, inp in enumerate(tx["inputs"]): 247 | if "prev_out" in inp: 248 | if inp["prev_out"].get("addr", None) in addrs: 249 | key = str(inp["prev_out"]["tx_index"]) + \ 250 | ':'+str(inp["prev_out"]["n"]) 251 | if outs.get(key): 252 | outs[key]["spend"] = tx["hash"]+':'+str(i) 253 | return [outs[k] for k in outs] 254 | 255 | 256 | # Pushes a transaction to the network using https://blockchain.info/pushtx 257 | def bci_pushtx(tx): 258 | if not re.match('^[0-9a-fA-F]*$', tx): 259 | tx = tx.encode('hex') 260 | return make_request('https://blockchain.info/pushtx', ('tx='+tx).encode("utf-8")) 261 | 262 | 263 | def eligius_pushtx(tx): 264 | if not re.match('^[0-9a-fA-F]*$', tx): 265 | tx = tx.encode('hex') 266 | s = make_request( 267 | 'http://eligius.st/~wizkid057/newstats/pushtxn.php', 268 | 'transaction='+tx+'&send=Push') 269 | strings = re.findall('string[^"]*"[^"]*"', s) 270 | for string in strings: 271 | quote = re.findall('"[^"]*"', string)[0] 272 | if len(quote) >= 5: 273 | return quote[1:-1] 274 | 275 | 276 | def blockr_pushtx(tx, network='btc'): 277 | if network == 'testnet': 278 | blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push' 279 | elif network == 'btc': 280 | blockr_url = 'http://btc.blockr.io/api/v1/tx/push' 281 | else: 282 | raise Exception( 283 | 'Unsupported network {0} for blockr_pushtx'.format(network)) 284 | 285 | if not re.match('^[0-9a-fA-F]*$', tx): 286 | tx = tx.encode('hex') 287 | 288 | return make_request(blockr_url, ('{"hex":"%s"}' % tx).encode("utf-8")) 289 | 290 | 291 | def helloblock_pushtx(tx): 292 | if not re.match('^[0-9a-fA-F]*$', tx): 293 | tx = tx.encode('hex') 294 | return make_request('https://mainnet.helloblock.io/v1/transactions', 295 | 'rawTxHex='+tx) 296 | 297 | pushtx_getters = { 298 | 'bci': bci_pushtx, 299 | 'blockr': blockr_pushtx, 300 | 'helloblock': helloblock_pushtx 301 | } 302 | 303 | 304 | def pushtx(*args, **kwargs): 305 | f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx) 306 | return f(*args) 307 | 308 | 309 | def last_block_height(network='btc'): 310 | if network == 'testnet': 311 | data = make_request('http://tbtc.blockr.io/api/v1/block/info/last') 312 | jsonobj = json.loads(data.decode("utf-8")) 313 | return jsonobj["data"]["nb"] 314 | 315 | data = make_request('https://blockchain.info/latestblock') 316 | jsonobj = json.loads(data.decode("utf-8")) 317 | return jsonobj["height"] 318 | 319 | 320 | # Gets a specific transaction 321 | def bci_fetchtx(txhash): 322 | if isinstance(txhash, list): 323 | return [bci_fetchtx(h) for h in txhash] 324 | if not re.match('^[0-9a-fA-F]*$', txhash): 325 | txhash = txhash.encode('hex') 326 | data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex') 327 | return data 328 | 329 | 330 | def blockr_fetchtx(txhash, network='btc'): 331 | if network == 'testnet': 332 | blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/' 333 | elif network == 'btc': 334 | blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/' 335 | else: 336 | raise Exception( 337 | 'Unsupported network {0} for blockr_fetchtx'.format(network)) 338 | if isinstance(txhash, list): 339 | txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x) 340 | else x for x in txhash]) 341 | jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) 342 | return [d['tx']['hex'] for d in jsondata['data']] 343 | else: 344 | if not re.match('^[0-9a-fA-F]*$', txhash): 345 | txhash = txhash.encode('hex') 346 | jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8")) 347 | return jsondata['data']['tx']['hex'] 348 | 349 | 350 | def helloblock_fetchtx(txhash, network='btc'): 351 | if isinstance(txhash, list): 352 | return [helloblock_fetchtx(h) for h in txhash] 353 | if not re.match('^[0-9a-fA-F]*$', txhash): 354 | txhash = txhash.encode('hex') 355 | if network == 'testnet': 356 | url = 'https://testnet.helloblock.io/v1/transactions/' 357 | elif network == 'btc': 358 | url = 'https://mainnet.helloblock.io/v1/transactions/' 359 | else: 360 | raise Exception( 361 | 'Unsupported network {0} for helloblock_fetchtx'.format(network)) 362 | data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"] 363 | o = { 364 | "locktime": data["locktime"], 365 | "version": data["version"], 366 | "ins": [], 367 | "outs": [] 368 | } 369 | for inp in data["inputs"]: 370 | o["ins"].append({ 371 | "script": inp["scriptSig"], 372 | "outpoint": { 373 | "index": inp["prevTxoutIndex"], 374 | "hash": inp["prevTxHash"], 375 | }, 376 | "sequence": 4294967295 377 | }) 378 | for outp in data["outputs"]: 379 | o["outs"].append({ 380 | "value": outp["value"], 381 | "script": outp["scriptPubKey"] 382 | }) 383 | from bitcoin.transaction import serialize 384 | from bitcoin.transaction import txhash as TXHASH 385 | tx = serialize(o) 386 | assert TXHASH(tx) == txhash 387 | return tx 388 | 389 | 390 | fetchtx_getters = { 391 | 'bci': bci_fetchtx, 392 | 'blockr': blockr_fetchtx, 393 | 'helloblock': helloblock_fetchtx 394 | } 395 | 396 | 397 | def fetchtx(*args, **kwargs): 398 | f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx) 399 | return f(*args) 400 | 401 | 402 | def firstbits(address): 403 | if len(address) >= 25: 404 | return make_request('https://blockchain.info/q/getfirstbits/'+address) 405 | else: 406 | return make_request( 407 | 'https://blockchain.info/q/resolvefirstbits/'+address) 408 | 409 | 410 | def get_block_at_height(height): 411 | j = json.loads(make_request("https://blockchain.info/block-height/" + 412 | str(height)+"?format=json").decode("utf-8")) 413 | for b in j['blocks']: 414 | if b['main_chain'] is True: 415 | return b 416 | raise Exception("Block at this height not found") 417 | 418 | 419 | def _get_block(inp): 420 | if len(str(inp)) < 64: 421 | return get_block_at_height(inp) 422 | else: 423 | return json.loads(make_request( 424 | 'https://blockchain.info/rawblock/'+inp).decode("utf-8")) 425 | 426 | 427 | def bci_get_block_header_data(inp): 428 | j = _get_block(inp) 429 | return { 430 | 'version': j['ver'], 431 | 'hash': j['hash'], 432 | 'prevhash': j['prev_block'], 433 | 'timestamp': j['time'], 434 | 'merkle_root': j['mrkl_root'], 435 | 'bits': j['bits'], 436 | 'nonce': j['nonce'], 437 | } 438 | 439 | def blockr_get_block_header_data(height, network='btc'): 440 | if network == 'testnet': 441 | blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/" 442 | elif network == 'btc': 443 | blockr_url = "http://btc.blockr.io/api/v1/block/raw/" 444 | else: 445 | raise Exception( 446 | 'Unsupported network {0} for blockr_get_block_header_data'.format(network)) 447 | 448 | k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) 449 | j = k['data'] 450 | return { 451 | 'version': j['version'], 452 | 'hash': j['hash'], 453 | 'prevhash': j['previousblockhash'], 454 | 'timestamp': j['time'], 455 | 'merkle_root': j['merkleroot'], 456 | 'bits': int(j['bits'], 16), 457 | 'nonce': j['nonce'], 458 | } 459 | 460 | 461 | def get_block_timestamp(height, network='btc'): 462 | if network == 'testnet': 463 | blockr_url = "http://tbtc.blockr.io/api/v1/block/info/" 464 | elif network == 'btc': 465 | blockr_url = "http://btc.blockr.io/api/v1/block/info/" 466 | else: 467 | raise Exception( 468 | 'Unsupported network {0} for get_block_timestamp'.format(network)) 469 | 470 | import time, calendar 471 | if isinstance(height, list): 472 | k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8")) 473 | o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'], 474 | "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']} 475 | return [o[x] for x in height] 476 | else: 477 | k = json.loads(make_request(blockr_url + str(height)).decode("utf-8")) 478 | j = k['data']['time_utc'] 479 | return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ")) 480 | 481 | 482 | block_header_data_getters = { 483 | 'bci': bci_get_block_header_data, 484 | 'blockr': blockr_get_block_header_data 485 | } 486 | 487 | 488 | def get_block_header_data(inp, **kwargs): 489 | f = block_header_data_getters.get(kwargs.get('source', ''), 490 | bci_get_block_header_data) 491 | return f(inp, **kwargs) 492 | 493 | 494 | def get_txs_in_block(inp): 495 | j = _get_block(inp) 496 | hashes = [t['hash'] for t in j['tx']] 497 | return hashes 498 | 499 | 500 | def get_block_height(txhash): 501 | j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8")) 502 | return j['block_height'] 503 | 504 | # fromAddr, toAddr, 12345, changeAddress 505 | def get_tx_composite(inputs, outputs, output_value, change_address=None, network=None): 506 | """mktx using blockcypher API""" 507 | inputs = [inputs] if not isinstance(inputs, list) else inputs 508 | outputs = [outputs] if not isinstance(outputs, list) else outputs 509 | network = set_network(change_address or inputs) if not network else network.lower() 510 | url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format( 511 | network=('test3' if network=='testnet' else 'main')) 512 | is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a)) 513 | if any([is_address(x) for x in inputs]): 514 | inputs_type = 'addresses' # also accepts UTXOs, only addresses supported presently 515 | if any([is_address(x) for x in outputs]): 516 | outputs_type = 'addresses' # TODO: add UTXO support 517 | data = { 518 | 'inputs': [{inputs_type: inputs}], 519 | 'confirmations': 0, 520 | 'preference': 'high', 521 | 'outputs': [{outputs_type: outputs, "value": output_value}] 522 | } 523 | if change_address: 524 | data["change_address"] = change_address # 525 | jdata = json.loads(make_request(url, data)) 526 | hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0] 527 | assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash 528 | return txh.encode("utf-8") 529 | 530 | blockcypher_mktx = get_tx_composite 531 | -------------------------------------------------------------------------------- /bitcoin/blocks.py: -------------------------------------------------------------------------------- 1 | from bitcoin.main import * 2 | 3 | 4 | def serialize_header(inp): 5 | o = encode(inp['version'], 256, 4)[::-1] + \ 6 | inp['prevhash'].decode('hex')[::-1] + \ 7 | inp['merkle_root'].decode('hex')[::-1] + \ 8 | encode(inp['timestamp'], 256, 4)[::-1] + \ 9 | encode(inp['bits'], 256, 4)[::-1] + \ 10 | encode(inp['nonce'], 256, 4)[::-1] 11 | h = bin_sha256(bin_sha256(o))[::-1].encode('hex') 12 | assert h == inp['hash'], (sha256(o), inp['hash']) 13 | return o.encode('hex') 14 | 15 | 16 | def deserialize_header(inp): 17 | inp = inp.decode('hex') 18 | return { 19 | "version": decode(inp[:4][::-1], 256), 20 | "prevhash": inp[4:36][::-1].encode('hex'), 21 | "merkle_root": inp[36:68][::-1].encode('hex'), 22 | "timestamp": decode(inp[68:72][::-1], 256), 23 | "bits": decode(inp[72:76][::-1], 256), 24 | "nonce": decode(inp[76:80][::-1], 256), 25 | "hash": bin_sha256(bin_sha256(inp))[::-1].encode('hex') 26 | } 27 | 28 | 29 | def mk_merkle_proof(header, hashes, index): 30 | nodes = [h.decode('hex')[::-1] for h in hashes] 31 | if len(nodes) % 2 and len(nodes) > 2: 32 | nodes.append(nodes[-1]) 33 | layers = [nodes] 34 | while len(nodes) > 1: 35 | newnodes = [] 36 | for i in range(0, len(nodes) - 1, 2): 37 | newnodes.append(bin_sha256(bin_sha256(nodes[i] + nodes[i+1]))) 38 | if len(newnodes) % 2 and len(newnodes) > 2: 39 | newnodes.append(newnodes[-1]) 40 | nodes = newnodes 41 | layers.append(nodes) 42 | # Sanity check, make sure merkle root is valid 43 | assert nodes[0][::-1].encode('hex') == header['merkle_root'] 44 | merkle_siblings = \ 45 | [layers[i][(index >> i) ^ 1] for i in range(len(layers)-1)] 46 | return { 47 | "hash": hashes[index], 48 | "siblings": [x[::-1].encode('hex') for x in merkle_siblings], 49 | "header": header 50 | } 51 | -------------------------------------------------------------------------------- /bitcoin/composite.py: -------------------------------------------------------------------------------- 1 | from bitcoin.main import * 2 | from bitcoin.transaction import * 3 | from bitcoin.bci import * 4 | from bitcoin.deterministic import * 5 | from bitcoin.blocks import * 6 | 7 | 8 | # Takes privkey, address, value (satoshis), fee (satoshis) 9 | def send(frm, to, value, fee=10000, **kwargs): 10 | return sendmultitx(frm, to + ":" + str(value), fee, **kwargs) 11 | 12 | 13 | # Takes privkey, "address1:value1,address2:value2" (satoshis), fee (satoshis) 14 | def sendmultitx(frm, *args, **kwargs): 15 | tv, fee = args[:-1], int(args[-1]) 16 | outs = [] 17 | outvalue = 0 18 | for a in tv: 19 | outs.append(a) 20 | outvalue += int(a.split(":")[1]) 21 | 22 | u = unspent(privtoaddr(frm), **kwargs) 23 | u2 = select(u, int(outvalue)+int(fee)) 24 | argz = u2 + outs + [privtoaddr(frm), fee] 25 | tx = mksend(*argz) 26 | tx2 = signall(tx, frm) 27 | return pushtx(tx2, **kwargs) 28 | 29 | 30 | # Takes address, address, value (satoshis), fee(satoshis) 31 | def preparetx(frm, to, value, fee=10000, **kwargs): 32 | tovalues = to + ":" + str(value) 33 | return preparemultitx(frm, tovalues, fee, **kwargs) 34 | 35 | 36 | # Takes address, address:value, address:value ... (satoshis), fee(satoshis) 37 | def preparemultitx(frm, *args, **kwargs): 38 | tv, fee = args[:-1], int(args[-1]) 39 | outs = [] 40 | outvalue = 0 41 | for a in tv: 42 | outs.append(a) 43 | outvalue += int(a.split(":")[1]) 44 | 45 | u = unspent(frm, **kwargs) 46 | u2 = select(u, int(outvalue)+int(fee)) 47 | argz = u2 + outs + [frm, fee] 48 | return mksend(*argz) 49 | 50 | 51 | # BIP32 hierarchical deterministic multisig script 52 | def bip32_hdm_script(*args): 53 | if len(args) == 3: 54 | keys, req, path = args 55 | else: 56 | i, keys, path = 0, [], [] 57 | while len(args[i]) > 40: 58 | keys.append(args[i]) 59 | i += 1 60 | req = int(args[i]) 61 | path = map(int, args[i+1:]) 62 | pubs = sorted(map(lambda x: bip32_descend(x, path), keys)) 63 | return mk_multisig_script(pubs, req) 64 | 65 | 66 | # BIP32 hierarchical deterministic multisig address 67 | def bip32_hdm_addr(*args): 68 | return scriptaddr(bip32_hdm_script(*args)) 69 | 70 | 71 | # Setup a coinvault transaction 72 | def setup_coinvault_tx(tx, script): 73 | txobj = deserialize(tx) 74 | N = deserialize_script(script)[-2] 75 | for inp in txobj["ins"]: 76 | inp["script"] = serialize_script([None] * (N+1) + [script]) 77 | return serialize(txobj) 78 | 79 | 80 | # Sign a coinvault transaction 81 | def sign_coinvault_tx(tx, priv): 82 | pub = privtopub(priv) 83 | txobj = deserialize(tx) 84 | subscript = deserialize_script(txobj['ins'][0]['script']) 85 | oscript = deserialize_script(subscript[-1]) 86 | k, pubs = oscript[0], oscript[1:-2] 87 | for j in range(len(txobj['ins'])): 88 | scr = deserialize_script(txobj['ins'][j]['script']) 89 | for i, p in enumerate(pubs): 90 | if p == pub: 91 | scr[i+1] = multisign(tx, j, subscript[-1], priv) 92 | if len(filter(lambda x: x, scr[1:-1])) >= k: 93 | scr = [None] + filter(lambda x: x, scr[1:-1])[:k] + [scr[-1]] 94 | txobj['ins'][j]['script'] = serialize_script(scr) 95 | return serialize(txobj) 96 | 97 | 98 | # Inspects a transaction 99 | def inspect(tx, **kwargs): 100 | d = deserialize(tx) 101 | isum = 0 102 | ins = {} 103 | for _in in d['ins']: 104 | h = _in['outpoint']['hash'] 105 | i = _in['outpoint']['index'] 106 | prevout = deserialize(fetchtx(h, **kwargs))['outs'][i] 107 | isum += prevout['value'] 108 | a = script_to_address(prevout['script']) 109 | ins[a] = ins.get(a, 0) + prevout['value'] 110 | outs = [] 111 | osum = 0 112 | for _out in d['outs']: 113 | outs.append({'address': script_to_address(_out['script']), 114 | 'value': _out['value']}) 115 | osum += _out['value'] 116 | return { 117 | 'fee': isum - osum, 118 | 'outs': outs, 119 | 'ins': ins 120 | } 121 | 122 | 123 | def merkle_prove(txhash): 124 | blocknum = str(get_block_height(txhash)) 125 | header = get_block_header_data(blocknum) 126 | hashes = get_txs_in_block(blocknum) 127 | i = hashes.index(txhash) 128 | return mk_merkle_proof(header, hashes, i) 129 | -------------------------------------------------------------------------------- /bitcoin/deterministic.py: -------------------------------------------------------------------------------- 1 | from bitcoin.main import * 2 | import hmac 3 | import hashlib 4 | from binascii import hexlify 5 | # Electrum wallets 6 | 7 | 8 | def electrum_stretch(seed): 9 | return slowsha(seed) 10 | 11 | # Accepts seed or stretched seed, returns master public key 12 | 13 | 14 | def electrum_mpk(seed): 15 | if len(seed) == 32: 16 | seed = electrum_stretch(seed) 17 | return privkey_to_pubkey(seed)[2:] 18 | 19 | # Accepts (seed or stretched seed), index and secondary index 20 | # (conventionally 0 for ordinary addresses, 1 for change) , returns privkey 21 | 22 | 23 | def electrum_privkey(seed, n, for_change=0): 24 | if len(seed) == 32: 25 | seed = electrum_stretch(seed) 26 | mpk = electrum_mpk(seed) 27 | offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+binascii.unhexlify(mpk)) 28 | return add_privkeys(seed, offset) 29 | 30 | # Accepts (seed or stretched seed or master pubkey), index and secondary index 31 | # (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey 32 | 33 | 34 | def electrum_pubkey(masterkey, n, for_change=0): 35 | if len(masterkey) == 32: 36 | mpk = electrum_mpk(electrum_stretch(masterkey)) 37 | elif len(masterkey) == 64: 38 | mpk = electrum_mpk(masterkey) 39 | else: 40 | mpk = masterkey 41 | bin_mpk = encode_pubkey(mpk, 'bin_electrum') 42 | offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+bin_mpk) 43 | return add_pubkeys('04'+mpk, privtopub(offset)) 44 | 45 | # seed/stretched seed/pubkey -> address (convenience method) 46 | 47 | 48 | def electrum_address(masterkey, n, for_change=0, version=0): 49 | return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), version) 50 | 51 | # Given a master public key, a private key from that wallet and its index, 52 | # cracks the secret exponent which can be used to generate all other private 53 | # keys in the wallet 54 | 55 | 56 | def crack_electrum_wallet(mpk, pk, n, for_change=0): 57 | bin_mpk = encode_pubkey(mpk, 'bin_electrum') 58 | offset = dbl_sha256(str(n)+':'+str(for_change)+':'+bin_mpk) 59 | return subtract_privkeys(pk, offset) 60 | 61 | # Below code ASSUMES binary inputs and compressed pubkeys 62 | MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' 63 | MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' 64 | TESTNET_PRIVATE = b'\x04\x35\x83\x94' 65 | TESTNET_PUBLIC = b'\x04\x35\x87\xCF' 66 | PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] 67 | PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] 68 | 69 | # BIP32 child key derivation 70 | 71 | 72 | def raw_bip32_ckd(rawtuple, i): 73 | vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple 74 | i = int(i) 75 | 76 | if vbytes in PRIVATE: 77 | priv = key 78 | pub = privtopub(key) 79 | else: 80 | pub = key 81 | 82 | if i >= 2**31: 83 | if vbytes in PUBLIC: 84 | raise Exception("Can't do private derivation on public key!") 85 | I = hmac.new(chaincode, b'\x00'+priv[:32]+encode(i, 256, 4), hashlib.sha512).digest() 86 | else: 87 | I = hmac.new(chaincode, pub+encode(i, 256, 4), hashlib.sha512).digest() 88 | 89 | if vbytes in PRIVATE: 90 | newkey = add_privkeys(I[:32]+B'\x01', priv) 91 | fingerprint = bin_hash160(privtopub(key))[:4] 92 | if vbytes in PUBLIC: 93 | newkey = add_pubkeys(compress(privtopub(I[:32])), key) 94 | fingerprint = bin_hash160(key)[:4] 95 | 96 | return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) 97 | 98 | 99 | def bip32_serialize(rawtuple): 100 | vbytes, depth, fingerprint, i, chaincode, key = rawtuple 101 | i = encode(i, 256, 4) 102 | chaincode = encode(hash_to_int(chaincode), 256, 32) 103 | keydata = b'\x00'+key[:-1] if vbytes in PRIVATE else key 104 | bindata = vbytes + from_int_to_byte(depth % 256) + fingerprint + i + chaincode + keydata 105 | return changebase(bindata+bin_dbl_sha256(bindata)[:4], 256, 58) 106 | 107 | 108 | def bip32_deserialize(data): 109 | dbin = changebase(data, 58, 256) 110 | if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: 111 | raise Exception("Invalid checksum") 112 | vbytes = dbin[0:4] 113 | depth = from_byte_to_int(dbin[4]) 114 | fingerprint = dbin[5:9] 115 | i = decode(dbin[9:13], 256) 116 | chaincode = dbin[13:45] 117 | key = dbin[46:78]+b'\x01' if vbytes in PRIVATE else dbin[45:78] 118 | return (vbytes, depth, fingerprint, i, chaincode, key) 119 | 120 | 121 | def raw_bip32_privtopub(rawtuple): 122 | vbytes, depth, fingerprint, i, chaincode, key = rawtuple 123 | newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC 124 | return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key)) 125 | 126 | 127 | def bip32_privtopub(data): 128 | return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) 129 | 130 | 131 | def bip32_ckd(data, i): 132 | return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) 133 | 134 | 135 | def bip32_master_key(seed, vbytes=MAINNET_PRIVATE): 136 | I = hmac.new(from_string_to_bytes("Bitcoin seed"), seed, hashlib.sha512).digest() 137 | return bip32_serialize((vbytes, 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01')) 138 | 139 | 140 | def bip32_bin_extract_key(data): 141 | return bip32_deserialize(data)[-1] 142 | 143 | 144 | def bip32_extract_key(data): 145 | return safe_hexlify(bip32_deserialize(data)[-1]) 146 | 147 | # Exploits the same vulnerability as above in Electrum wallets 148 | # Takes a BIP32 pubkey and one of the child privkeys of its corresponding 149 | # privkey and returns the BIP32 privkey associated with that pubkey 150 | 151 | 152 | def raw_crack_bip32_privkey(parent_pub, priv): 153 | vbytes, depth, fingerprint, i, chaincode, key = priv 154 | pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub 155 | i = int(i) 156 | 157 | if i >= 2**31: 158 | raise Exception("Can't crack private derivation!") 159 | 160 | I = hmac.new(pchaincode, pkey+encode(i, 256, 4), hashlib.sha512).digest() 161 | 162 | pprivkey = subtract_privkeys(key, I[:32]+b'\x01') 163 | 164 | newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE 165 | return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) 166 | 167 | 168 | def crack_bip32_privkey(parent_pub, priv): 169 | dsppub = bip32_deserialize(parent_pub) 170 | dspriv = bip32_deserialize(priv) 171 | return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv)) 172 | 173 | 174 | def coinvault_pub_to_bip32(*args): 175 | if len(args) == 1: 176 | args = args[0].split(' ') 177 | vals = map(int, args[34:]) 178 | I1 = ''.join(map(chr, vals[:33])) 179 | I2 = ''.join(map(chr, vals[35:67])) 180 | return bip32_serialize((MAINNET_PUBLIC, 0, b'\x00'*4, 0, I2, I1)) 181 | 182 | 183 | def coinvault_priv_to_bip32(*args): 184 | if len(args) == 1: 185 | args = args[0].split(' ') 186 | vals = map(int, args[34:]) 187 | I2 = ''.join(map(chr, vals[35:67])) 188 | I3 = ''.join(map(chr, vals[72:104])) 189 | return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01')) 190 | 191 | 192 | def bip32_descend(*args): 193 | if len(args) == 2 and isinstance(args[1], list): 194 | key, path = args 195 | else: 196 | key, path = args[0], map(int, args[1:]) 197 | for p in path: 198 | key = bip32_ckd(key, p) 199 | return bip32_extract_key(key) 200 | -------------------------------------------------------------------------------- /bitcoin/english.txt: -------------------------------------------------------------------------------- 1 | abandon 2 | ability 3 | able 4 | about 5 | above 6 | absent 7 | absorb 8 | abstract 9 | absurd 10 | abuse 11 | access 12 | accident 13 | account 14 | accuse 15 | achieve 16 | acid 17 | acoustic 18 | acquire 19 | across 20 | act 21 | action 22 | actor 23 | actress 24 | actual 25 | adapt 26 | add 27 | addict 28 | address 29 | adjust 30 | admit 31 | adult 32 | advance 33 | advice 34 | aerobic 35 | affair 36 | afford 37 | afraid 38 | again 39 | age 40 | agent 41 | agree 42 | ahead 43 | aim 44 | air 45 | airport 46 | aisle 47 | alarm 48 | album 49 | alcohol 50 | alert 51 | alien 52 | all 53 | alley 54 | allow 55 | almost 56 | alone 57 | alpha 58 | already 59 | also 60 | alter 61 | always 62 | amateur 63 | amazing 64 | among 65 | amount 66 | amused 67 | analyst 68 | anchor 69 | ancient 70 | anger 71 | angle 72 | angry 73 | animal 74 | ankle 75 | announce 76 | annual 77 | another 78 | answer 79 | antenna 80 | antique 81 | anxiety 82 | any 83 | apart 84 | apology 85 | appear 86 | apple 87 | approve 88 | april 89 | arch 90 | arctic 91 | area 92 | arena 93 | argue 94 | arm 95 | armed 96 | armor 97 | army 98 | around 99 | arrange 100 | arrest 101 | arrive 102 | arrow 103 | art 104 | artefact 105 | artist 106 | artwork 107 | ask 108 | aspect 109 | assault 110 | asset 111 | assist 112 | assume 113 | asthma 114 | athlete 115 | atom 116 | attack 117 | attend 118 | attitude 119 | attract 120 | auction 121 | audit 122 | august 123 | aunt 124 | author 125 | auto 126 | autumn 127 | average 128 | avocado 129 | avoid 130 | awake 131 | aware 132 | away 133 | awesome 134 | awful 135 | awkward 136 | axis 137 | baby 138 | bachelor 139 | bacon 140 | badge 141 | bag 142 | balance 143 | balcony 144 | ball 145 | bamboo 146 | banana 147 | banner 148 | bar 149 | barely 150 | bargain 151 | barrel 152 | base 153 | basic 154 | basket 155 | battle 156 | beach 157 | bean 158 | beauty 159 | because 160 | become 161 | beef 162 | before 163 | begin 164 | behave 165 | behind 166 | believe 167 | below 168 | belt 169 | bench 170 | benefit 171 | best 172 | betray 173 | better 174 | between 175 | beyond 176 | bicycle 177 | bid 178 | bike 179 | bind 180 | biology 181 | bird 182 | birth 183 | bitter 184 | black 185 | blade 186 | blame 187 | blanket 188 | blast 189 | bleak 190 | bless 191 | blind 192 | blood 193 | blossom 194 | blouse 195 | blue 196 | blur 197 | blush 198 | board 199 | boat 200 | body 201 | boil 202 | bomb 203 | bone 204 | bonus 205 | book 206 | boost 207 | border 208 | boring 209 | borrow 210 | boss 211 | bottom 212 | bounce 213 | box 214 | boy 215 | bracket 216 | brain 217 | brand 218 | brass 219 | brave 220 | bread 221 | breeze 222 | brick 223 | bridge 224 | brief 225 | bright 226 | bring 227 | brisk 228 | broccoli 229 | broken 230 | bronze 231 | broom 232 | brother 233 | brown 234 | brush 235 | bubble 236 | buddy 237 | budget 238 | buffalo 239 | build 240 | bulb 241 | bulk 242 | bullet 243 | bundle 244 | bunker 245 | burden 246 | burger 247 | burst 248 | bus 249 | business 250 | busy 251 | butter 252 | buyer 253 | buzz 254 | cabbage 255 | cabin 256 | cable 257 | cactus 258 | cage 259 | cake 260 | call 261 | calm 262 | camera 263 | camp 264 | can 265 | canal 266 | cancel 267 | candy 268 | cannon 269 | canoe 270 | canvas 271 | canyon 272 | capable 273 | capital 274 | captain 275 | car 276 | carbon 277 | card 278 | cargo 279 | carpet 280 | carry 281 | cart 282 | case 283 | cash 284 | casino 285 | castle 286 | casual 287 | cat 288 | catalog 289 | catch 290 | category 291 | cattle 292 | caught 293 | cause 294 | caution 295 | cave 296 | ceiling 297 | celery 298 | cement 299 | census 300 | century 301 | cereal 302 | certain 303 | chair 304 | chalk 305 | champion 306 | change 307 | chaos 308 | chapter 309 | charge 310 | chase 311 | chat 312 | cheap 313 | check 314 | cheese 315 | chef 316 | cherry 317 | chest 318 | chicken 319 | chief 320 | child 321 | chimney 322 | choice 323 | choose 324 | chronic 325 | chuckle 326 | chunk 327 | churn 328 | cigar 329 | cinnamon 330 | circle 331 | citizen 332 | city 333 | civil 334 | claim 335 | clap 336 | clarify 337 | claw 338 | clay 339 | clean 340 | clerk 341 | clever 342 | click 343 | client 344 | cliff 345 | climb 346 | clinic 347 | clip 348 | clock 349 | clog 350 | close 351 | cloth 352 | cloud 353 | clown 354 | club 355 | clump 356 | cluster 357 | clutch 358 | coach 359 | coast 360 | coconut 361 | code 362 | coffee 363 | coil 364 | coin 365 | collect 366 | color 367 | column 368 | combine 369 | come 370 | comfort 371 | comic 372 | common 373 | company 374 | concert 375 | conduct 376 | confirm 377 | congress 378 | connect 379 | consider 380 | control 381 | convince 382 | cook 383 | cool 384 | copper 385 | copy 386 | coral 387 | core 388 | corn 389 | correct 390 | cost 391 | cotton 392 | couch 393 | country 394 | couple 395 | course 396 | cousin 397 | cover 398 | coyote 399 | crack 400 | cradle 401 | craft 402 | cram 403 | crane 404 | crash 405 | crater 406 | crawl 407 | crazy 408 | cream 409 | credit 410 | creek 411 | crew 412 | cricket 413 | crime 414 | crisp 415 | critic 416 | crop 417 | cross 418 | crouch 419 | crowd 420 | crucial 421 | cruel 422 | cruise 423 | crumble 424 | crunch 425 | crush 426 | cry 427 | crystal 428 | cube 429 | culture 430 | cup 431 | cupboard 432 | curious 433 | current 434 | curtain 435 | curve 436 | cushion 437 | custom 438 | cute 439 | cycle 440 | dad 441 | damage 442 | damp 443 | dance 444 | danger 445 | daring 446 | dash 447 | daughter 448 | dawn 449 | day 450 | deal 451 | debate 452 | debris 453 | decade 454 | december 455 | decide 456 | decline 457 | decorate 458 | decrease 459 | deer 460 | defense 461 | define 462 | defy 463 | degree 464 | delay 465 | deliver 466 | demand 467 | demise 468 | denial 469 | dentist 470 | deny 471 | depart 472 | depend 473 | deposit 474 | depth 475 | deputy 476 | derive 477 | describe 478 | desert 479 | design 480 | desk 481 | despair 482 | destroy 483 | detail 484 | detect 485 | develop 486 | device 487 | devote 488 | diagram 489 | dial 490 | diamond 491 | diary 492 | dice 493 | diesel 494 | diet 495 | differ 496 | digital 497 | dignity 498 | dilemma 499 | dinner 500 | dinosaur 501 | direct 502 | dirt 503 | disagree 504 | discover 505 | disease 506 | dish 507 | dismiss 508 | disorder 509 | display 510 | distance 511 | divert 512 | divide 513 | divorce 514 | dizzy 515 | doctor 516 | document 517 | dog 518 | doll 519 | dolphin 520 | domain 521 | donate 522 | donkey 523 | donor 524 | door 525 | dose 526 | double 527 | dove 528 | draft 529 | dragon 530 | drama 531 | drastic 532 | draw 533 | dream 534 | dress 535 | drift 536 | drill 537 | drink 538 | drip 539 | drive 540 | drop 541 | drum 542 | dry 543 | duck 544 | dumb 545 | dune 546 | during 547 | dust 548 | dutch 549 | duty 550 | dwarf 551 | dynamic 552 | eager 553 | eagle 554 | early 555 | earn 556 | earth 557 | easily 558 | east 559 | easy 560 | echo 561 | ecology 562 | economy 563 | edge 564 | edit 565 | educate 566 | effort 567 | egg 568 | eight 569 | either 570 | elbow 571 | elder 572 | electric 573 | elegant 574 | element 575 | elephant 576 | elevator 577 | elite 578 | else 579 | embark 580 | embody 581 | embrace 582 | emerge 583 | emotion 584 | employ 585 | empower 586 | empty 587 | enable 588 | enact 589 | end 590 | endless 591 | endorse 592 | enemy 593 | energy 594 | enforce 595 | engage 596 | engine 597 | enhance 598 | enjoy 599 | enlist 600 | enough 601 | enrich 602 | enroll 603 | ensure 604 | enter 605 | entire 606 | entry 607 | envelope 608 | episode 609 | equal 610 | equip 611 | era 612 | erase 613 | erode 614 | erosion 615 | error 616 | erupt 617 | escape 618 | essay 619 | essence 620 | estate 621 | eternal 622 | ethics 623 | evidence 624 | evil 625 | evoke 626 | evolve 627 | exact 628 | example 629 | excess 630 | exchange 631 | excite 632 | exclude 633 | excuse 634 | execute 635 | exercise 636 | exhaust 637 | exhibit 638 | exile 639 | exist 640 | exit 641 | exotic 642 | expand 643 | expect 644 | expire 645 | explain 646 | expose 647 | express 648 | extend 649 | extra 650 | eye 651 | eyebrow 652 | fabric 653 | face 654 | faculty 655 | fade 656 | faint 657 | faith 658 | fall 659 | false 660 | fame 661 | family 662 | famous 663 | fan 664 | fancy 665 | fantasy 666 | farm 667 | fashion 668 | fat 669 | fatal 670 | father 671 | fatigue 672 | fault 673 | favorite 674 | feature 675 | february 676 | federal 677 | fee 678 | feed 679 | feel 680 | female 681 | fence 682 | festival 683 | fetch 684 | fever 685 | few 686 | fiber 687 | fiction 688 | field 689 | figure 690 | file 691 | film 692 | filter 693 | final 694 | find 695 | fine 696 | finger 697 | finish 698 | fire 699 | firm 700 | first 701 | fiscal 702 | fish 703 | fit 704 | fitness 705 | fix 706 | flag 707 | flame 708 | flash 709 | flat 710 | flavor 711 | flee 712 | flight 713 | flip 714 | float 715 | flock 716 | floor 717 | flower 718 | fluid 719 | flush 720 | fly 721 | foam 722 | focus 723 | fog 724 | foil 725 | fold 726 | follow 727 | food 728 | foot 729 | force 730 | forest 731 | forget 732 | fork 733 | fortune 734 | forum 735 | forward 736 | fossil 737 | foster 738 | found 739 | fox 740 | fragile 741 | frame 742 | frequent 743 | fresh 744 | friend 745 | fringe 746 | frog 747 | front 748 | frost 749 | frown 750 | frozen 751 | fruit 752 | fuel 753 | fun 754 | funny 755 | furnace 756 | fury 757 | future 758 | gadget 759 | gain 760 | galaxy 761 | gallery 762 | game 763 | gap 764 | garage 765 | garbage 766 | garden 767 | garlic 768 | garment 769 | gas 770 | gasp 771 | gate 772 | gather 773 | gauge 774 | gaze 775 | general 776 | genius 777 | genre 778 | gentle 779 | genuine 780 | gesture 781 | ghost 782 | giant 783 | gift 784 | giggle 785 | ginger 786 | giraffe 787 | girl 788 | give 789 | glad 790 | glance 791 | glare 792 | glass 793 | glide 794 | glimpse 795 | globe 796 | gloom 797 | glory 798 | glove 799 | glow 800 | glue 801 | goat 802 | goddess 803 | gold 804 | good 805 | goose 806 | gorilla 807 | gospel 808 | gossip 809 | govern 810 | gown 811 | grab 812 | grace 813 | grain 814 | grant 815 | grape 816 | grass 817 | gravity 818 | great 819 | green 820 | grid 821 | grief 822 | grit 823 | grocery 824 | group 825 | grow 826 | grunt 827 | guard 828 | guess 829 | guide 830 | guilt 831 | guitar 832 | gun 833 | gym 834 | habit 835 | hair 836 | half 837 | hammer 838 | hamster 839 | hand 840 | happy 841 | harbor 842 | hard 843 | harsh 844 | harvest 845 | hat 846 | have 847 | hawk 848 | hazard 849 | head 850 | health 851 | heart 852 | heavy 853 | hedgehog 854 | height 855 | hello 856 | helmet 857 | help 858 | hen 859 | hero 860 | hidden 861 | high 862 | hill 863 | hint 864 | hip 865 | hire 866 | history 867 | hobby 868 | hockey 869 | hold 870 | hole 871 | holiday 872 | hollow 873 | home 874 | honey 875 | hood 876 | hope 877 | horn 878 | horror 879 | horse 880 | hospital 881 | host 882 | hotel 883 | hour 884 | hover 885 | hub 886 | huge 887 | human 888 | humble 889 | humor 890 | hundred 891 | hungry 892 | hunt 893 | hurdle 894 | hurry 895 | hurt 896 | husband 897 | hybrid 898 | ice 899 | icon 900 | idea 901 | identify 902 | idle 903 | ignore 904 | ill 905 | illegal 906 | illness 907 | image 908 | imitate 909 | immense 910 | immune 911 | impact 912 | impose 913 | improve 914 | impulse 915 | inch 916 | include 917 | income 918 | increase 919 | index 920 | indicate 921 | indoor 922 | industry 923 | infant 924 | inflict 925 | inform 926 | inhale 927 | inherit 928 | initial 929 | inject 930 | injury 931 | inmate 932 | inner 933 | innocent 934 | input 935 | inquiry 936 | insane 937 | insect 938 | inside 939 | inspire 940 | install 941 | intact 942 | interest 943 | into 944 | invest 945 | invite 946 | involve 947 | iron 948 | island 949 | isolate 950 | issue 951 | item 952 | ivory 953 | jacket 954 | jaguar 955 | jar 956 | jazz 957 | jealous 958 | jeans 959 | jelly 960 | jewel 961 | job 962 | join 963 | joke 964 | journey 965 | joy 966 | judge 967 | juice 968 | jump 969 | jungle 970 | junior 971 | junk 972 | just 973 | kangaroo 974 | keen 975 | keep 976 | ketchup 977 | key 978 | kick 979 | kid 980 | kidney 981 | kind 982 | kingdom 983 | kiss 984 | kit 985 | kitchen 986 | kite 987 | kitten 988 | kiwi 989 | knee 990 | knife 991 | knock 992 | know 993 | lab 994 | label 995 | labor 996 | ladder 997 | lady 998 | lake 999 | lamp 1000 | language 1001 | laptop 1002 | large 1003 | later 1004 | latin 1005 | laugh 1006 | laundry 1007 | lava 1008 | law 1009 | lawn 1010 | lawsuit 1011 | layer 1012 | lazy 1013 | leader 1014 | leaf 1015 | learn 1016 | leave 1017 | lecture 1018 | left 1019 | leg 1020 | legal 1021 | legend 1022 | leisure 1023 | lemon 1024 | lend 1025 | length 1026 | lens 1027 | leopard 1028 | lesson 1029 | letter 1030 | level 1031 | liar 1032 | liberty 1033 | library 1034 | license 1035 | life 1036 | lift 1037 | light 1038 | like 1039 | limb 1040 | limit 1041 | link 1042 | lion 1043 | liquid 1044 | list 1045 | little 1046 | live 1047 | lizard 1048 | load 1049 | loan 1050 | lobster 1051 | local 1052 | lock 1053 | logic 1054 | lonely 1055 | long 1056 | loop 1057 | lottery 1058 | loud 1059 | lounge 1060 | love 1061 | loyal 1062 | lucky 1063 | luggage 1064 | lumber 1065 | lunar 1066 | lunch 1067 | luxury 1068 | lyrics 1069 | machine 1070 | mad 1071 | magic 1072 | magnet 1073 | maid 1074 | mail 1075 | main 1076 | major 1077 | make 1078 | mammal 1079 | man 1080 | manage 1081 | mandate 1082 | mango 1083 | mansion 1084 | manual 1085 | maple 1086 | marble 1087 | march 1088 | margin 1089 | marine 1090 | market 1091 | marriage 1092 | mask 1093 | mass 1094 | master 1095 | match 1096 | material 1097 | math 1098 | matrix 1099 | matter 1100 | maximum 1101 | maze 1102 | meadow 1103 | mean 1104 | measure 1105 | meat 1106 | mechanic 1107 | medal 1108 | media 1109 | melody 1110 | melt 1111 | member 1112 | memory 1113 | mention 1114 | menu 1115 | mercy 1116 | merge 1117 | merit 1118 | merry 1119 | mesh 1120 | message 1121 | metal 1122 | method 1123 | middle 1124 | midnight 1125 | milk 1126 | million 1127 | mimic 1128 | mind 1129 | minimum 1130 | minor 1131 | minute 1132 | miracle 1133 | mirror 1134 | misery 1135 | miss 1136 | mistake 1137 | mix 1138 | mixed 1139 | mixture 1140 | mobile 1141 | model 1142 | modify 1143 | mom 1144 | moment 1145 | monitor 1146 | monkey 1147 | monster 1148 | month 1149 | moon 1150 | moral 1151 | more 1152 | morning 1153 | mosquito 1154 | mother 1155 | motion 1156 | motor 1157 | mountain 1158 | mouse 1159 | move 1160 | movie 1161 | much 1162 | muffin 1163 | mule 1164 | multiply 1165 | muscle 1166 | museum 1167 | mushroom 1168 | music 1169 | must 1170 | mutual 1171 | myself 1172 | mystery 1173 | myth 1174 | naive 1175 | name 1176 | napkin 1177 | narrow 1178 | nasty 1179 | nation 1180 | nature 1181 | near 1182 | neck 1183 | need 1184 | negative 1185 | neglect 1186 | neither 1187 | nephew 1188 | nerve 1189 | nest 1190 | net 1191 | network 1192 | neutral 1193 | never 1194 | news 1195 | next 1196 | nice 1197 | night 1198 | noble 1199 | noise 1200 | nominee 1201 | noodle 1202 | normal 1203 | north 1204 | nose 1205 | notable 1206 | note 1207 | nothing 1208 | notice 1209 | novel 1210 | now 1211 | nuclear 1212 | number 1213 | nurse 1214 | nut 1215 | oak 1216 | obey 1217 | object 1218 | oblige 1219 | obscure 1220 | observe 1221 | obtain 1222 | obvious 1223 | occur 1224 | ocean 1225 | october 1226 | odor 1227 | off 1228 | offer 1229 | office 1230 | often 1231 | oil 1232 | okay 1233 | old 1234 | olive 1235 | olympic 1236 | omit 1237 | once 1238 | one 1239 | onion 1240 | online 1241 | only 1242 | open 1243 | opera 1244 | opinion 1245 | oppose 1246 | option 1247 | orange 1248 | orbit 1249 | orchard 1250 | order 1251 | ordinary 1252 | organ 1253 | orient 1254 | original 1255 | orphan 1256 | ostrich 1257 | other 1258 | outdoor 1259 | outer 1260 | output 1261 | outside 1262 | oval 1263 | oven 1264 | over 1265 | own 1266 | owner 1267 | oxygen 1268 | oyster 1269 | ozone 1270 | pact 1271 | paddle 1272 | page 1273 | pair 1274 | palace 1275 | palm 1276 | panda 1277 | panel 1278 | panic 1279 | panther 1280 | paper 1281 | parade 1282 | parent 1283 | park 1284 | parrot 1285 | party 1286 | pass 1287 | patch 1288 | path 1289 | patient 1290 | patrol 1291 | pattern 1292 | pause 1293 | pave 1294 | payment 1295 | peace 1296 | peanut 1297 | pear 1298 | peasant 1299 | pelican 1300 | pen 1301 | penalty 1302 | pencil 1303 | people 1304 | pepper 1305 | perfect 1306 | permit 1307 | person 1308 | pet 1309 | phone 1310 | photo 1311 | phrase 1312 | physical 1313 | piano 1314 | picnic 1315 | picture 1316 | piece 1317 | pig 1318 | pigeon 1319 | pill 1320 | pilot 1321 | pink 1322 | pioneer 1323 | pipe 1324 | pistol 1325 | pitch 1326 | pizza 1327 | place 1328 | planet 1329 | plastic 1330 | plate 1331 | play 1332 | please 1333 | pledge 1334 | pluck 1335 | plug 1336 | plunge 1337 | poem 1338 | poet 1339 | point 1340 | polar 1341 | pole 1342 | police 1343 | pond 1344 | pony 1345 | pool 1346 | popular 1347 | portion 1348 | position 1349 | possible 1350 | post 1351 | potato 1352 | pottery 1353 | poverty 1354 | powder 1355 | power 1356 | practice 1357 | praise 1358 | predict 1359 | prefer 1360 | prepare 1361 | present 1362 | pretty 1363 | prevent 1364 | price 1365 | pride 1366 | primary 1367 | print 1368 | priority 1369 | prison 1370 | private 1371 | prize 1372 | problem 1373 | process 1374 | produce 1375 | profit 1376 | program 1377 | project 1378 | promote 1379 | proof 1380 | property 1381 | prosper 1382 | protect 1383 | proud 1384 | provide 1385 | public 1386 | pudding 1387 | pull 1388 | pulp 1389 | pulse 1390 | pumpkin 1391 | punch 1392 | pupil 1393 | puppy 1394 | purchase 1395 | purity 1396 | purpose 1397 | purse 1398 | push 1399 | put 1400 | puzzle 1401 | pyramid 1402 | quality 1403 | quantum 1404 | quarter 1405 | question 1406 | quick 1407 | quit 1408 | quiz 1409 | quote 1410 | rabbit 1411 | raccoon 1412 | race 1413 | rack 1414 | radar 1415 | radio 1416 | rail 1417 | rain 1418 | raise 1419 | rally 1420 | ramp 1421 | ranch 1422 | random 1423 | range 1424 | rapid 1425 | rare 1426 | rate 1427 | rather 1428 | raven 1429 | raw 1430 | razor 1431 | ready 1432 | real 1433 | reason 1434 | rebel 1435 | rebuild 1436 | recall 1437 | receive 1438 | recipe 1439 | record 1440 | recycle 1441 | reduce 1442 | reflect 1443 | reform 1444 | refuse 1445 | region 1446 | regret 1447 | regular 1448 | reject 1449 | relax 1450 | release 1451 | relief 1452 | rely 1453 | remain 1454 | remember 1455 | remind 1456 | remove 1457 | render 1458 | renew 1459 | rent 1460 | reopen 1461 | repair 1462 | repeat 1463 | replace 1464 | report 1465 | require 1466 | rescue 1467 | resemble 1468 | resist 1469 | resource 1470 | response 1471 | result 1472 | retire 1473 | retreat 1474 | return 1475 | reunion 1476 | reveal 1477 | review 1478 | reward 1479 | rhythm 1480 | rib 1481 | ribbon 1482 | rice 1483 | rich 1484 | ride 1485 | ridge 1486 | rifle 1487 | right 1488 | rigid 1489 | ring 1490 | riot 1491 | ripple 1492 | risk 1493 | ritual 1494 | rival 1495 | river 1496 | road 1497 | roast 1498 | robot 1499 | robust 1500 | rocket 1501 | romance 1502 | roof 1503 | rookie 1504 | room 1505 | rose 1506 | rotate 1507 | rough 1508 | round 1509 | route 1510 | royal 1511 | rubber 1512 | rude 1513 | rug 1514 | rule 1515 | run 1516 | runway 1517 | rural 1518 | sad 1519 | saddle 1520 | sadness 1521 | safe 1522 | sail 1523 | salad 1524 | salmon 1525 | salon 1526 | salt 1527 | salute 1528 | same 1529 | sample 1530 | sand 1531 | satisfy 1532 | satoshi 1533 | sauce 1534 | sausage 1535 | save 1536 | say 1537 | scale 1538 | scan 1539 | scare 1540 | scatter 1541 | scene 1542 | scheme 1543 | school 1544 | science 1545 | scissors 1546 | scorpion 1547 | scout 1548 | scrap 1549 | screen 1550 | script 1551 | scrub 1552 | sea 1553 | search 1554 | season 1555 | seat 1556 | second 1557 | secret 1558 | section 1559 | security 1560 | seed 1561 | seek 1562 | segment 1563 | select 1564 | sell 1565 | seminar 1566 | senior 1567 | sense 1568 | sentence 1569 | series 1570 | service 1571 | session 1572 | settle 1573 | setup 1574 | seven 1575 | shadow 1576 | shaft 1577 | shallow 1578 | share 1579 | shed 1580 | shell 1581 | sheriff 1582 | shield 1583 | shift 1584 | shine 1585 | ship 1586 | shiver 1587 | shock 1588 | shoe 1589 | shoot 1590 | shop 1591 | short 1592 | shoulder 1593 | shove 1594 | shrimp 1595 | shrug 1596 | shuffle 1597 | shy 1598 | sibling 1599 | sick 1600 | side 1601 | siege 1602 | sight 1603 | sign 1604 | silent 1605 | silk 1606 | silly 1607 | silver 1608 | similar 1609 | simple 1610 | since 1611 | sing 1612 | siren 1613 | sister 1614 | situate 1615 | six 1616 | size 1617 | skate 1618 | sketch 1619 | ski 1620 | skill 1621 | skin 1622 | skirt 1623 | skull 1624 | slab 1625 | slam 1626 | sleep 1627 | slender 1628 | slice 1629 | slide 1630 | slight 1631 | slim 1632 | slogan 1633 | slot 1634 | slow 1635 | slush 1636 | small 1637 | smart 1638 | smile 1639 | smoke 1640 | smooth 1641 | snack 1642 | snake 1643 | snap 1644 | sniff 1645 | snow 1646 | soap 1647 | soccer 1648 | social 1649 | sock 1650 | soda 1651 | soft 1652 | solar 1653 | soldier 1654 | solid 1655 | solution 1656 | solve 1657 | someone 1658 | song 1659 | soon 1660 | sorry 1661 | sort 1662 | soul 1663 | sound 1664 | soup 1665 | source 1666 | south 1667 | space 1668 | spare 1669 | spatial 1670 | spawn 1671 | speak 1672 | special 1673 | speed 1674 | spell 1675 | spend 1676 | sphere 1677 | spice 1678 | spider 1679 | spike 1680 | spin 1681 | spirit 1682 | split 1683 | spoil 1684 | sponsor 1685 | spoon 1686 | sport 1687 | spot 1688 | spray 1689 | spread 1690 | spring 1691 | spy 1692 | square 1693 | squeeze 1694 | squirrel 1695 | stable 1696 | stadium 1697 | staff 1698 | stage 1699 | stairs 1700 | stamp 1701 | stand 1702 | start 1703 | state 1704 | stay 1705 | steak 1706 | steel 1707 | stem 1708 | step 1709 | stereo 1710 | stick 1711 | still 1712 | sting 1713 | stock 1714 | stomach 1715 | stone 1716 | stool 1717 | story 1718 | stove 1719 | strategy 1720 | street 1721 | strike 1722 | strong 1723 | struggle 1724 | student 1725 | stuff 1726 | stumble 1727 | style 1728 | subject 1729 | submit 1730 | subway 1731 | success 1732 | such 1733 | sudden 1734 | suffer 1735 | sugar 1736 | suggest 1737 | suit 1738 | summer 1739 | sun 1740 | sunny 1741 | sunset 1742 | super 1743 | supply 1744 | supreme 1745 | sure 1746 | surface 1747 | surge 1748 | surprise 1749 | surround 1750 | survey 1751 | suspect 1752 | sustain 1753 | swallow 1754 | swamp 1755 | swap 1756 | swarm 1757 | swear 1758 | sweet 1759 | swift 1760 | swim 1761 | swing 1762 | switch 1763 | sword 1764 | symbol 1765 | symptom 1766 | syrup 1767 | system 1768 | table 1769 | tackle 1770 | tag 1771 | tail 1772 | talent 1773 | talk 1774 | tank 1775 | tape 1776 | target 1777 | task 1778 | taste 1779 | tattoo 1780 | taxi 1781 | teach 1782 | team 1783 | tell 1784 | ten 1785 | tenant 1786 | tennis 1787 | tent 1788 | term 1789 | test 1790 | text 1791 | thank 1792 | that 1793 | theme 1794 | then 1795 | theory 1796 | there 1797 | they 1798 | thing 1799 | this 1800 | thought 1801 | three 1802 | thrive 1803 | throw 1804 | thumb 1805 | thunder 1806 | ticket 1807 | tide 1808 | tiger 1809 | tilt 1810 | timber 1811 | time 1812 | tiny 1813 | tip 1814 | tired 1815 | tissue 1816 | title 1817 | toast 1818 | tobacco 1819 | today 1820 | toddler 1821 | toe 1822 | together 1823 | toilet 1824 | token 1825 | tomato 1826 | tomorrow 1827 | tone 1828 | tongue 1829 | tonight 1830 | tool 1831 | tooth 1832 | top 1833 | topic 1834 | topple 1835 | torch 1836 | tornado 1837 | tortoise 1838 | toss 1839 | total 1840 | tourist 1841 | toward 1842 | tower 1843 | town 1844 | toy 1845 | track 1846 | trade 1847 | traffic 1848 | tragic 1849 | train 1850 | transfer 1851 | trap 1852 | trash 1853 | travel 1854 | tray 1855 | treat 1856 | tree 1857 | trend 1858 | trial 1859 | tribe 1860 | trick 1861 | trigger 1862 | trim 1863 | trip 1864 | trophy 1865 | trouble 1866 | truck 1867 | true 1868 | truly 1869 | trumpet 1870 | trust 1871 | truth 1872 | try 1873 | tube 1874 | tuition 1875 | tumble 1876 | tuna 1877 | tunnel 1878 | turkey 1879 | turn 1880 | turtle 1881 | twelve 1882 | twenty 1883 | twice 1884 | twin 1885 | twist 1886 | two 1887 | type 1888 | typical 1889 | ugly 1890 | umbrella 1891 | unable 1892 | unaware 1893 | uncle 1894 | uncover 1895 | under 1896 | undo 1897 | unfair 1898 | unfold 1899 | unhappy 1900 | uniform 1901 | unique 1902 | unit 1903 | universe 1904 | unknown 1905 | unlock 1906 | until 1907 | unusual 1908 | unveil 1909 | update 1910 | upgrade 1911 | uphold 1912 | upon 1913 | upper 1914 | upset 1915 | urban 1916 | urge 1917 | usage 1918 | use 1919 | used 1920 | useful 1921 | useless 1922 | usual 1923 | utility 1924 | vacant 1925 | vacuum 1926 | vague 1927 | valid 1928 | valley 1929 | valve 1930 | van 1931 | vanish 1932 | vapor 1933 | various 1934 | vast 1935 | vault 1936 | vehicle 1937 | velvet 1938 | vendor 1939 | venture 1940 | venue 1941 | verb 1942 | verify 1943 | version 1944 | very 1945 | vessel 1946 | veteran 1947 | viable 1948 | vibrant 1949 | vicious 1950 | victory 1951 | video 1952 | view 1953 | village 1954 | vintage 1955 | violin 1956 | virtual 1957 | virus 1958 | visa 1959 | visit 1960 | visual 1961 | vital 1962 | vivid 1963 | vocal 1964 | voice 1965 | void 1966 | volcano 1967 | volume 1968 | vote 1969 | voyage 1970 | wage 1971 | wagon 1972 | wait 1973 | walk 1974 | wall 1975 | walnut 1976 | want 1977 | warfare 1978 | warm 1979 | warrior 1980 | wash 1981 | wasp 1982 | waste 1983 | water 1984 | wave 1985 | way 1986 | wealth 1987 | weapon 1988 | wear 1989 | weasel 1990 | weather 1991 | web 1992 | wedding 1993 | weekend 1994 | weird 1995 | welcome 1996 | west 1997 | wet 1998 | whale 1999 | what 2000 | wheat 2001 | wheel 2002 | when 2003 | where 2004 | whip 2005 | whisper 2006 | wide 2007 | width 2008 | wife 2009 | wild 2010 | will 2011 | win 2012 | window 2013 | wine 2014 | wing 2015 | wink 2016 | winner 2017 | winter 2018 | wire 2019 | wisdom 2020 | wise 2021 | wish 2022 | witness 2023 | wolf 2024 | woman 2025 | wonder 2026 | wood 2027 | wool 2028 | word 2029 | work 2030 | world 2031 | worry 2032 | worth 2033 | wrap 2034 | wreck 2035 | wrestle 2036 | wrist 2037 | write 2038 | wrong 2039 | yard 2040 | year 2041 | yellow 2042 | you 2043 | young 2044 | youth 2045 | zebra 2046 | zero 2047 | zone 2048 | zoo 2049 | -------------------------------------------------------------------------------- /bitcoin/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from .py2specials import * 3 | from .py3specials import * 4 | import binascii 5 | import hashlib 6 | import re 7 | import sys 8 | import os 9 | import base64 10 | import time 11 | import random 12 | import hmac 13 | from bitcoin.ripemd import * 14 | 15 | # Elliptic curve parameters (secp256k1) 16 | 17 | P = 2**256 - 2**32 - 977 18 | N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 19 | A = 0 20 | B = 7 21 | Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 22 | Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 23 | G = (Gx, Gy) 24 | 25 | 26 | def change_curve(p, n, a, b, gx, gy): 27 | global P, N, A, B, Gx, Gy, G 28 | P, N, A, B, Gx, Gy = p, n, a, b, gx, gy 29 | G = (Gx, Gy) 30 | 31 | 32 | def getG(): 33 | return G 34 | 35 | # Extended Euclidean Algorithm 36 | 37 | 38 | def inv(a, n): 39 | if a == 0: 40 | return 0 41 | lm, hm = 1, 0 42 | low, high = a % n, n 43 | while low > 1: 44 | r = high//low 45 | nm, new = hm-lm*r, high-low*r 46 | lm, low, hm, high = nm, new, lm, low 47 | return lm % n 48 | 49 | 50 | 51 | # JSON access (for pybtctool convenience) 52 | 53 | 54 | def access(obj, prop): 55 | if isinstance(obj, dict): 56 | if prop in obj: 57 | return obj[prop] 58 | elif '.' in prop: 59 | return obj[float(prop)] 60 | else: 61 | return obj[int(prop)] 62 | else: 63 | return obj[int(prop)] 64 | 65 | 66 | def multiaccess(obj, prop): 67 | return [access(o, prop) for o in obj] 68 | 69 | 70 | def slice(obj, start=0, end=2**200): 71 | return obj[int(start):int(end)] 72 | 73 | 74 | def count(obj): 75 | return len(obj) 76 | 77 | _sum = sum 78 | 79 | 80 | def sum(obj): 81 | return _sum(obj) 82 | 83 | 84 | def isinf(p): 85 | return p[0] == 0 and p[1] == 0 86 | 87 | 88 | def to_jacobian(p): 89 | o = (p[0], p[1], 1) 90 | return o 91 | 92 | 93 | def jacobian_double(p): 94 | if not p[1]: 95 | return (0, 0, 0) 96 | ysq = (p[1] ** 2) % P 97 | S = (4 * p[0] * ysq) % P 98 | M = (3 * p[0] ** 2 + A * p[2] ** 4) % P 99 | nx = (M**2 - 2 * S) % P 100 | ny = (M * (S - nx) - 8 * ysq ** 2) % P 101 | nz = (2 * p[1] * p[2]) % P 102 | return (nx, ny, nz) 103 | 104 | 105 | def jacobian_add(p, q): 106 | if not p[1]: 107 | return q 108 | if not q[1]: 109 | return p 110 | U1 = (p[0] * q[2] ** 2) % P 111 | U2 = (q[0] * p[2] ** 2) % P 112 | S1 = (p[1] * q[2] ** 3) % P 113 | S2 = (q[1] * p[2] ** 3) % P 114 | if U1 == U2: 115 | if S1 != S2: 116 | return (0, 0, 1) 117 | return jacobian_double(p) 118 | H = U2 - U1 119 | R = S2 - S1 120 | H2 = (H * H) % P 121 | H3 = (H * H2) % P 122 | U1H2 = (U1 * H2) % P 123 | nx = (R ** 2 - H3 - 2 * U1H2) % P 124 | ny = (R * (U1H2 - nx) - S1 * H3) % P 125 | nz = (H * p[2] * q[2]) % P 126 | return (nx, ny, nz) 127 | 128 | 129 | def from_jacobian(p): 130 | z = inv(p[2], P) 131 | return ((p[0] * z**2) % P, (p[1] * z**3) % P) 132 | 133 | 134 | def jacobian_multiply(a, n): 135 | if a[1] == 0 or n == 0: 136 | return (0, 0, 1) 137 | if n == 1: 138 | return a 139 | if n < 0 or n >= N: 140 | return jacobian_multiply(a, n % N) 141 | if (n % 2) == 0: 142 | return jacobian_double(jacobian_multiply(a, n//2)) 143 | if (n % 2) == 1: 144 | return jacobian_add(jacobian_double(jacobian_multiply(a, n//2)), a) 145 | 146 | 147 | def fast_multiply(a, n): 148 | return from_jacobian(jacobian_multiply(to_jacobian(a), n)) 149 | 150 | 151 | def fast_add(a, b): 152 | return from_jacobian(jacobian_add(to_jacobian(a), to_jacobian(b))) 153 | 154 | # Functions for handling pubkey and privkey formats 155 | 156 | 157 | def get_pubkey_format(pub): 158 | if is_python2: 159 | two = '\x02' 160 | three = '\x03' 161 | four = '\x04' 162 | else: 163 | two = 2 164 | three = 3 165 | four = 4 166 | 167 | if isinstance(pub, (tuple, list)): return 'decimal' 168 | elif len(pub) == 65 and pub[0] == four: return 'bin' 169 | elif len(pub) == 130 and pub[0:2] == '04': return 'hex' 170 | elif len(pub) == 33 and pub[0] in [two, three]: return 'bin_compressed' 171 | elif len(pub) == 66 and pub[0:2] in ['02', '03']: return 'hex_compressed' 172 | elif len(pub) == 64: return 'bin_electrum' 173 | elif len(pub) == 128: return 'hex_electrum' 174 | else: raise Exception("Pubkey not in recognized format") 175 | 176 | 177 | def encode_pubkey(pub, formt): 178 | if not isinstance(pub, (tuple, list)): 179 | pub = decode_pubkey(pub) 180 | if formt == 'decimal': return pub 181 | elif formt == 'bin': return b'\x04' + encode(pub[0], 256, 32) + encode(pub[1], 256, 32) 182 | elif formt == 'bin_compressed': 183 | return from_int_to_byte(2+(pub[1] % 2)) + encode(pub[0], 256, 32) 184 | elif formt == 'hex': return '04' + encode(pub[0], 16, 64) + encode(pub[1], 16, 64) 185 | elif formt == 'hex_compressed': 186 | return '0'+str(2+(pub[1] % 2)) + encode(pub[0], 16, 64) 187 | elif formt == 'bin_electrum': return encode(pub[0], 256, 32) + encode(pub[1], 256, 32) 188 | elif formt == 'hex_electrum': return encode(pub[0], 16, 64) + encode(pub[1], 16, 64) 189 | else: raise Exception("Invalid format!") 190 | 191 | 192 | def decode_pubkey(pub, formt=None): 193 | if not formt: formt = get_pubkey_format(pub) 194 | if formt == 'decimal': return pub 195 | elif formt == 'bin': return (decode(pub[1:33], 256), decode(pub[33:65], 256)) 196 | elif formt == 'bin_compressed': 197 | x = decode(pub[1:33], 256) 198 | beta = pow(int(x*x*x+A*x+B), int((P+1)//4), int(P)) 199 | y = (P-beta) if ((beta + from_byte_to_int(pub[0])) % 2) else beta 200 | return (x, y) 201 | elif formt == 'hex': return (decode(pub[2:66], 16), decode(pub[66:130], 16)) 202 | elif formt == 'hex_compressed': 203 | return decode_pubkey(safe_from_hex(pub), 'bin_compressed') 204 | elif formt == 'bin_electrum': 205 | return (decode(pub[:32], 256), decode(pub[32:64], 256)) 206 | elif formt == 'hex_electrum': 207 | return (decode(pub[:64], 16), decode(pub[64:128], 16)) 208 | else: raise Exception("Invalid format!") 209 | 210 | def get_privkey_format(priv): 211 | if isinstance(priv, int_types): return 'decimal' 212 | elif len(priv) == 32: return 'bin' 213 | elif len(priv) == 33: return 'bin_compressed' 214 | elif len(priv) == 64: return 'hex' 215 | elif len(priv) == 66: return 'hex_compressed' 216 | else: 217 | bin_p = b58check_to_bin(priv) 218 | if len(bin_p) == 32: return 'wif' 219 | elif len(bin_p) == 33: return 'wif_compressed' 220 | else: raise Exception("WIF does not represent privkey") 221 | 222 | def encode_privkey(priv, formt, vbyte=0): 223 | if not isinstance(priv, int_types): 224 | return encode_privkey(decode_privkey(priv), formt, vbyte) 225 | if formt == 'decimal': return priv 226 | elif formt == 'bin': return encode(priv, 256, 32) 227 | elif formt == 'bin_compressed': return encode(priv, 256, 32)+b'\x01' 228 | elif formt == 'hex': return encode(priv, 16, 64) 229 | elif formt == 'hex_compressed': return encode(priv, 16, 64)+'01' 230 | elif formt == 'wif': 231 | return bin_to_b58check(encode(priv, 256, 32), 128+int(vbyte)) 232 | elif formt == 'wif_compressed': 233 | return bin_to_b58check(encode(priv, 256, 32)+b'\x01', 128+int(vbyte)) 234 | else: raise Exception("Invalid format!") 235 | 236 | def decode_privkey(priv,formt=None): 237 | if not formt: formt = get_privkey_format(priv) 238 | if formt == 'decimal': return priv 239 | elif formt == 'bin': return decode(priv, 256) 240 | elif formt == 'bin_compressed': return decode(priv[:32], 256) 241 | elif formt == 'hex': return decode(priv, 16) 242 | elif formt == 'hex_compressed': return decode(priv[:64], 16) 243 | elif formt == 'wif': return decode(b58check_to_bin(priv),256) 244 | elif formt == 'wif_compressed': 245 | return decode(b58check_to_bin(priv)[:32],256) 246 | else: raise Exception("WIF does not represent privkey") 247 | 248 | def add_pubkeys(p1, p2): 249 | f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) 250 | return encode_pubkey(fast_add(decode_pubkey(p1, f1), decode_pubkey(p2, f2)), f1) 251 | 252 | def add_privkeys(p1, p2): 253 | f1, f2 = get_privkey_format(p1), get_privkey_format(p2) 254 | return encode_privkey((decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1) 255 | 256 | def mul_privkeys(p1, p2): 257 | f1, f2 = get_privkey_format(p1), get_privkey_format(p2) 258 | return encode_privkey((decode_privkey(p1, f1) * decode_privkey(p2, f2)) % N, f1) 259 | 260 | def multiply(pubkey, privkey): 261 | f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey) 262 | pubkey, privkey = decode_pubkey(pubkey, f1), decode_privkey(privkey, f2) 263 | # http://safecurves.cr.yp.to/twist.html 264 | if not isinf(pubkey) and (pubkey[0]**3+B-pubkey[1]*pubkey[1]) % P != 0: 265 | raise Exception("Point not on curve") 266 | return encode_pubkey(fast_multiply(pubkey, privkey), f1) 267 | 268 | 269 | def divide(pubkey, privkey): 270 | factor = inv(decode_privkey(privkey), N) 271 | return multiply(pubkey, factor) 272 | 273 | 274 | def compress(pubkey): 275 | f = get_pubkey_format(pubkey) 276 | if 'compressed' in f: return pubkey 277 | elif f == 'bin': return encode_pubkey(decode_pubkey(pubkey, f), 'bin_compressed') 278 | elif f == 'hex' or f == 'decimal': 279 | return encode_pubkey(decode_pubkey(pubkey, f), 'hex_compressed') 280 | 281 | 282 | def decompress(pubkey): 283 | f = get_pubkey_format(pubkey) 284 | if 'compressed' not in f: return pubkey 285 | elif f == 'bin_compressed': return encode_pubkey(decode_pubkey(pubkey, f), 'bin') 286 | elif f == 'hex_compressed' or f == 'decimal': 287 | return encode_pubkey(decode_pubkey(pubkey, f), 'hex') 288 | 289 | 290 | def privkey_to_pubkey(privkey): 291 | f = get_privkey_format(privkey) 292 | privkey = decode_privkey(privkey, f) 293 | if privkey >= N: 294 | raise Exception("Invalid privkey") 295 | if f in ['bin', 'bin_compressed', 'hex', 'hex_compressed', 'decimal']: 296 | return encode_pubkey(fast_multiply(G, privkey), f) 297 | else: 298 | return encode_pubkey(fast_multiply(G, privkey), f.replace('wif', 'hex')) 299 | 300 | privtopub = privkey_to_pubkey 301 | 302 | 303 | def privkey_to_address(priv, magicbyte=0): 304 | return pubkey_to_address(privkey_to_pubkey(priv), magicbyte) 305 | privtoaddr = privkey_to_address 306 | 307 | 308 | def neg_pubkey(pubkey): 309 | f = get_pubkey_format(pubkey) 310 | pubkey = decode_pubkey(pubkey, f) 311 | return encode_pubkey((pubkey[0], (P-pubkey[1]) % P), f) 312 | 313 | 314 | def neg_privkey(privkey): 315 | f = get_privkey_format(privkey) 316 | privkey = decode_privkey(privkey, f) 317 | return encode_privkey((N - privkey) % N, f) 318 | 319 | def subtract_pubkeys(p1, p2): 320 | f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) 321 | k2 = decode_pubkey(p2, f2) 322 | return encode_pubkey(fast_add(decode_pubkey(p1, f1), (k2[0], (P - k2[1]) % P)), f1) 323 | 324 | 325 | def subtract_privkeys(p1, p2): 326 | f1, f2 = get_privkey_format(p1), get_privkey_format(p2) 327 | k2 = decode_privkey(p2, f2) 328 | return encode_privkey((decode_privkey(p1, f1) - k2) % N, f1) 329 | 330 | # Hashes 331 | 332 | 333 | def bin_hash160(string): 334 | intermed = hashlib.sha256(string).digest() 335 | digest = '' 336 | try: 337 | digest = hashlib.new('ripemd160', intermed).digest() 338 | except: 339 | digest = RIPEMD160(intermed).digest() 340 | return digest 341 | 342 | 343 | def hash160(string): 344 | return safe_hexlify(bin_hash160(string)) 345 | 346 | 347 | def bin_sha256(string): 348 | binary_data = string if isinstance(string, bytes) else bytes(string, 'utf-8') 349 | return hashlib.sha256(binary_data).digest() 350 | 351 | def sha256(string): 352 | return bytes_to_hex_string(bin_sha256(string)) 353 | 354 | 355 | def bin_ripemd160(string): 356 | try: 357 | digest = hashlib.new('ripemd160', string).digest() 358 | except: 359 | digest = RIPEMD160(string).digest() 360 | return digest 361 | 362 | 363 | def ripemd160(string): 364 | return safe_hexlify(bin_ripemd160(string)) 365 | 366 | 367 | def bin_dbl_sha256(s): 368 | bytes_to_hash = from_string_to_bytes(s) 369 | return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() 370 | 371 | 372 | def dbl_sha256(string): 373 | return safe_hexlify(bin_dbl_sha256(string)) 374 | 375 | 376 | def bin_slowsha(string): 377 | string = from_string_to_bytes(string) 378 | orig_input = string 379 | for i in range(100000): 380 | string = hashlib.sha256(string + orig_input).digest() 381 | return string 382 | 383 | 384 | def slowsha(string): 385 | return safe_hexlify(bin_slowsha(string)) 386 | 387 | 388 | def hash_to_int(x): 389 | if len(x) in [40, 64]: 390 | return decode(x, 16) 391 | return decode(x, 256) 392 | 393 | 394 | def num_to_var_int(x): 395 | x = int(x) 396 | if x < 253: return from_int_to_byte(x) 397 | elif x < 65536: return from_int_to_byte(253)+encode(x, 256, 2)[::-1] 398 | elif x < 4294967296: return from_int_to_byte(254) + encode(x, 256, 4)[::-1] 399 | else: return from_int_to_byte(255) + encode(x, 256, 8)[::-1] 400 | 401 | 402 | # WTF, Electrum? 403 | def electrum_sig_hash(message): 404 | padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(message)) + from_string_to_bytes(message) 405 | return bin_dbl_sha256(padded) 406 | 407 | 408 | def random_key(): 409 | # Gotta be secure after that java.SecureRandom fiasco... 410 | entropy = random_string(32) \ 411 | + str(random.randrange(2**256)) \ 412 | + str(int(time.time() * 1000000)) 413 | return sha256(entropy) 414 | 415 | 416 | def random_electrum_seed(): 417 | entropy = os.urandom(32) \ 418 | + str(random.randrange(2**256)) \ 419 | + str(int(time.time() * 1000000)) 420 | return sha256(entropy)[:32] 421 | 422 | # Encodings 423 | 424 | def b58check_to_bin(inp): 425 | leadingzbytes = len(re.match('^1*', inp).group(0)) 426 | data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) 427 | assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] 428 | return data[1:-4] 429 | 430 | 431 | def get_version_byte(inp): 432 | leadingzbytes = len(re.match('^1*', inp).group(0)) 433 | data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) 434 | assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] 435 | return ord(data[0]) 436 | 437 | 438 | def hex_to_b58check(inp, magicbyte=0): 439 | return bin_to_b58check(binascii.unhexlify(inp), magicbyte) 440 | 441 | 442 | def b58check_to_hex(inp): 443 | return safe_hexlify(b58check_to_bin(inp)) 444 | 445 | 446 | def pubkey_to_address(pubkey, magicbyte=0): 447 | if isinstance(pubkey, (list, tuple)): 448 | pubkey = encode_pubkey(pubkey, 'bin') 449 | if len(pubkey) in [66, 130]: 450 | return bin_to_b58check( 451 | bin_hash160(binascii.unhexlify(pubkey)), magicbyte) 452 | return bin_to_b58check(bin_hash160(pubkey), magicbyte) 453 | 454 | pubtoaddr = pubkey_to_address 455 | 456 | 457 | def is_privkey(priv): 458 | try: 459 | get_privkey_format(priv) 460 | return True 461 | except: 462 | return False 463 | 464 | def is_pubkey(pubkey): 465 | try: 466 | get_pubkey_format(pubkey) 467 | return True 468 | except: 469 | return False 470 | 471 | def is_address(addr): 472 | ADDR_RE = re.compile("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$") 473 | return bool(ADDR_RE.match(addr)) 474 | 475 | 476 | # EDCSA 477 | 478 | 479 | def encode_sig(v, r, s): 480 | vb, rb, sb = from_int_to_byte(v), encode(r, 256), encode(s, 256) 481 | 482 | result = base64.b64encode(vb+b'\x00'*(32-len(rb))+rb+b'\x00'*(32-len(sb))+sb) 483 | return result if is_python2 else str(result, 'utf-8') 484 | 485 | 486 | def decode_sig(sig): 487 | bytez = base64.b64decode(sig) 488 | return from_byte_to_int(bytez[0]), decode(bytez[1:33], 256), decode(bytez[33:], 256) 489 | 490 | # https://tools.ietf.org/html/rfc6979#section-3.2 491 | 492 | 493 | def deterministic_generate_k(msghash, priv): 494 | v = b'\x01' * 32 495 | k = b'\x00' * 32 496 | priv = encode_privkey(priv, 'bin') 497 | msghash = encode(hash_to_int(msghash), 256, 32) 498 | k = hmac.new(k, v+b'\x00'+priv+msghash, hashlib.sha256).digest() 499 | v = hmac.new(k, v, hashlib.sha256).digest() 500 | k = hmac.new(k, v+b'\x01'+priv+msghash, hashlib.sha256).digest() 501 | v = hmac.new(k, v, hashlib.sha256).digest() 502 | return decode(hmac.new(k, v, hashlib.sha256).digest(), 256) 503 | 504 | 505 | def ecdsa_raw_sign(msghash, priv): 506 | 507 | z = hash_to_int(msghash) 508 | k = deterministic_generate_k(msghash, priv) 509 | 510 | r, y = fast_multiply(G, k) 511 | s = inv(k, N) * (z + r*decode_privkey(priv)) % N 512 | 513 | v, r, s = 27+((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s 514 | if 'compressed' in get_privkey_format(priv): 515 | v += 4 516 | return v, r, s 517 | 518 | 519 | def ecdsa_sign(msg, priv): 520 | v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) 521 | sig = encode_sig(v, r, s) 522 | assert ecdsa_verify(msg, sig, 523 | privtopub(priv)), "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) 524 | return sig 525 | 526 | 527 | def ecdsa_raw_verify(msghash, vrs, pub): 528 | v, r, s = vrs 529 | if not (27 <= v <= 34): 530 | return False 531 | 532 | w = inv(s, N) 533 | z = hash_to_int(msghash) 534 | 535 | u1, u2 = z*w % N, r*w % N 536 | x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) 537 | return bool(r == x and (r % N) and (s % N)) 538 | 539 | 540 | # For BitcoinCore, (msg = addr or msg = "") be default 541 | def ecdsa_verify_addr(msg, sig, addr): 542 | assert is_address(addr) 543 | Q = ecdsa_recover(msg, sig) 544 | magic = get_version_byte(addr) 545 | return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) 546 | 547 | 548 | def ecdsa_verify(msg, sig, pub): 549 | if is_address(pub): 550 | return ecdsa_verify_addr(msg, sig, pub) 551 | return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) 552 | 553 | 554 | def ecdsa_raw_recover(msghash, vrs): 555 | v, r, s = vrs 556 | if not (27 <= v <= 34): 557 | raise ValueError("%d must in range 27-31" % v) 558 | x = r 559 | xcubedaxb = (x*x*x+A*x+B) % P 560 | beta = pow(xcubedaxb, (P+1)//4, P) 561 | y = beta if v % 2 ^ beta % 2 else (P - beta) 562 | # If xcubedaxb is not a quadratic residue, then r cannot be the x coord 563 | # for a point on the curve, and so the sig is invalid 564 | if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): 565 | return False 566 | z = hash_to_int(msghash) 567 | Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) 568 | XY = jacobian_multiply((x, y, 1), s) 569 | Qr = jacobian_add(Gz, XY) 570 | Q = jacobian_multiply(Qr, inv(r, N)) 571 | Q = from_jacobian(Q) 572 | 573 | # if ecdsa_raw_verify(msghash, vrs, Q): 574 | return Q 575 | # return False 576 | 577 | 578 | def ecdsa_recover(msg, sig): 579 | v,r,s = decode_sig(sig) 580 | Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) 581 | return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') 582 | -------------------------------------------------------------------------------- /bitcoin/mnemonic.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os.path 3 | import binascii 4 | import random 5 | from bisect import bisect_left 6 | 7 | wordlist_english=list(open(os.path.join(os.path.dirname(os.path.realpath(__file__)),'english.txt'),'r')) 8 | 9 | def eint_to_bytes(entint,entbits): 10 | a=hex(entint)[2:].rstrip('L').zfill(32) 11 | print(a) 12 | return binascii.unhexlify(a) 13 | 14 | def mnemonic_int_to_words(mint,mint_num_words,wordlist=wordlist_english): 15 | backwords=[wordlist[(mint >> (11*x)) & 0x7FF].strip() for x in range(mint_num_words)] 16 | return backwords[::-1] 17 | 18 | def entropy_cs(entbytes): 19 | entropy_size=8*len(entbytes) 20 | checksum_size=entropy_size//32 21 | hd=hashlib.sha256(entbytes).hexdigest() 22 | csint=int(hd,16) >> (256-checksum_size) 23 | return csint,checksum_size 24 | 25 | def entropy_to_words(entbytes,wordlist=wordlist_english): 26 | if(len(entbytes) < 4 or len(entbytes) % 4 != 0): 27 | raise ValueError("The size of the entropy must be a multiple of 4 bytes (multiple of 32 bits)") 28 | entropy_size=8*len(entbytes) 29 | csint,checksum_size = entropy_cs(entbytes) 30 | entint=int(binascii.hexlify(entbytes),16) 31 | mint=(entint << checksum_size) | csint 32 | mint_num_words=(entropy_size+checksum_size)//11 33 | 34 | return mnemonic_int_to_words(mint,mint_num_words,wordlist) 35 | 36 | def words_bisect(word,wordlist=wordlist_english): 37 | lo=bisect_left(wordlist,word) 38 | hi=len(wordlist)-bisect_left(wordlist[:lo:-1],word) 39 | 40 | return lo,hi 41 | 42 | def words_split(wordstr,wordlist=wordlist_english): 43 | def popword(wordstr,wordlist): 44 | for fwl in range(1,9): 45 | w=wordstr[:fwl].strip() 46 | lo,hi=words_bisect(w,wordlist) 47 | if(hi-lo == 1): 48 | return w,wordstr[fwl:].lstrip() 49 | wordlist=wordlist[lo:hi] 50 | raise Exception("Wordstr %s not found in list" %(w)) 51 | 52 | words=[] 53 | tail=wordstr 54 | while(len(tail)): 55 | head,tail=popword(tail,wordlist) 56 | words.append(head) 57 | return words 58 | 59 | def words_to_mnemonic_int(words,wordlist=wordlist_english): 60 | if(isinstance(words,str)): 61 | words=words_split(words,wordlist) 62 | return sum([wordlist.index(w) << (11*x) for x,w in enumerate(words[::-1])]) 63 | 64 | def words_verify(words,wordlist=wordlist_english): 65 | if(isinstance(words,str)): 66 | words=words_split(words,wordlist) 67 | 68 | mint = words_to_mnemonic_int(words,wordlist) 69 | mint_bits=len(words)*11 70 | cs_bits=mint_bits//32 71 | entropy_bits=mint_bits-cs_bits 72 | eint=mint >> cs_bits 73 | csint=mint & ((1 << cs_bits)-1) 74 | ebytes=_eint_to_bytes(eint,entropy_bits) 75 | return csint == entropy_cs(ebytes) 76 | 77 | def mnemonic_to_seed(mnemonic_phrase,passphrase=b''): 78 | try: 79 | from hashlib import pbkdf2_hmac 80 | def pbkdf2_hmac_sha256(password,salt,iters=2048): 81 | return pbkdf2_hmac(hash_name='sha512',password=password,salt=salt,iterations=iters) 82 | except: 83 | try: 84 | from Crypto.Protocol.KDF import PBKDF2 85 | from Crypto.Hash import SHA512,HMAC 86 | 87 | def pbkdf2_hmac_sha256(password,salt,iters=2048): 88 | return PBKDF2(password=password,salt=salt,dkLen=64,count=iters,prf=lambda p,s: HMAC.new(p,s,SHA512).digest()) 89 | except: 90 | try: 91 | 92 | from pbkdf2 import PBKDF2 93 | import hmac 94 | def pbkdf2_hmac_sha256(password,salt,iters=2048): 95 | return PBKDF2(password,salt, iterations=iters, macmodule=hmac, digestmodule=hashlib.sha512).read(64) 96 | except: 97 | raise RuntimeError("No implementation of pbkdf2 was found!") 98 | 99 | return pbkdf2_hmac_sha256(password=mnemonic_phrase,salt=b'mnemonic'+passphrase) 100 | 101 | def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits=random.getrandbits): 102 | prefix_bits=len(prefix)*11 103 | mine_bits=entbits-prefix_bits 104 | pint=words_to_mnemonic_int(prefix,wordlist) 105 | pint<<=mine_bits 106 | dint=randombits(mine_bits) 107 | count=0 108 | while(not satisfunction(entropy_to_words(eint_to_bytes(pint+dint,entbits)))): 109 | dint=randombits(mine_bits) 110 | if((count & 0xFFFF) == 0): 111 | print("Searched %f percent of the space" % (float(count)/float(1 << mine_bits))) 112 | 113 | return entropy_to_words(eint_to_bytes(pint+dint,entbits)) 114 | 115 | if __name__=="__main__": 116 | import json 117 | testvectors=json.load(open('vectors.json','r')) 118 | passed=True 119 | for v in testvectors['english']: 120 | ebytes=binascii.unhexlify(v[0]) 121 | w=' '.join(entropy_to_words(ebytes)) 122 | seed=mnemonic_to_seed(w,passphrase='TREZOR') 123 | passed = passed and w==v[1] 124 | passed = passed and binascii.hexlify(seed)==v[2] 125 | print("Tests %s." % ("Passed" if passed else "Failed")) 126 | 127 | 128 | -------------------------------------------------------------------------------- /bitcoin/py2specials.py: -------------------------------------------------------------------------------- 1 | import sys, re 2 | import binascii 3 | import os 4 | import hashlib 5 | 6 | 7 | if sys.version_info.major == 2: 8 | string_types = (str, unicode) 9 | string_or_bytes_types = string_types 10 | int_types = (int, float, long) 11 | 12 | # Base switching 13 | code_strings = { 14 | 2: '01', 15 | 10: '0123456789', 16 | 16: '0123456789abcdef', 17 | 32: 'abcdefghijklmnopqrstuvwxyz234567', 18 | 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', 19 | 256: ''.join([chr(x) for x in range(256)]) 20 | } 21 | 22 | def bin_dbl_sha256(s): 23 | bytes_to_hash = from_string_to_bytes(s) 24 | return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() 25 | 26 | def lpad(msg, symbol, length): 27 | if len(msg) >= length: 28 | return msg 29 | return symbol * (length - len(msg)) + msg 30 | 31 | def get_code_string(base): 32 | if base in code_strings: 33 | return code_strings[base] 34 | else: 35 | raise ValueError("Invalid base!") 36 | 37 | def changebase(string, frm, to, minlen=0): 38 | if frm == to: 39 | return lpad(string, get_code_string(frm)[0], minlen) 40 | return encode(decode(string, frm), to, minlen) 41 | 42 | def bin_to_b58check(inp, magicbyte=0): 43 | if magicbyte == 0: 44 | inp = '\x00' + inp 45 | while magicbyte > 0: 46 | inp = chr(int(magicbyte % 256)) + inp 47 | magicbyte //= 256 48 | leadingzbytes = len(re.match('^\x00*', inp).group(0)) 49 | checksum = bin_dbl_sha256(inp)[:4] 50 | return '1' * leadingzbytes + changebase(inp+checksum, 256, 58) 51 | 52 | def bytes_to_hex_string(b): 53 | return b.encode('hex') 54 | 55 | def safe_from_hex(s): 56 | return s.decode('hex') 57 | 58 | def from_int_representation_to_bytes(a): 59 | return str(a) 60 | 61 | def from_int_to_byte(a): 62 | return chr(a) 63 | 64 | def from_byte_to_int(a): 65 | return ord(a) 66 | 67 | def from_bytes_to_string(s): 68 | return s 69 | 70 | def from_string_to_bytes(a): 71 | return a 72 | 73 | def safe_hexlify(a): 74 | return binascii.hexlify(a) 75 | 76 | def encode(val, base, minlen=0): 77 | base, minlen = int(base), int(minlen) 78 | code_string = get_code_string(base) 79 | result = "" 80 | while val > 0: 81 | result = code_string[val % base] + result 82 | val //= base 83 | return code_string[0] * max(minlen - len(result), 0) + result 84 | 85 | def decode(string, base): 86 | base = int(base) 87 | code_string = get_code_string(base) 88 | result = 0 89 | if base == 16: 90 | string = string.lower() 91 | while len(string) > 0: 92 | result *= base 93 | result += code_string.find(string[0]) 94 | string = string[1:] 95 | return result 96 | 97 | def random_string(x): 98 | return os.urandom(x) 99 | -------------------------------------------------------------------------------- /bitcoin/py3specials.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import binascii 3 | import hashlib 4 | 5 | 6 | if sys.version_info.major == 3: 7 | string_types = (str) 8 | string_or_bytes_types = (str, bytes) 9 | int_types = (int, float) 10 | # Base switching 11 | code_strings = { 12 | 2: '01', 13 | 10: '0123456789', 14 | 16: '0123456789abcdef', 15 | 32: 'abcdefghijklmnopqrstuvwxyz234567', 16 | 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', 17 | 256: ''.join([chr(x) for x in range(256)]) 18 | } 19 | 20 | def bin_dbl_sha256(s): 21 | bytes_to_hash = from_string_to_bytes(s) 22 | return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() 23 | 24 | def lpad(msg, symbol, length): 25 | if len(msg) >= length: 26 | return msg 27 | return symbol * (length - len(msg)) + msg 28 | 29 | def get_code_string(base): 30 | if base in code_strings: 31 | return code_strings[base] 32 | else: 33 | raise ValueError("Invalid base!") 34 | 35 | def changebase(string, frm, to, minlen=0): 36 | if frm == to: 37 | return lpad(string, get_code_string(frm)[0], minlen) 38 | return encode(decode(string, frm), to, minlen) 39 | 40 | def bin_to_b58check(inp, magicbyte=0): 41 | if magicbyte == 0: 42 | inp = from_int_to_byte(0) + inp 43 | while magicbyte > 0: 44 | inp = from_int_to_byte(magicbyte % 256) + inp 45 | magicbyte //= 256 46 | 47 | leadingzbytes = 0 48 | for x in inp: 49 | if x != 0: 50 | break 51 | leadingzbytes += 1 52 | 53 | checksum = bin_dbl_sha256(inp)[:4] 54 | return '1' * leadingzbytes + changebase(inp+checksum, 256, 58) 55 | 56 | def bytes_to_hex_string(b): 57 | if isinstance(b, str): 58 | return b 59 | 60 | return ''.join('{:02x}'.format(y) for y in b) 61 | 62 | def safe_from_hex(s): 63 | return bytes.fromhex(s) 64 | 65 | def from_int_representation_to_bytes(a): 66 | return bytes(str(a), 'utf-8') 67 | 68 | def from_int_to_byte(a): 69 | return bytes([a]) 70 | 71 | def from_byte_to_int(a): 72 | return a 73 | 74 | def from_string_to_bytes(a): 75 | return a if isinstance(a, bytes) else bytes(a, 'utf-8') 76 | 77 | def safe_hexlify(a): 78 | return str(binascii.hexlify(a), 'utf-8') 79 | 80 | def encode(val, base, minlen=0): 81 | base, minlen = int(base), int(minlen) 82 | code_string = get_code_string(base) 83 | result_bytes = bytes() 84 | while val > 0: 85 | curcode = code_string[val % base] 86 | result_bytes = bytes([ord(curcode)]) + result_bytes 87 | val //= base 88 | 89 | pad_size = minlen - len(result_bytes) 90 | 91 | padding_element = b'\x00' if base == 256 else b'1' \ 92 | if base == 58 else b'0' 93 | if (pad_size > 0): 94 | result_bytes = padding_element*pad_size + result_bytes 95 | 96 | result_string = ''.join([chr(y) for y in result_bytes]) 97 | result = result_bytes if base == 256 else result_string 98 | 99 | return result 100 | 101 | def decode(string, base): 102 | if base == 256 and isinstance(string, str): 103 | string = bytes(bytearray.fromhex(string)) 104 | base = int(base) 105 | code_string = get_code_string(base) 106 | result = 0 107 | if base == 256: 108 | def extract(d, cs): 109 | return d 110 | else: 111 | def extract(d, cs): 112 | return cs.find(d if isinstance(d, str) else chr(d)) 113 | 114 | if base == 16: 115 | string = string.lower() 116 | while len(string) > 0: 117 | result *= base 118 | result += extract(string[0], code_string) 119 | string = string[1:] 120 | return result 121 | 122 | def random_string(x): 123 | return str(os.urandom(x)) 124 | -------------------------------------------------------------------------------- /bitcoin/ripemd.py: -------------------------------------------------------------------------------- 1 | ## ripemd.py - pure Python implementation of the RIPEMD-160 algorithm. 2 | ## Bjorn Edstrom 16 december 2007. 3 | ## 4 | ## Copyrights 5 | ## ========== 6 | ## 7 | ## This code is a derived from an implementation by Markus Friedl which is 8 | ## subject to the following license. This Python implementation is not 9 | ## subject to any other license. 10 | ## 11 | ##/* 12 | ## * Copyright (c) 2001 Markus Friedl. All rights reserved. 13 | ## * 14 | ## * Redistribution and use in source and binary forms, with or without 15 | ## * modification, are permitted provided that the following conditions 16 | ## * are met: 17 | ## * 1. Redistributions of source code must retain the above copyright 18 | ## * notice, this list of conditions and the following disclaimer. 19 | ## * 2. Redistributions in binary form must reproduce the above copyright 20 | ## * notice, this list of conditions and the following disclaimer in the 21 | ## * documentation and/or other materials provided with the distribution. 22 | ## * 23 | ## * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 24 | ## * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | ## * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | ## * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | ## * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | ## * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | ## * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | ## * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | ## * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 | ## * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | ## */ 34 | ##/* 35 | ## * Preneel, Bosselaers, Dobbertin, "The Cryptographic Hash Function RIPEMD-160", 36 | ## * RSA Laboratories, CryptoBytes, Volume 3, Number 2, Autumn 1997, 37 | ## * ftp://ftp.rsasecurity.com/pub/cryptobytes/crypto3n2.pdf 38 | ## */ 39 | 40 | try: 41 | import psyco 42 | psyco.full() 43 | except ImportError: 44 | pass 45 | 46 | import sys 47 | 48 | is_python2 = sys.version_info.major == 2 49 | #block_size = 1 50 | digest_size = 20 51 | digestsize = 20 52 | 53 | try: 54 | range = xrange 55 | except: 56 | pass 57 | 58 | class RIPEMD160: 59 | """Return a new RIPEMD160 object. An optional string argument 60 | may be provided; if present, this string will be automatically 61 | hashed.""" 62 | 63 | def __init__(self, arg=None): 64 | self.ctx = RMDContext() 65 | if arg: 66 | self.update(arg) 67 | self.dig = None 68 | 69 | def update(self, arg): 70 | """update(arg)""" 71 | RMD160Update(self.ctx, arg, len(arg)) 72 | self.dig = None 73 | 74 | def digest(self): 75 | """digest()""" 76 | if self.dig: 77 | return self.dig 78 | ctx = self.ctx.copy() 79 | self.dig = RMD160Final(self.ctx) 80 | self.ctx = ctx 81 | return self.dig 82 | 83 | def hexdigest(self): 84 | """hexdigest()""" 85 | dig = self.digest() 86 | hex_digest = '' 87 | for d in dig: 88 | if (is_python2): 89 | hex_digest += '%02x' % ord(d) 90 | else: 91 | hex_digest += '%02x' % d 92 | return hex_digest 93 | 94 | def copy(self): 95 | """copy()""" 96 | import copy 97 | return copy.deepcopy(self) 98 | 99 | 100 | 101 | def new(arg=None): 102 | """Return a new RIPEMD160 object. An optional string argument 103 | may be provided; if present, this string will be automatically 104 | hashed.""" 105 | return RIPEMD160(arg) 106 | 107 | 108 | 109 | # 110 | # Private. 111 | # 112 | 113 | class RMDContext: 114 | def __init__(self): 115 | self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 116 | 0x10325476, 0xC3D2E1F0] # uint32 117 | self.count = 0 # uint64 118 | self.buffer = [0]*64 # uchar 119 | def copy(self): 120 | ctx = RMDContext() 121 | ctx.state = self.state[:] 122 | ctx.count = self.count 123 | ctx.buffer = self.buffer[:] 124 | return ctx 125 | 126 | K0 = 0x00000000 127 | K1 = 0x5A827999 128 | K2 = 0x6ED9EBA1 129 | K3 = 0x8F1BBCDC 130 | K4 = 0xA953FD4E 131 | 132 | KK0 = 0x50A28BE6 133 | KK1 = 0x5C4DD124 134 | KK2 = 0x6D703EF3 135 | KK3 = 0x7A6D76E9 136 | KK4 = 0x00000000 137 | 138 | def ROL(n, x): 139 | return ((x << n) & 0xffffffff) | (x >> (32 - n)) 140 | 141 | def F0(x, y, z): 142 | return x ^ y ^ z 143 | 144 | def F1(x, y, z): 145 | return (x & y) | (((~x) % 0x100000000) & z) 146 | 147 | def F2(x, y, z): 148 | return (x | ((~y) % 0x100000000)) ^ z 149 | 150 | def F3(x, y, z): 151 | return (x & z) | (((~z) % 0x100000000) & y) 152 | 153 | def F4(x, y, z): 154 | return x ^ (y | ((~z) % 0x100000000)) 155 | 156 | def R(a, b, c, d, e, Fj, Kj, sj, rj, X): 157 | a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e 158 | c = ROL(10, c) 159 | return a % 0x100000000, c 160 | 161 | PADDING = [0x80] + [0]*63 162 | 163 | import sys 164 | import struct 165 | 166 | def RMD160Transform(state, block): #uint32 state[5], uchar block[64] 167 | x = [0]*16 168 | if sys.byteorder == 'little': 169 | if is_python2: 170 | x = struct.unpack('<16L', ''.join([chr(x) for x in block[0:64]])) 171 | else: 172 | x = struct.unpack('<16L', bytes(block[0:64])) 173 | else: 174 | raise "Error!!" 175 | a = state[0] 176 | b = state[1] 177 | c = state[2] 178 | d = state[3] 179 | e = state[4] 180 | 181 | #/* Round 1 */ 182 | a, c = R(a, b, c, d, e, F0, K0, 11, 0, x); 183 | e, b = R(e, a, b, c, d, F0, K0, 14, 1, x); 184 | d, a = R(d, e, a, b, c, F0, K0, 15, 2, x); 185 | c, e = R(c, d, e, a, b, F0, K0, 12, 3, x); 186 | b, d = R(b, c, d, e, a, F0, K0, 5, 4, x); 187 | a, c = R(a, b, c, d, e, F0, K0, 8, 5, x); 188 | e, b = R(e, a, b, c, d, F0, K0, 7, 6, x); 189 | d, a = R(d, e, a, b, c, F0, K0, 9, 7, x); 190 | c, e = R(c, d, e, a, b, F0, K0, 11, 8, x); 191 | b, d = R(b, c, d, e, a, F0, K0, 13, 9, x); 192 | a, c = R(a, b, c, d, e, F0, K0, 14, 10, x); 193 | e, b = R(e, a, b, c, d, F0, K0, 15, 11, x); 194 | d, a = R(d, e, a, b, c, F0, K0, 6, 12, x); 195 | c, e = R(c, d, e, a, b, F0, K0, 7, 13, x); 196 | b, d = R(b, c, d, e, a, F0, K0, 9, 14, x); 197 | a, c = R(a, b, c, d, e, F0, K0, 8, 15, x); #/* #15 */ 198 | #/* Round 2 */ 199 | e, b = R(e, a, b, c, d, F1, K1, 7, 7, x); 200 | d, a = R(d, e, a, b, c, F1, K1, 6, 4, x); 201 | c, e = R(c, d, e, a, b, F1, K1, 8, 13, x); 202 | b, d = R(b, c, d, e, a, F1, K1, 13, 1, x); 203 | a, c = R(a, b, c, d, e, F1, K1, 11, 10, x); 204 | e, b = R(e, a, b, c, d, F1, K1, 9, 6, x); 205 | d, a = R(d, e, a, b, c, F1, K1, 7, 15, x); 206 | c, e = R(c, d, e, a, b, F1, K1, 15, 3, x); 207 | b, d = R(b, c, d, e, a, F1, K1, 7, 12, x); 208 | a, c = R(a, b, c, d, e, F1, K1, 12, 0, x); 209 | e, b = R(e, a, b, c, d, F1, K1, 15, 9, x); 210 | d, a = R(d, e, a, b, c, F1, K1, 9, 5, x); 211 | c, e = R(c, d, e, a, b, F1, K1, 11, 2, x); 212 | b, d = R(b, c, d, e, a, F1, K1, 7, 14, x); 213 | a, c = R(a, b, c, d, e, F1, K1, 13, 11, x); 214 | e, b = R(e, a, b, c, d, F1, K1, 12, 8, x); #/* #31 */ 215 | #/* Round 3 */ 216 | d, a = R(d, e, a, b, c, F2, K2, 11, 3, x); 217 | c, e = R(c, d, e, a, b, F2, K2, 13, 10, x); 218 | b, d = R(b, c, d, e, a, F2, K2, 6, 14, x); 219 | a, c = R(a, b, c, d, e, F2, K2, 7, 4, x); 220 | e, b = R(e, a, b, c, d, F2, K2, 14, 9, x); 221 | d, a = R(d, e, a, b, c, F2, K2, 9, 15, x); 222 | c, e = R(c, d, e, a, b, F2, K2, 13, 8, x); 223 | b, d = R(b, c, d, e, a, F2, K2, 15, 1, x); 224 | a, c = R(a, b, c, d, e, F2, K2, 14, 2, x); 225 | e, b = R(e, a, b, c, d, F2, K2, 8, 7, x); 226 | d, a = R(d, e, a, b, c, F2, K2, 13, 0, x); 227 | c, e = R(c, d, e, a, b, F2, K2, 6, 6, x); 228 | b, d = R(b, c, d, e, a, F2, K2, 5, 13, x); 229 | a, c = R(a, b, c, d, e, F2, K2, 12, 11, x); 230 | e, b = R(e, a, b, c, d, F2, K2, 7, 5, x); 231 | d, a = R(d, e, a, b, c, F2, K2, 5, 12, x); #/* #47 */ 232 | #/* Round 4 */ 233 | c, e = R(c, d, e, a, b, F3, K3, 11, 1, x); 234 | b, d = R(b, c, d, e, a, F3, K3, 12, 9, x); 235 | a, c = R(a, b, c, d, e, F3, K3, 14, 11, x); 236 | e, b = R(e, a, b, c, d, F3, K3, 15, 10, x); 237 | d, a = R(d, e, a, b, c, F3, K3, 14, 0, x); 238 | c, e = R(c, d, e, a, b, F3, K3, 15, 8, x); 239 | b, d = R(b, c, d, e, a, F3, K3, 9, 12, x); 240 | a, c = R(a, b, c, d, e, F3, K3, 8, 4, x); 241 | e, b = R(e, a, b, c, d, F3, K3, 9, 13, x); 242 | d, a = R(d, e, a, b, c, F3, K3, 14, 3, x); 243 | c, e = R(c, d, e, a, b, F3, K3, 5, 7, x); 244 | b, d = R(b, c, d, e, a, F3, K3, 6, 15, x); 245 | a, c = R(a, b, c, d, e, F3, K3, 8, 14, x); 246 | e, b = R(e, a, b, c, d, F3, K3, 6, 5, x); 247 | d, a = R(d, e, a, b, c, F3, K3, 5, 6, x); 248 | c, e = R(c, d, e, a, b, F3, K3, 12, 2, x); #/* #63 */ 249 | #/* Round 5 */ 250 | b, d = R(b, c, d, e, a, F4, K4, 9, 4, x); 251 | a, c = R(a, b, c, d, e, F4, K4, 15, 0, x); 252 | e, b = R(e, a, b, c, d, F4, K4, 5, 5, x); 253 | d, a = R(d, e, a, b, c, F4, K4, 11, 9, x); 254 | c, e = R(c, d, e, a, b, F4, K4, 6, 7, x); 255 | b, d = R(b, c, d, e, a, F4, K4, 8, 12, x); 256 | a, c = R(a, b, c, d, e, F4, K4, 13, 2, x); 257 | e, b = R(e, a, b, c, d, F4, K4, 12, 10, x); 258 | d, a = R(d, e, a, b, c, F4, K4, 5, 14, x); 259 | c, e = R(c, d, e, a, b, F4, K4, 12, 1, x); 260 | b, d = R(b, c, d, e, a, F4, K4, 13, 3, x); 261 | a, c = R(a, b, c, d, e, F4, K4, 14, 8, x); 262 | e, b = R(e, a, b, c, d, F4, K4, 11, 11, x); 263 | d, a = R(d, e, a, b, c, F4, K4, 8, 6, x); 264 | c, e = R(c, d, e, a, b, F4, K4, 5, 15, x); 265 | b, d = R(b, c, d, e, a, F4, K4, 6, 13, x); #/* #79 */ 266 | 267 | aa = a; 268 | bb = b; 269 | cc = c; 270 | dd = d; 271 | ee = e; 272 | 273 | a = state[0] 274 | b = state[1] 275 | c = state[2] 276 | d = state[3] 277 | e = state[4] 278 | 279 | #/* Parallel round 1 */ 280 | a, c = R(a, b, c, d, e, F4, KK0, 8, 5, x) 281 | e, b = R(e, a, b, c, d, F4, KK0, 9, 14, x) 282 | d, a = R(d, e, a, b, c, F4, KK0, 9, 7, x) 283 | c, e = R(c, d, e, a, b, F4, KK0, 11, 0, x) 284 | b, d = R(b, c, d, e, a, F4, KK0, 13, 9, x) 285 | a, c = R(a, b, c, d, e, F4, KK0, 15, 2, x) 286 | e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x) 287 | d, a = R(d, e, a, b, c, F4, KK0, 5, 4, x) 288 | c, e = R(c, d, e, a, b, F4, KK0, 7, 13, x) 289 | b, d = R(b, c, d, e, a, F4, KK0, 7, 6, x) 290 | a, c = R(a, b, c, d, e, F4, KK0, 8, 15, x) 291 | e, b = R(e, a, b, c, d, F4, KK0, 11, 8, x) 292 | d, a = R(d, e, a, b, c, F4, KK0, 14, 1, x) 293 | c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x) 294 | b, d = R(b, c, d, e, a, F4, KK0, 12, 3, x) 295 | a, c = R(a, b, c, d, e, F4, KK0, 6, 12, x) #/* #15 */ 296 | #/* Parallel round 2 */ 297 | e, b = R(e, a, b, c, d, F3, KK1, 9, 6, x) 298 | d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x) 299 | c, e = R(c, d, e, a, b, F3, KK1, 15, 3, x) 300 | b, d = R(b, c, d, e, a, F3, KK1, 7, 7, x) 301 | a, c = R(a, b, c, d, e, F3, KK1, 12, 0, x) 302 | e, b = R(e, a, b, c, d, F3, KK1, 8, 13, x) 303 | d, a = R(d, e, a, b, c, F3, KK1, 9, 5, x) 304 | c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x) 305 | b, d = R(b, c, d, e, a, F3, KK1, 7, 14, x) 306 | a, c = R(a, b, c, d, e, F3, KK1, 7, 15, x) 307 | e, b = R(e, a, b, c, d, F3, KK1, 12, 8, x) 308 | d, a = R(d, e, a, b, c, F3, KK1, 7, 12, x) 309 | c, e = R(c, d, e, a, b, F3, KK1, 6, 4, x) 310 | b, d = R(b, c, d, e, a, F3, KK1, 15, 9, x) 311 | a, c = R(a, b, c, d, e, F3, KK1, 13, 1, x) 312 | e, b = R(e, a, b, c, d, F3, KK1, 11, 2, x) #/* #31 */ 313 | #/* Parallel round 3 */ 314 | d, a = R(d, e, a, b, c, F2, KK2, 9, 15, x) 315 | c, e = R(c, d, e, a, b, F2, KK2, 7, 5, x) 316 | b, d = R(b, c, d, e, a, F2, KK2, 15, 1, x) 317 | a, c = R(a, b, c, d, e, F2, KK2, 11, 3, x) 318 | e, b = R(e, a, b, c, d, F2, KK2, 8, 7, x) 319 | d, a = R(d, e, a, b, c, F2, KK2, 6, 14, x) 320 | c, e = R(c, d, e, a, b, F2, KK2, 6, 6, x) 321 | b, d = R(b, c, d, e, a, F2, KK2, 14, 9, x) 322 | a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x) 323 | e, b = R(e, a, b, c, d, F2, KK2, 13, 8, x) 324 | d, a = R(d, e, a, b, c, F2, KK2, 5, 12, x) 325 | c, e = R(c, d, e, a, b, F2, KK2, 14, 2, x) 326 | b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x) 327 | a, c = R(a, b, c, d, e, F2, KK2, 13, 0, x) 328 | e, b = R(e, a, b, c, d, F2, KK2, 7, 4, x) 329 | d, a = R(d, e, a, b, c, F2, KK2, 5, 13, x) #/* #47 */ 330 | #/* Parallel round 4 */ 331 | c, e = R(c, d, e, a, b, F1, KK3, 15, 8, x) 332 | b, d = R(b, c, d, e, a, F1, KK3, 5, 6, x) 333 | a, c = R(a, b, c, d, e, F1, KK3, 8, 4, x) 334 | e, b = R(e, a, b, c, d, F1, KK3, 11, 1, x) 335 | d, a = R(d, e, a, b, c, F1, KK3, 14, 3, x) 336 | c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x) 337 | b, d = R(b, c, d, e, a, F1, KK3, 6, 15, x) 338 | a, c = R(a, b, c, d, e, F1, KK3, 14, 0, x) 339 | e, b = R(e, a, b, c, d, F1, KK3, 6, 5, x) 340 | d, a = R(d, e, a, b, c, F1, KK3, 9, 12, x) 341 | c, e = R(c, d, e, a, b, F1, KK3, 12, 2, x) 342 | b, d = R(b, c, d, e, a, F1, KK3, 9, 13, x) 343 | a, c = R(a, b, c, d, e, F1, KK3, 12, 9, x) 344 | e, b = R(e, a, b, c, d, F1, KK3, 5, 7, x) 345 | d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x) 346 | c, e = R(c, d, e, a, b, F1, KK3, 8, 14, x) #/* #63 */ 347 | #/* Parallel round 5 */ 348 | b, d = R(b, c, d, e, a, F0, KK4, 8, 12, x) 349 | a, c = R(a, b, c, d, e, F0, KK4, 5, 15, x) 350 | e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x) 351 | d, a = R(d, e, a, b, c, F0, KK4, 9, 4, x) 352 | c, e = R(c, d, e, a, b, F0, KK4, 12, 1, x) 353 | b, d = R(b, c, d, e, a, F0, KK4, 5, 5, x) 354 | a, c = R(a, b, c, d, e, F0, KK4, 14, 8, x) 355 | e, b = R(e, a, b, c, d, F0, KK4, 6, 7, x) 356 | d, a = R(d, e, a, b, c, F0, KK4, 8, 6, x) 357 | c, e = R(c, d, e, a, b, F0, KK4, 13, 2, x) 358 | b, d = R(b, c, d, e, a, F0, KK4, 6, 13, x) 359 | a, c = R(a, b, c, d, e, F0, KK4, 5, 14, x) 360 | e, b = R(e, a, b, c, d, F0, KK4, 15, 0, x) 361 | d, a = R(d, e, a, b, c, F0, KK4, 13, 3, x) 362 | c, e = R(c, d, e, a, b, F0, KK4, 11, 9, x) 363 | b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) #/* #79 */ 364 | 365 | t = (state[1] + cc + d) % 0x100000000; 366 | state[1] = (state[2] + dd + e) % 0x100000000; 367 | state[2] = (state[3] + ee + a) % 0x100000000; 368 | state[3] = (state[4] + aa + b) % 0x100000000; 369 | state[4] = (state[0] + bb + c) % 0x100000000; 370 | state[0] = t % 0x100000000; 371 | 372 | pass 373 | 374 | 375 | def RMD160Update(ctx, inp, inplen): 376 | if type(inp) == str: 377 | inp = [ord(i)&0xff for i in inp] 378 | 379 | have = int((ctx.count // 8) % 64) 380 | inplen = int(inplen) 381 | need = 64 - have 382 | ctx.count += 8 * inplen 383 | off = 0 384 | if inplen >= need: 385 | if have: 386 | for i in range(need): 387 | ctx.buffer[have+i] = inp[i] 388 | RMD160Transform(ctx.state, ctx.buffer) 389 | off = need 390 | have = 0 391 | while off + 64 <= inplen: 392 | RMD160Transform(ctx.state, inp[off:]) #<--- 393 | off += 64 394 | if off < inplen: 395 | # memcpy(ctx->buffer + have, input+off, len-off); 396 | for i in range(inplen - off): 397 | ctx.buffer[have+i] = inp[off+i] 398 | 399 | def RMD160Final(ctx): 400 | size = struct.pack(" 73: return False 180 | if (sig[0] != 0x30): return False 181 | if (sig[1] != len(sig)-3): return False 182 | rlen = sig[3] 183 | if (5+rlen >= len(sig)): return False 184 | slen = sig[5+rlen] 185 | if (rlen + slen + 7 != len(sig)): return False 186 | if (sig[2] != 0x02): return False 187 | if (rlen == 0): return False 188 | if (sig[4] & 0x80): return False 189 | if (rlen > 1 and (sig[4] == 0x00) and not (sig[5] & 0x80)): return False 190 | if (sig[4+rlen] != 0x02): return False 191 | if (slen == 0): return False 192 | if (sig[rlen+6] & 0x80): return False 193 | if (slen > 1 and (sig[6+rlen] == 0x00) and not (sig[7+rlen] & 0x80)): 194 | return False 195 | return True 196 | 197 | def txhash(tx, hashcode=None): 198 | if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx): 199 | tx = changebase(tx, 16, 256) 200 | if hashcode: 201 | return dbl_sha256(from_string_to_bytes(tx) + encode(int(hashcode), 256, 4)[::-1]) 202 | else: 203 | return safe_hexlify(bin_dbl_sha256(tx)[::-1]) 204 | 205 | 206 | def bin_txhash(tx, hashcode=None): 207 | return binascii.unhexlify(txhash(tx, hashcode)) 208 | 209 | 210 | def ecdsa_tx_sign(tx, priv, hashcode=SIGHASH_ALL): 211 | rawsig = ecdsa_raw_sign(bin_txhash(tx, hashcode), priv) 212 | return der_encode_sig(*rawsig)+encode(hashcode, 16, 2) 213 | 214 | 215 | def ecdsa_tx_verify(tx, sig, pub, hashcode=SIGHASH_ALL): 216 | return ecdsa_raw_verify(bin_txhash(tx, hashcode), der_decode_sig(sig), pub) 217 | 218 | 219 | def ecdsa_tx_recover(tx, sig, hashcode=SIGHASH_ALL): 220 | z = bin_txhash(tx, hashcode) 221 | _, r, s = der_decode_sig(sig) 222 | left = ecdsa_raw_recover(z, (0, r, s)) 223 | right = ecdsa_raw_recover(z, (1, r, s)) 224 | return (encode_pubkey(left, 'hex'), encode_pubkey(right, 'hex')) 225 | 226 | # Scripts 227 | 228 | 229 | def mk_pubkey_script(addr): 230 | # Keep the auxiliary functions around for altcoins' sake 231 | return '76a914' + b58check_to_hex(addr) + '88ac' 232 | 233 | 234 | def mk_scripthash_script(addr): 235 | return 'a914' + b58check_to_hex(addr) + '87' 236 | 237 | # Address representation to output script 238 | 239 | 240 | def address_to_script(addr): 241 | if addr[0] == '3' or addr[0] == '2': 242 | return mk_scripthash_script(addr) 243 | else: 244 | return mk_pubkey_script(addr) 245 | 246 | # Output script to address representation 247 | 248 | 249 | def script_to_address(script, vbyte=0): 250 | if re.match('^[0-9a-fA-F]*$', script): 251 | script = binascii.unhexlify(script) 252 | if script[:3] == b'\x76\xa9\x14' and script[-2:] == b'\x88\xac' and len(script) == 25: 253 | return bin_to_b58check(script[3:-2], vbyte) # pubkey hash addresses 254 | else: 255 | if vbyte in [111, 196]: 256 | # Testnet 257 | scripthash_byte = 196 258 | elif vbyte == 0: 259 | # Mainnet 260 | scripthash_byte = 5 261 | else: 262 | scripthash_byte = vbyte 263 | # BIP0016 scripthash addresses 264 | return bin_to_b58check(script[2:-1], scripthash_byte) 265 | 266 | 267 | def p2sh_scriptaddr(script, magicbyte=5): 268 | if re.match('^[0-9a-fA-F]*$', script): 269 | script = binascii.unhexlify(script) 270 | return hex_to_b58check(hash160(script), magicbyte) 271 | scriptaddr = p2sh_scriptaddr 272 | 273 | 274 | def deserialize_script(script): 275 | if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script): 276 | return json_changebase(deserialize_script(binascii.unhexlify(script)), 277 | lambda x: safe_hexlify(x)) 278 | out, pos = [], 0 279 | while pos < len(script): 280 | code = from_byte_to_int(script[pos]) 281 | if code == 0: 282 | out.append(None) 283 | pos += 1 284 | elif code <= 75: 285 | out.append(script[pos+1:pos+1+code]) 286 | pos += 1 + code 287 | elif code <= 78: 288 | szsz = pow(2, code - 76) 289 | sz = decode(script[pos+szsz: pos:-1], 256) 290 | out.append(script[pos + 1 + szsz:pos + 1 + szsz + sz]) 291 | pos += 1 + szsz + sz 292 | elif code <= 96: 293 | out.append(code - 80) 294 | pos += 1 295 | else: 296 | out.append(code) 297 | pos += 1 298 | return out 299 | 300 | 301 | def serialize_script_unit(unit): 302 | if isinstance(unit, int): 303 | if unit < 16: 304 | return from_int_to_byte(unit + 80) 305 | else: 306 | return from_int_to_byte(unit) 307 | elif unit is None: 308 | return b'\x00' 309 | else: 310 | if len(unit) <= 75: 311 | return from_int_to_byte(len(unit))+unit 312 | elif len(unit) < 256: 313 | return from_int_to_byte(76)+from_int_to_byte(len(unit))+unit 314 | elif len(unit) < 65536: 315 | return from_int_to_byte(77)+encode(len(unit), 256, 2)[::-1]+unit 316 | else: 317 | return from_int_to_byte(78)+encode(len(unit), 256, 4)[::-1]+unit 318 | 319 | 320 | if is_python2: 321 | def serialize_script(script): 322 | if json_is_base(script, 16): 323 | return binascii.hexlify(serialize_script(json_changebase(script, 324 | lambda x: binascii.unhexlify(x)))) 325 | return ''.join(map(serialize_script_unit, script)) 326 | else: 327 | def serialize_script(script): 328 | if json_is_base(script, 16): 329 | return safe_hexlify(serialize_script(json_changebase(script, 330 | lambda x: binascii.unhexlify(x)))) 331 | 332 | result = bytes() 333 | for b in map(serialize_script_unit, script): 334 | result += b if isinstance(b, bytes) else bytes(b, 'utf-8') 335 | return result 336 | 337 | 338 | def mk_multisig_script(*args): # [pubs],k or pub1,pub2...pub[n],k 339 | if isinstance(args[0], list): 340 | pubs, k = args[0], int(args[1]) 341 | else: 342 | pubs = list(filter(lambda x: len(str(x)) >= 32, args)) 343 | k = int(args[len(pubs)]) 344 | return serialize_script([k]+pubs+[len(pubs)]+[0xae]) 345 | 346 | # Signing and verifying 347 | 348 | 349 | def verify_tx_input(tx, i, script, sig, pub): 350 | if re.match('^[0-9a-fA-F]*$', tx): 351 | tx = binascii.unhexlify(tx) 352 | if re.match('^[0-9a-fA-F]*$', script): 353 | script = binascii.unhexlify(script) 354 | if not re.match('^[0-9a-fA-F]*$', sig): 355 | sig = safe_hexlify(sig) 356 | hashcode = decode(sig[-2:], 16) 357 | modtx = signature_form(tx, int(i), script, hashcode) 358 | return ecdsa_tx_verify(modtx, sig, pub, hashcode) 359 | 360 | 361 | def sign(tx, i, priv, hashcode=SIGHASH_ALL): 362 | i = int(i) 363 | if (not is_python2 and isinstance(re, bytes)) or not re.match('^[0-9a-fA-F]*$', tx): 364 | return binascii.unhexlify(sign(safe_hexlify(tx), i, priv)) 365 | if len(priv) <= 33: 366 | priv = safe_hexlify(priv) 367 | pub = privkey_to_pubkey(priv) 368 | address = pubkey_to_address(pub) 369 | signing_tx = signature_form(tx, i, mk_pubkey_script(address), hashcode) 370 | sig = ecdsa_tx_sign(signing_tx, priv, hashcode) 371 | txobj = deserialize(tx) 372 | txobj["ins"][i]["script"] = serialize_script([sig, pub]) 373 | return serialize(txobj) 374 | 375 | 376 | def signall(tx, priv): 377 | # if priv is a dictionary, assume format is 378 | # { 'txinhash:txinidx' : privkey } 379 | if isinstance(priv, dict): 380 | for e, i in enumerate(deserialize(tx)["ins"]): 381 | k = priv["%s:%d" % (i["outpoint"]["hash"], i["outpoint"]["index"])] 382 | tx = sign(tx, e, k) 383 | else: 384 | for i in range(len(deserialize(tx)["ins"])): 385 | tx = sign(tx, i, priv) 386 | return tx 387 | 388 | 389 | def multisign(tx, i, script, pk, hashcode=SIGHASH_ALL): 390 | if re.match('^[0-9a-fA-F]*$', tx): 391 | tx = binascii.unhexlify(tx) 392 | if re.match('^[0-9a-fA-F]*$', script): 393 | script = binascii.unhexlify(script) 394 | modtx = signature_form(tx, i, script, hashcode) 395 | return ecdsa_tx_sign(modtx, pk, hashcode) 396 | 397 | 398 | def apply_multisignatures(*args): 399 | # tx,i,script,sigs OR tx,i,script,sig1,sig2...,sig[n] 400 | tx, i, script = args[0], int(args[1]), args[2] 401 | sigs = args[3] if isinstance(args[3], list) else list(args[3:]) 402 | 403 | if isinstance(script, str) and re.match('^[0-9a-fA-F]*$', script): 404 | script = binascii.unhexlify(script) 405 | sigs = [binascii.unhexlify(x) if x[:2] == '30' else x for x in sigs] 406 | if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx): 407 | return safe_hexlify(apply_multisignatures(binascii.unhexlify(tx), i, script, sigs)) 408 | 409 | # Not pushing empty elements on the top of the stack if passing no 410 | # script (in case of bare multisig inputs there is no script) 411 | script_blob = [] if script.__len__() == 0 else [script] 412 | 413 | txobj = deserialize(tx) 414 | txobj["ins"][i]["script"] = serialize_script([None]+sigs+script_blob) 415 | return serialize(txobj) 416 | 417 | 418 | def is_inp(arg): 419 | return len(arg) > 64 or "output" in arg or "outpoint" in arg 420 | 421 | 422 | def mktx(*args): 423 | # [in0, in1...],[out0, out1...] or in0, in1 ... out0 out1 ... 424 | ins, outs = [], [] 425 | for arg in args: 426 | if isinstance(arg, list): 427 | for a in arg: (ins if is_inp(a) else outs).append(a) 428 | else: 429 | (ins if is_inp(arg) else outs).append(arg) 430 | 431 | txobj = {"locktime": 0, "version": 1, "ins": [], "outs": []} 432 | for i in ins: 433 | if isinstance(i, dict) and "outpoint" in i: 434 | txobj["ins"].append(i) 435 | else: 436 | if isinstance(i, dict) and "output" in i: 437 | i = i["output"] 438 | txobj["ins"].append({ 439 | "outpoint": {"hash": i[:64], "index": int(i[65:])}, 440 | "script": "", 441 | "sequence": 4294967295 442 | }) 443 | for o in outs: 444 | if isinstance(o, string_or_bytes_types): 445 | addr = o[:o.find(':')] 446 | val = int(o[o.find(':')+1:]) 447 | o = {} 448 | if re.match('^[0-9a-fA-F]*$', addr): 449 | o["script"] = addr 450 | else: 451 | o["address"] = addr 452 | o["value"] = val 453 | 454 | outobj = {} 455 | if "address" in o: 456 | outobj["script"] = address_to_script(o["address"]) 457 | elif "script" in o: 458 | outobj["script"] = o["script"] 459 | else: 460 | raise Exception("Could not find 'address' or 'script' in output.") 461 | outobj["value"] = o["value"] 462 | txobj["outs"].append(outobj) 463 | 464 | return serialize(txobj) 465 | 466 | 467 | def select(unspent, value): 468 | value = int(value) 469 | high = [u for u in unspent if u["value"] >= value] 470 | high.sort(key=lambda u: u["value"]) 471 | low = [u for u in unspent if u["value"] < value] 472 | low.sort(key=lambda u: -u["value"]) 473 | if len(high): 474 | return [high[0]] 475 | i, tv = 0, 0 476 | while tv < value and i < len(low): 477 | tv += low[i]["value"] 478 | i += 1 479 | if tv < value: 480 | raise Exception("Not enough funds") 481 | return low[:i] 482 | 483 | # Only takes inputs of the form { "output": blah, "value": foo } 484 | 485 | 486 | def mksend(*args): 487 | argz, change, fee = args[:-2], args[-2], int(args[-1]) 488 | ins, outs = [], [] 489 | for arg in argz: 490 | if isinstance(arg, list): 491 | for a in arg: 492 | (ins if is_inp(a) else outs).append(a) 493 | else: 494 | (ins if is_inp(arg) else outs).append(arg) 495 | 496 | isum = sum([i["value"] for i in ins]) 497 | osum, outputs2 = 0, [] 498 | for o in outs: 499 | if isinstance(o, string_types): 500 | o2 = { 501 | "address": o[:o.find(':')], 502 | "value": int(o[o.find(':')+1:]) 503 | } 504 | else: 505 | o2 = o 506 | outputs2.append(o2) 507 | osum += o2["value"] 508 | 509 | if isum < osum+fee: 510 | raise Exception("Not enough money") 511 | elif isum > osum+fee+5430: 512 | outputs2 += [{"address": change, "value": isum-osum-fee}] 513 | 514 | return mktx(ins, outputs2) 515 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import random 4 | import codecs 5 | import redis 6 | import os 7 | import importlib 8 | 9 | import telebot 10 | import jinja2 11 | 12 | 13 | logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level = logging.INFO) 14 | 15 | class Bot: 16 | def __init__(self, bot_name="super-bot", debug=False): 17 | self.logger = logging.getLogger(bot_name) 18 | self.logger.info("Starting bot") 19 | 20 | self.handlers = {} 21 | self.debug = debug 22 | self.callback_handlers = {} 23 | 24 | self.logger.info("Load config") 25 | self.admin = int(os.environ["ADMIN"]) 26 | self.const = {} 27 | self.const.update(json.loads(codecs.open("config/init.json", "r", "utf-8").read())) 28 | self.const["messages"] = json.loads(codecs.open("config/messages.json", "r", "utf-8").read()) 29 | self.const["keyboards"] = json.loads(codecs.open("config/keyboards.json", "r", "utf-8").read()) 30 | 31 | self.logger.info("Connect to Telegram") 32 | self.telegram = telebot.TeleBot(os.environ["BOT_TOKEN"]) 33 | self.telegram.set_update_listener(self.proсess_updates) 34 | 35 | self.redis = redis.from_url(os.environ.get("REDIS_URL","redis://localhost:6379")) 36 | self.data = {} 37 | 38 | self.logger.info("Collect modules") 39 | self._collect_modules() 40 | 41 | self.logger.info("Ready") 42 | 43 | def _collect_modules(self): 44 | for module_name in os.listdir("modules"): 45 | if module_name.endswith(".py"): 46 | module = importlib.import_module("modules.%s" % module_name[:-3]) 47 | module.init(self) 48 | 49 | 50 | def user_set(self, user_id, field, value, **kwargs): 51 | key = "user:%s:%s"%(user_id, field) 52 | value = json.dumps(value) 53 | self.redis.set(key, value, kwargs) 54 | 55 | self.logger.info("user:%s set[%s]>>\"%s\""%(user_id, field, value)) 56 | 57 | def user_get(self, user_id, field, default=None): 58 | key = "user:%s:%s"%(user_id, field) 59 | value = self.redis.get(key) 60 | if type(value) is bytes: value = value.decode('utf-8') 61 | 62 | self.logger.info("user:%s get[%s]>>\"%s\""%(user_id, field, value)) 63 | 64 | if value is None: value = default 65 | else: value = json.loads(value) 66 | 67 | return value 68 | 69 | def user_delete(self, user_id, field): 70 | key = "user:%s:%s"%(user_id, field) 71 | self.redis.delete(key) 72 | self.logger.info("user:%s delete[%s]"%(user_id, field)) 73 | 74 | 75 | 76 | def set_next_handler(self, user_id, handler): 77 | self.user_set(user_id, "next_handler", handler) 78 | 79 | def call_handler(self, handler, message, forward_flag=True): 80 | self.logger.info("user:%s call_handler[%s]" % (message.u_id, handler)) 81 | 82 | try: 83 | if forward_flag: message.forward = True 84 | self.handlers[handler](self, message) 85 | except Exception as ex: 86 | self.logger.error(ex) 87 | if self.debug: raise ex 88 | 89 | def proсess_updates(self, updates): 90 | if type(updates) is telebot.types.Update: 91 | if updates.message is not None: self._process_message(updates.message) 92 | if updates.callback_query is not None: self._process_callback(updates.callback_query) 93 | return 94 | 95 | for update in updates: 96 | if type(update) is telebot.types.Message: self._process_message(update) 97 | if type(update) is telebot.types.CallbackQuery: self._process_message(update) 98 | 99 | def _process_message(self, message): 100 | message.u_id = message.chat.id 101 | message.forward = False 102 | 103 | if message.text == self.const["menu-button"]: 104 | self.set_next_handler(message.u_id, self.const["default-handler"]) 105 | message.forward = True 106 | 107 | current_handler = self.user_get(message.u_id, "next_handler", default = self.const["default-handler"]) 108 | self.set_next_handler(message.u_id, self.const["default-handler"]) 109 | 110 | try: 111 | self.call_handler(current_handler, message, forward_flag=False) 112 | except Exception as ex: 113 | self.logger.error(ex) 114 | if self.debug: raise ex 115 | self.set_next_handler(message.u_id, self.const["default-handler"]) 116 | self.call_handler(self.const["default-handler"], message) 117 | 118 | def _process_callback(self, query): 119 | query.u_id = query.message.chat.id 120 | query.message.u_id = query.u_id 121 | if query.data: 122 | callback = query.data.split("/")[0] 123 | try: 124 | self.logger.info("user:%s callback[%s]"%(query.u_id, query.u_id)) 125 | self.callback_handlers[callback](self, query) 126 | except Exception as ex: 127 | self.logger.error(ex) 128 | if self.debug: raise ex 129 | else: self.logger.error("user:%s callback[None]"%(query.u_id)) 130 | 131 | def render_message(self, key, **kwargs): 132 | messages = self.const["messages"][key] 133 | 134 | if type(messages) is list: message = random.choice(messages) 135 | else: message = messages 136 | 137 | message = jinja2.Template(message) 138 | 139 | return message.render(**kwargs) 140 | 141 | def get_keyboard(self, keyboard): 142 | if keyboard is None: 143 | markup = telebot.types.ReplyKeyboardRemove() 144 | else: 145 | if type(keyboard) is str: keyboard = self.const["keyboards"][keyboard] 146 | markup = telebot.types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True) 147 | for row in keyboard: 148 | keyboard_row = [] 149 | for col in row: 150 | if "--position" in col[1]: 151 | keyboard_row.append(telebot.types.KeyboardButton(col[0], request_location = True)) 152 | else: 153 | keyboard_row.append(telebot.types.KeyboardButton(col[0])) 154 | markup.row(*keyboard_row) 155 | 156 | return markup 157 | 158 | def get_inline_keyboard(self, keyboard, params = {}): 159 | if type(keyboard) is str: keyboard = self.const["keyboards"][keyboard] 160 | markup = telebot.types.InlineKeyboardMarkup(row_width=3) 161 | for row in keyboard: 162 | keyboard_row = [] 163 | for col in row: 164 | if "--url" in col[1]: 165 | item_url = params.get(col[1].split("/")[1]) 166 | if not item_url is None: keyboard_row.append(telebot.types.InlineKeyboardButton(col[0], url = item_url)) 167 | elif "--data" in col[1]: 168 | data = params.get(col[1].split("/")[1]) 169 | if not data is None: 170 | col[1] = "/".join([col[1].split("/")[0], data]) 171 | keyboard_row.append(telebot.types.InlineKeyboardButton(col[0], callback_data = col[1])) 172 | else: 173 | keyboard_row.append(telebot.types.InlineKeyboardButton(col[0], callback_data = col[1])) 174 | markup.row(*keyboard_row) 175 | 176 | return markup 177 | 178 | def get_key(self, keyboard, message): 179 | if type(keyboard) is str: keyboard = self.const["keyboards"][keyboard] 180 | for row in keyboard: 181 | for col in row: 182 | if message == col[0]: return col[1] -------------------------------------------------------------------------------- /config/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "default-handler": "main-menu", 3 | "menu-button": "Меню 🏠", 4 | "comission": 1000, 5 | "banks": ["📗 Сбербанк", "📒 Тинькофф", "📘 ВТБ", "📕 Альфа-Банк"] 6 | } -------------------------------------------------------------------------------- /config/keyboards.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu-keyboard": [[["💳 Кошелек", "wallet-info/start"], ["📈 Купить", "buy-money/start"]], [["Получить 📥", "get-money/start"],["Отправить 📤", "send-money/start"]]], 3 | "admin-menu-keyboard": [[["💳 Кошелек", "wallet-info/start"]], [["Получить 📥", "get-money/start"],["Отправить 📤", "send-money/start"]]], 4 | "accept": [[["✅ Да", "yes"]], [["❌ Нет", "no"]]], 5 | "confirm-tx": [[["✅ Подтвердить отправку", "admin-confirm-tx/tx-id/--data"]], [["❌ Отменить отправку", "admin-not-confirm-tx/tx-id/--data"]]], 6 | "confirm-purchase": [[["✅ Да, я отправил.", "yes"]], [["❌ Отменить транзакцию", "no"]]], 7 | "back-to-menu": [[["Меню 🏠", "back-to-menu"]]] 8 | } -------------------------------------------------------------------------------- /config/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu-message": "Чем могу помочь?", 3 | "start": "Добрый день ✌️, я *Fastcoin-Бот*, я помогу вам с легкостью оперировать вашими виртуальными накомплениями 💰.\nBitcoin кошелек создан, можете начать пользование системой 🛠.", 4 | 5 | "get-address-to-send": "🎫 *Bitcoin адрес получателя*\nВведите публичный *bitcoin адрес* для отправки средств.", 6 | "get-value-to-send": "Введите колличество 💰 *BTC* для отправки.\n\n_Учтите, комиссия при переводе составит:_ *{{\"{:.8f}\".format(comission|float)}} BTC*", 7 | "accept-sending": "‼️*Внимание* ‼️\n\nВы действительно хотите отправить *{{btc}} BTC* на *{{address}}*?", 8 | "incorrect-address-to-send": "⛔️ Некорректный Bitcoin адрес, попробуйте еще раз.", 9 | "incorrect-value-to-send": "⛔️ Некорректный значение BTC, попробуйте еще раз.", 10 | "no-funds-to-send": "⛔️ Недостаточно средств для совершения транзакции.", 11 | "push-error": "⛔️ Ошибка при отправке транзакции.", 12 | "ready-send": "✅ Готово!\n_Подтверждение транзакции другими участниками сети произойдет в течение 10-60 минут_", 13 | 14 | "get-value-to-buy": "💵 *Введите сумму покупки*\n_Текущий курс: {{currency}} RUB_\n\n_Покупка осуществляется переводом средств на указанную карту._\n\nНапример: *200 RUB* или *0.5 BTC*", 15 | "incorrect-value-to-buy": "⛔️ Некорректный ввод, попробуйте еще раз.", 16 | "get-username-to-buy": "💁‍♂️ *Имя отправителя*\nВведите ФИО лица с чьего счета будет производится оплата.", 17 | "pending-payment": "⏳ *Ожидается оплата*\nДля получения *{{\"{:.8f}\".format(btc|float)}} BTC* отправьте *{{\"{:.2f}\".format(rub|float)}} RUB* на указанный счет в течение *30 минут*.\n\n_Тапните на номер карты для копирования_", 18 | "card-number": "{% for card in cards %}*{{card.name}}*: `{{card.number}}`\n{% endfor %}", 19 | "btc-address": "*{{address}}*", 20 | 21 | "tx-creating-error": "🚫 Создание данной транзакции по техническим причинам невозможно.", 22 | "tx-cancelled": "🚫 Транзакция отменена.", 23 | "wait-accepting": "🕰 Ожидайте подтверждения оператора.", 24 | 25 | "tx-info-for-admin": "👨‍🔧 *Отправитель:* {{tx.username}}\n🎫 *Адрес:* {{tx.address}}\n💰 *Сумма:* {{\"{:.8f}\".format(tx.btc_value/10**8|float)}} BTC\n💵 *Оплата:* {{\"{:.2f}\".format(tx.rub_value|float)}} RUB\n\n{% if state == 1%}✅ Транзакция подтверждена.{% elif state == 2 %}🚫 Транзакция отменена.{% endif %}", 26 | 27 | "tx-confirmed":"✅ Готово! Транзакция подтверждена.", 28 | "tx-not-confirmed":"🚫 Транзакция отменена администратором.", 29 | "old-tx": "🚫 Транзакция отменена в связи со сроком давности.", 30 | 31 | "wallet-info": "💼 *Bitcoin кошелек*\n\n💰 *Баланс*: _{{\"{:.8f}\".format(balance|float)}} BTC_\n💵 *Примерно*: _{{\"{:.2f}\".format(rub|float)}} RUB_", 32 | "get-money": "*📥 Внести Bitcoin*\nДля пополнения *BTC* с внешнего кошелька используйте многоразовый адрес ниже. \n_Средства поступают через 1 подтверждение сети._" 33 | } -------------------------------------------------------------------------------- /main_wallet.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import copy 4 | import random 5 | import os 6 | import json 7 | 8 | from wallet import Wallet 9 | 10 | import bitcoin 11 | 12 | class MainWallet(Wallet): 13 | def __init__(self, bot, priv): 14 | self.bot = bot 15 | self.ttl = 35*60 16 | 17 | pub = bitcoin.privtopub(priv) 18 | addr = bitcoin.pubtoaddr(pub) 19 | 20 | self.cards = [{"name":name, "number":number} for name, number in zip(bot.const["banks"], os.environ.get("CARD_NUMBER").split(";"))] 21 | self.private = priv 22 | self.public = pub 23 | self.address = addr 24 | self.comission = bot.const["comission"] 25 | 26 | def get_tx(self, tx_id): 27 | self._compare_all_tx() 28 | tx = self.bot.user_get(0, "tx/%s" % tx_id) 29 | return tx 30 | 31 | def _compare_all_tx(self): 32 | tx_list = self.bot.user_get(0, "tx-list", default=[]) 33 | for tx_id in copy.copy(tx_list): 34 | tx = self.bot.user_get(0, "tx/%s" % tx_id) 35 | if tx: 36 | if time.time() - tx["time"] >= self.ttl: 37 | self.bot.user_delete(0, "tx/%s" % tx_id) 38 | tx_list.remove(tx_id) 39 | else: 40 | tx_list.remove(tx_id) 41 | 42 | self.bot.user_set(0, "tx-list", tx_list) 43 | 44 | def _compare_tx(self, tx): 45 | self._compare_all_tx() 46 | tx_list = self.bot.user_get(0, "tx-list", default=[]) 47 | cur_balance = self.get_balance() 48 | 49 | for tx_id in copy.copy(tx_list): 50 | _tx = self.bot.user_get(0, "tx/%s" % tx_id) 51 | cur_balance -= _tx["btc_value"] 52 | 53 | if cur_balance - tx["btc_value"] >= 0 and tx["btc_value"] != 0: 54 | return True 55 | else: 56 | return False 57 | 58 | def generate_id(self): 59 | return "".join([random.choice("1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM") for i in range(23)]) 60 | 61 | def add_tx(self, username, u_id, btc_value, rub_value): 62 | user_wallet = Wallet(self.bot, u_id) 63 | 64 | tx = {"id": self.generate_id(), 65 | "username": username, 66 | "uid": u_id, 67 | "time": time.time(), 68 | "address": user_wallet.address, 69 | "btc_value": btc_value*10**8, 70 | "rub_value": rub_value} 71 | 72 | 73 | if self._compare_tx(tx): 74 | self.bot.user_set(0, "tx/%s" % tx["id"], tx) 75 | 76 | tx_list = self.bot.user_get(0, "tx-list", default=[]) 77 | tx_list.append(tx["id"]) 78 | self.bot.user_set(0, "tx-list", tx_list) 79 | return tx["id"] 80 | else: 81 | return -1 82 | 83 | def confirm_tx(self, tx_id): 84 | tx_list = self.bot.user_get(0, "tx-list", default=[]) 85 | tx = self.bot.user_get(0, "tx/%s" % tx_id) 86 | 87 | tx_list.remove(tx_id) 88 | self.bot.user_set(0, "tx-list", tx_list) 89 | self.bot.user_delete(0, "tx/%s" % tx_id) 90 | 91 | self.send_money(tx["btc_value"], tx["address"]) 92 | 93 | def not_confirm_tx(self, tx_id): 94 | tx_list = self.bot.user_get(0, "tx-list", default=[]) 95 | 96 | tx = self.bot.user_get(0, "tx/%s" % tx_id) 97 | 98 | tx_list.remove(tx_id) 99 | self.bot.user_set(0, "tx-list", tx_list) 100 | self.bot.user_delete(0, "tx/%s" % tx_id) -------------------------------------------------------------------------------- /modules/buy_money.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | from main_wallet import MainWallet 5 | 6 | main_wallet = None 7 | 8 | def init(bot): 9 | global main_wallet 10 | main_wallet = MainWallet(bot, os.environ.get("PRIVATE_KEY")) 11 | 12 | bot.handlers["buy-money/start"] = start 13 | bot.handlers["buy-money/get-value"] = get_value 14 | bot.handlers["buy-money/get-username"] = get_username 15 | bot.handlers["buy-money/confirm"] = confirm 16 | 17 | bot.callback_handlers["admin-confirm-tx"] = admin_confirm 18 | bot.callback_handlers["admin-not-confirm-tx"] = admin_not_confirm 19 | 20 | def start(bot, message): 21 | get_value_message = bot.render_message("get-value-to-buy", currency=main_wallet.get_currency()) 22 | back_to_menu_keyboard = bot.get_keyboard("back-to-menu") 23 | 24 | bot.telegram.send_message(message.u_id, get_value_message, parse_mode="Markdown", reply_markup=back_to_menu_keyboard) 25 | bot.set_next_handler(message.u_id, "buy-money/get-value") 26 | 27 | def get_value(bot, message): 28 | incorrect_value_message = bot.render_message("incorrect-value-to-buy") 29 | get_username_message = bot.render_message("get-username-to-buy") 30 | back_to_menu_keyboard = bot.get_keyboard("back-to-menu") 31 | 32 | 33 | message.text = message.text.upper() 34 | serach_result = re.search("(?P[0-9]{1,}([,.][0-9]{1,}){0,1}).*(?P(BTC|RUB))", message.text) 35 | if serach_result: 36 | value, currency = float(serach_result.group("value")), serach_result.group("currency") 37 | if currency == "RUB": 38 | rub = value 39 | btc = value/(main_wallet.get_currency() * 1.1) 40 | else: 41 | btc = value 42 | rub = value*(main_wallet.get_currency() * 1.1) 43 | 44 | 45 | tx = {"rub_value": rub, 46 | "btc_value": btc, 47 | "uid": message.u_id} 48 | 49 | bot.user_set(message.u_id, "buy-btc:tx", tx) 50 | 51 | bot.telegram.send_message(message.u_id, get_username_message, parse_mode="Markdown", reply_markup=back_to_menu_keyboard) 52 | bot.set_next_handler(message.u_id, "buy-money/get-username") 53 | else: 54 | bot.telegram.send_message(message.u_id, incorrect_value_message, parse_mode="Markdown", reply_markup=back_to_menu_keyboard) 55 | bot.call_handler("buy-money/start", message) 56 | return 57 | 58 | def get_username(bot, message): 59 | tx = bot.user_get(message.u_id, "buy-btc:tx") 60 | 61 | confirm_message = bot.render_message("pending-payment", btc=tx["btc_value"], rub=tx["rub_value"]) 62 | card_number_message = bot.render_message("card-number", cards=main_wallet.cards) 63 | 64 | tx_creating_error_message = bot.render_message("tx-creating-error") 65 | confirm_keyboard = bot.get_keyboard("confirm-purchase") 66 | 67 | if not message.forward: 68 | username = message.text 69 | tx["username"] = username 70 | 71 | tx_id = main_wallet.add_tx(tx["username"], tx["uid"], tx["btc_value"], tx["rub_value"]) 72 | bot.user_delete(message.u_id, "buy-btc:tx") 73 | 74 | if tx_id == -1: 75 | bot.telegram.send_message(message.u_id, tx_creating_error_message, parse_mode="Markdown") 76 | bot.call_handler("main-menu", message) 77 | return 78 | 79 | bot.user_set(message.u_id, "buy-btc:tx-id", tx_id) 80 | 81 | bot.telegram.send_message(message.u_id, confirm_message, parse_mode="Markdown", reply_markup=confirm_keyboard) 82 | bot.telegram.send_message(message.u_id, card_number_message, parse_mode="Markdown", reply_markup=confirm_keyboard) 83 | 84 | bot.set_next_handler(message.u_id, "buy-money/confirm") 85 | 86 | def confirm(bot, message): 87 | cancelled_message = bot.render_message("tx-cancelled") 88 | wait_message = bot.render_message("wait-accepting") 89 | old_tx_message = bot.render_message("old-tx") 90 | 91 | if message.u_id != bot.admin: 92 | menu_keyboard = bot.get_keyboard("menu-keyboard") 93 | else: 94 | menu_keyboard = bot.get_keyboard("admin-menu-keyboard") 95 | 96 | tx_id = bot.user_get(message.u_id, "buy-btc:tx-id") 97 | 98 | tx = main_wallet.get_tx(tx_id) 99 | if not tx: 100 | bot.telegram.send_message(message.u_id, old_tx_message, parse_mode="Markdown", reply_markup=menu_keyboard) 101 | return 102 | 103 | if bot.get_key("confirm-purchase", message.text) == "yes": 104 | bot.telegram.send_message(message.u_id, wait_message, parse_mode="Markdown", reply_markup=menu_keyboard) 105 | send_confirm_message_to_admin(bot, message, tx_id) 106 | elif bot.get_key("confirm-purchase", message.text) == "no": 107 | main_wallet.not_confirm_tx(tx_id) 108 | bot.user_delete(message.u_id, "buy-btc:tx-id") 109 | bot.telegram.send_message(message.u_id, cancelled_message, parse_mode="Markdown", reply_markup=menu_keyboard) 110 | else: 111 | bot.call_handler("buy-money/get-username", message) 112 | return 113 | 114 | def send_confirm_message_to_admin(bot, message, tx_id): 115 | tx = main_wallet.get_tx(tx_id) 116 | tx_info_message = bot.render_message("tx-info-for-admin", tx=tx) 117 | tx_confirm_keyboard = bot.get_inline_keyboard("confirm-tx", params={"tx-id":tx_id}) 118 | 119 | bot.telegram.send_message(bot.admin, tx_info_message, parse_mode="Markdown", reply_markup=tx_confirm_keyboard) 120 | 121 | 122 | def admin_confirm(bot, query): 123 | tx_id = query.data.split("/")[1] 124 | tx = main_wallet.get_tx(tx_id) 125 | 126 | tx_confirmed_message = bot.render_message("tx-confirmed") 127 | 128 | menu_keyboard = bot.get_keyboard("menu-keyboard") 129 | 130 | tx_info_message = bot.render_message("tx-info-for-admin", tx=tx, state=1) 131 | 132 | main_wallet.confirm_tx(tx_id) 133 | bot.telegram.send_message(tx["uid"], tx_confirmed_message, parse_mode="Markdown", reply_markup=menu_keyboard) 134 | bot.telegram.edit_message_text(chat_id=query.u_id, 135 | message_id=query.message.message_id, 136 | text=tx_info_message, 137 | parse_mode="Markdown") 138 | 139 | def admin_not_confirm(bot, query): 140 | tx_id = query.data.split("/")[1] 141 | tx = main_wallet.get_tx(tx_id) 142 | 143 | tx_not_confirmed_message = bot.render_message("tx-not-confirmed") 144 | 145 | 146 | menu_keyboard = bot.get_keyboard("menu-keyboard") 147 | 148 | tx_info_message = bot.render_message("tx-info-for-admin", tx=tx, state=2) 149 | 150 | main_wallet.not_confirm_tx(tx_id) 151 | bot.telegram.send_message(tx["uid"], tx_not_confirmed_message, parse_mode="Markdown", reply_markup=menu_keyboard) 152 | bot.telegram.edit_message_text(chat_id=query.u_id, 153 | message_id=query.message.message_id, 154 | text=tx_info_message, 155 | parse_mode="Markdown") -------------------------------------------------------------------------------- /modules/get_money.py: -------------------------------------------------------------------------------- 1 | from wallet import Wallet 2 | from main_wallet import MainWallet 3 | 4 | import qrcode 5 | import io 6 | import os 7 | 8 | main_wallet = None 9 | 10 | def init(bot): 11 | global main_wallet 12 | 13 | bot.handlers["get-money/start"] = start 14 | main_wallet = MainWallet(bot, os.environ.get("PRIVATE_KEY")) 15 | 16 | def start(bot, message): 17 | if message.u_id != bot.admin: 18 | wallet = Wallet(bot, message.u_id) 19 | else: 20 | wallet = main_wallet 21 | 22 | if message.u_id != bot.admin: 23 | menu_keyboard = bot.get_keyboard("menu-keyboard") 24 | else: 25 | menu_keyboard = bot.get_keyboard("admin-menu-keyboard") 26 | 27 | info = bot.render_message("get-money") 28 | btc_address = bot.render_message("btc-address", address=wallet.address) 29 | 30 | 31 | qr_image = qrcode.make(wallet.address) 32 | qr_bytes = io.BytesIO() 33 | qr_image.save(qr_bytes, format='PNG') 34 | 35 | qr_file = io.BytesIO(qr_bytes.getvalue()) 36 | 37 | bot.telegram.send_message(message.u_id, info, reply_markup = menu_keyboard, parse_mode="Markdown") 38 | bot.telegram.send_message(message.u_id, btc_address, reply_markup = menu_keyboard, parse_mode="Markdown") 39 | bot.telegram.send_photo(message.u_id, qr_file) -------------------------------------------------------------------------------- /modules/menu.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*-? 2 | 3 | import telebot 4 | from wallet import Wallet 5 | 6 | 7 | def init(bot): 8 | bot.handlers["main-menu"] = menu 9 | 10 | def menu(bot, message): 11 | menu_message = bot.render_message("menu-message") 12 | wallet_message = bot.render_message("start") 13 | 14 | if message.u_id != bot.admin: 15 | menu_keyboard = bot.get_keyboard("menu-keyboard") 16 | else: 17 | menu_keyboard = bot.get_keyboard("admin-menu-keyboard") 18 | 19 | 20 | if not bot.user_get(message.u_id, "wallet"): 21 | wallet = Wallet(bot, message.u_id) 22 | 23 | bot.telegram.send_message(message.u_id, wallet_message, reply_markup=menu_keyboard, parse_mode="Markdown") 24 | 25 | key = bot.get_key("menu-keyboard", message.text) 26 | if key: 27 | bot.call_handler(key, message, forward_flag=False) 28 | else: 29 | bot.telegram.send_message(message.u_id, menu_message, reply_markup=menu_keyboard) -------------------------------------------------------------------------------- /modules/send_money.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | 4 | from wallet import Wallet 5 | from main_wallet import MainWallet 6 | 7 | main_wallet = None 8 | 9 | def init(bot): 10 | global main_wallet 11 | 12 | bot.handlers["send-money/start"] = start 13 | bot.handlers["send-money/get-address"] = get_address 14 | bot.handlers["send-money/get-value"] = get_value 15 | bot.handlers["send-money/accpet-sending"] = accept_sending 16 | main_wallet = MainWallet(bot, os.environ.get("PRIVATE_KEY")) 17 | 18 | 19 | def start(bot, message): 20 | get_address_message = bot.render_message("get-address-to-send") 21 | back_to_menu_keyboard = bot.get_keyboard("back-to-menu") 22 | 23 | bot.set_next_handler(message.u_id, "send-money/get-address") 24 | 25 | bot.telegram.send_message(message.u_id, get_address_message, reply_markup=back_to_menu_keyboard, parse_mode="Markdown") 26 | 27 | def get_address(bot, message): 28 | if message.u_id != bot.admin: 29 | wallet = Wallet(bot, message.u_id) 30 | else: 31 | wallet = main_wallet 32 | 33 | incorrect_address_message = bot.render_message("incorrect-address-to-send") 34 | get_value_message = bot.render_message("get-value-to-send", comission=wallet.comission/10**8) 35 | 36 | if not message.forward: 37 | if not re.search("[0-9|a-z|A-Z]{30,34}", message.text): 38 | bot.telegram.send_message(message.u_id, incorrect_address_message, parse_mode="Markdown") 39 | bot.call_handler("send-money", message) 40 | return 41 | 42 | bot.user_set(message.u_id, "address-to-send", message.text) 43 | 44 | bot.set_next_handler(message.u_id, "send-money/get-value") 45 | bot.telegram.send_message(message.u_id, get_value_message, parse_mode="Markdown") 46 | 47 | def get_value(bot, message): 48 | if message.u_id != bot.admin: 49 | wallet = Wallet(bot, message.u_id) 50 | else: 51 | wallet = main_wallet 52 | 53 | incorrect_value_message = bot.render_message("incorrect-value-to-send") 54 | 55 | search_result = re.search("(?P[0-9]{1,}([,.][0-9]{1,}){0,1})", message.text) 56 | 57 | if not search_result: 58 | bot.telegram.send_message(message.u_id, incorrect_value_message, parse_mode="Markdown") 59 | bot.call_handler("send-money/get-address", message) 60 | return 61 | 62 | 63 | btc_value = search_result.group("value").replace(",", ".") 64 | bot.user_set(message.u_id, "btc-to-send", float(btc_value) * 10**8) 65 | address = bot.user_get(message.u_id, "address-to-send") 66 | 67 | accept_sending_message = bot.render_message("accept-sending", btc=btc_value, address=address) 68 | accept_keyboard = bot.get_keyboard("accept") 69 | 70 | bot.set_next_handler(message.u_id, "send-money/accpet-sending") 71 | bot.telegram.send_message(message.u_id, accept_sending_message, parse_mode="Markdown", reply_markup=accept_keyboard) 72 | 73 | def accept_sending(bot, message): 74 | if message.u_id != bot.admin: 75 | wallet = Wallet(bot, message.u_id) 76 | else: 77 | wallet = main_wallet 78 | 79 | ready_send_message = bot.render_message("ready-send") 80 | no_funds_message = bot.render_message("no-funds-to-send") 81 | push_error_message = bot.render_message("push-error") 82 | 83 | address = bot.user_get(message.u_id, "address-to-send") 84 | btc_value = bot.user_get(message.u_id, "btc-to-send") 85 | 86 | keyboard = bot.get_keyboard("menu-keyboard") 87 | if bot.get_key("accept", message.text) == "yes": 88 | code = wallet.send_money(btc_value, address) 89 | 90 | if code == 0: 91 | bot.telegram.send_message(message.u_id, ready_send_message, parse_mode="Markdown", reply_markup=keyboard) 92 | elif code == -1: 93 | bot.telegram.send_message(message.u_id, no_funds_message, parse_mode="Markdown", reply_markup=keyboard) 94 | else: 95 | bot.telegram.send_message(message.u_id, push_error_message, parse_mode="Markdown", reply_markup=keyboard) 96 | else: 97 | bot.call_handler("main-menu", message) 98 | return -------------------------------------------------------------------------------- /modules/wallet_info.py: -------------------------------------------------------------------------------- 1 | from wallet import Wallet 2 | from main_wallet import MainWallet 3 | 4 | import os 5 | 6 | main_wallet = None 7 | 8 | def init(bot): 9 | global main_wallet 10 | 11 | bot.handlers["wallet-info/start"] = start 12 | main_wallet = MainWallet(bot, os.environ.get("PRIVATE_KEY")) 13 | 14 | def start(bot, message): 15 | if message.u_id != bot.admin: 16 | wallet = Wallet(bot, message.u_id) 17 | else: 18 | wallet = main_wallet 19 | 20 | if message.u_id != bot.admin: 21 | menu_keyboard = bot.get_keyboard("menu-keyboard") 22 | else: 23 | menu_keyboard = bot.get_keyboard("admin-menu-keyboard") 24 | 25 | balance = wallet.get_balance()/10**8 26 | curecy = wallet.get_currency() * balance 27 | 28 | info = bot.render_message("wallet-info", balance=wallet.get_balance()/10**8, rub=curecy) 29 | bot.telegram.send_message(message.u_id, info, reply_markup = menu_keyboard, parse_mode="Markdown") 30 | 31 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytelegrambotapi 2 | flask 3 | jinja2 4 | redis 5 | gunicorn 6 | qrcode 7 | Pillow -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.5.2 -------------------------------------------------------------------------------- /wallet.py: -------------------------------------------------------------------------------- 1 | from bitcoin import * 2 | import requests 3 | 4 | class Wallet(): 5 | def __init__(self, bot, uid): 6 | wallet = bot.user_get(uid, "wallet") 7 | if not wallet: 8 | wallet = self._create_wallet(uid) 9 | bot.user_set(uid, "wallet", wallet) 10 | 11 | self.address = wallet["address"] 12 | self.public = wallet["public"] 13 | self.private = wallet["private"] 14 | self.comission = bot.const["comission"] 15 | 16 | def _create_wallet(self, uid): 17 | key = random_key() 18 | private = sha256(key) 19 | public = privtopub(private) 20 | address = pubtoaddr(public) 21 | 22 | wallet = {"private": private, 23 | "public": public, 24 | "address": address} 25 | 26 | return wallet 27 | 28 | 29 | def get_currency(self): 30 | res = requests.get("https://blockchain.info/ticker").json() 31 | 32 | currency = res["RUB"]["buy"] 33 | return currency 34 | 35 | def get_balance(self): 36 | res = requests.get("https://blockchain.info/balance?active=%s" % self.address).json() 37 | 38 | balance = res[self.address]["final_balance"] 39 | return balance 40 | 41 | def send_money(self, value, address): 42 | balance = self.get_balance() 43 | 44 | if balance - (value + self.comission)<0: 45 | return -1 46 | 47 | try: 48 | h = bci_unspent(self.address) 49 | except Exception as ex: 50 | return -1 51 | 52 | 53 | 54 | if int(balance - (value + self.comission)) == 0: 55 | outs = [{'value': int(value), 'address': address}] 56 | else: 57 | outs = [{'value': int(value), 'address': address}, 58 | {'value': int(balance - (value + self.comission)), 'address': self.address}] 59 | 60 | try: 61 | tx = mktx(h, outs) 62 | for i in range(len(h)): 63 | tx = sign(tx, i, self.private) 64 | 65 | bci_pushtx(tx) 66 | except Exception as ex: 67 | return -2 68 | 69 | return 0 70 | 71 | --------------------------------------------------------------------------------