├── .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 |
--------------------------------------------------------------------------------