Действия происходят в далёком — год назад
В этой статье хочу рассказать, как можно из обычных вещей сделать нечто большее и новое используя python, qt и bitcoin библиотеки.
С чего всё начиналось
В то время мне необходимо было сделать приём платежей в криптовалюте и в частности Bitcoin, для одного сервиса на заказ, дабы пользователи могли пополнять личный счёт, оплачивать товары и при необходимости выводить их.
Как работали такие платёжные системы я тогда не знал и понятия не имел как реализовать такую вещь, не говоря уже о знаниях о том из чего состоит блок, что такое цепочка иерархии, приватный ключ и так далее.

Мне удалось найти пример того как реализована биткоин оплата в боте телеграм, так же на python и так же из одной статьи Habr.
Её я и взял за основу и потратил не мало времени что бы изучить базу всего строения.
Как работает блокчейн здесь описано не будет, таких статьей не мало. здесь будет то как я нашёл идею от простой задачи.
И так, вот как выглядит и работает платёжный приём на Bitcoin он же и получение средств в нашем клиенте на qt
для работы нам понадобиться библиотеки которые всё делают за нас ну или на половину…
в моём случае используется pywallet но есть так же bitcoiblib, py-hd-wallet, hdwallet и другие неплохие либы, у каждого есть свои недостатки и плюсы, наиболее хорошо показали себя в работе hdwallet и pywallet, для создания иерархически детерминированного кошелька тобишь дочернего адреса для вашего кошелька.
# Индекс адреса, индекс определяет глубино адреса, так как на одной seed фразе может быть несколько адресов index = 0 # стартуем от нулевого индекса # наша seed фраза или мнемоническая фраза, стандартная базовая абстракция при построении адреса, на ней всё осваивается seed = 'one two one two one two ...' # далее генерируем мастер ключ на основе мненоники master_key = wallet.HDPrivateKey.master_key_from_mnemonic(seed) # и далее по путям генерируем адрес стандарта BIP 44 и проходимся по иирархии от ключа до паблик и приват ключа root_keys = wallet.HDKey.from_path(master_key, "m/44'/0'/0'/0")[-1].public_key.to_b58check() xpublic_key = (root_keys) # Получаем наш дочерний адрес address = Wallet.deserialize(xpublic_key, network='BTC').get_child(index,is_prime=False).to_address() rootkeys_wif = wallet.HDKey.from_path(master_key, f"m/44'/0'/0'/0/{index}")[-1] # <- это для того что бы генерировать каждый раз новый адрес xprivatekey = rootkeys_wif.to_b58check() # Wallet Import Format он служит для осуществления транзакций wif = Wallet.deserialize(xprivatekey, network='BTC').export_to_wif()
вот так выглядт функция создания всех необходимых сущностей для дальнейшей работы
такая же по логике функция с библиотекой hdwallet.
index = [0, 1, 2] bip44_btc = BIP44HDWallet(cryptocurrency=BitcoinMainnet) bip44_btc.from_mnemonic(mnemonic=seedphrase, language="english") bip44_btc.clean_derivation() bip44_derivation: BIP44Derivation = BIP44Derivation(cryptocurrency=BitcoinMainnet, account=0, change=False,address=index[0]) bip44_btc.from_path(path=bip44_derivation) wif_0_44 = bip44_btc.wif() key = Key(wif=wif_0_44) balance_1_for_btc = key.get_balance('usd') addressinput_0_btc = bip44_btc.address()
С иирархией понятно. Идея
После реализации платёжной системы с приёмом биткоин, мне тут же пришла банальная и в то же время интересная идея, что если просто платёжку обернуть в приложение и сделать из этого Bitcoin Wallet. Да ещё и с контролем всех средств пользователей
Всё относительно просто, для интерфейса Qt, заворачиваем наш код в логику и готово.
Кто не понял
Имея нашу функцию мы на основе нашей же мнемонической фразы генерируем каждый раз новые адреса, ключи и прочее, с помощью которых мы можем получать и отправлять средства, но средства как раз будут храниться у нас на кошельке, в данном случае мы каким то образом централизуем систему и выступаем в роли банка.
Давай код!
Начинаем по стандарту PyQt5 с интерфейса


После интерфейса начнём его описывать.
import sqlite3 import bit import clipboard import qrcode as qrcode import requests from PyQt5 import QtWidgets from PyQt5.QtGui import * from PyQt5.QtWidgets import QDialog, QApplication, QMainWindow, QMessageBox from PyQt5.uic import loadUi from bs4 import BeautifulSoup from pywallet import wallet from pywallet.utils import *
Импорт нужных нам библиотек.
class LoginScreen(QMainWindow): def __init__(self): super(LoginScreen, self).__init__() loadUi("GUI/atom.ui",self) self.passwordline.setEchoMode(QtWidgets.QLineEdit.Password) self.registrnow.clicked.connect(self.gotoReg) self.Loginone.clicked.connect(self.loginfunction) def gotoReg(self): Reg = RegScreen() widget.addWidget(Reg) widget.setCurrentIndex(widget.currentIndex()+1) def loginfunction(self): password = self.passwordline.text() if len(password) == 0: self.error.setText("Please input all fields.") else: db = sqlite3.connect("wallet.db") curs = db.cursor() curs.execute(f"SELECT * FROM users WHERE Password = '{password}'") if not curs.fetchone(): self.error.setText("Incorrect password") else: fillprofile = Profile() widget.addWidget(fillprofile) widget.setCurrentIndex(widget.currentIndex() + 1)
класс входа в приложение, в конструкторе класса подгружаем наш ui и определяем кнопки
далее обычная минимальная функции проверки пароля и входа в систему с использованием Sqlite3.
В идеале
Конечно для продакшена этот проект не подходит, реализация была как пет-проект и в идеале бы конечно ui так не подгружать для каждого отдельный, можно было сделать stackedWidget и по нему переходить, и можно было бы обойтись в таком случае одним классом или двумя, подключить Postgresql какой нибудь, да и вообще софт в стол лучше на плюсах или шарпе.
class Profile(QDialog): def __init__(self): super(Profile, self).__init__() loadUi("GUI/main.ui", self) self.btcpricee() self.balanceuser() self.logout.clicked.connect(self.loginout) self.receive.clicked.connect(self.receivebtcaddress) self.receive_2.clicked.connect(self.receivebtcaddress) self.walletbutton.clicked.connect(self.walletent) self.sendd.clicked.connect(self.sendbtc) self.sendd_2.clicked.connect(self.sendbtc) self.settings.clicked.connect(self.settinges) def balanceuser(self): try: db = sqlite3.connect("wallet.db") curs = db.cursor() sss = "SELECT btc_address FROM users" curs.execute(sss) adres = curs.fetchone() url = f'https://www.blockchain.com/btc/address/{adres[0]}' headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'} Response = requests.get(url, headers=headers) wallet = BeautifulSoup(Response.content, 'html.parser') convert = wallet.findAll("span", {"class": "sc-16b9dsl-1","class": "ZwupP", "class": "u3ufsr-0", "class": "eQTRKC"}) rx = convert[6].text self.balance.setText(str(rx)) response = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json') data = response.json() x = data["bpi"]["USD"]["rate_float"] xx = rx.rstrip('BTC') us = (x * float(xx)) self.usdbalance.setText(str(us)) except: self.balance.setText(str('Loading...')) self.usdbalance.setText(str('---'))
функция индексирования баланса, достаём из бд адрес и просто парсим его и далее через setText выводим
class RegScreen(QDialog): def __init__(self): super(RegScreen, self).__init__() loadUi("GUI/reg.ui",self) self.loginperehod.clicked.connect(self.gotoLogin) self.signupreg.clicked.connect(self.registrationfunction) def gotoLogin(self): Log = LoginScreen() widget.addWidget(Log) widget.setCurrentIndex(widget.currentIndex() + 1) def registrationfunction(self): password_reg = self.passwordreg.text() repl_password = self.relacepasswordreg.text() if repl_password != password_reg: self.errorreg1_2.setText("Password does not match") else: if len(password_reg) == 0: self.errorreg1.setText("To register, you need to fill in all the fields") elif len(password_reg) < 8: self.passerror.setText('Password cannot be less than 8 characters') else: db = sqlite3.connect('wallet.db') curs = db.cursor() curs.execute('''CREATE TABLE IF NOT EXISTS users ( Password TEXT, balance INTEGER, btc_address, wif TEXT, btc_send TEXT )''') db.commit() curs.execute("SELECT Password FROM users") if curs.fetchone() is None: index = 0 seed = '' master_key = wallet.HDPrivateKey.master_key_from_mnemonic(seed) root_keys = wallet.HDKey.from_path(master_key, "m/44'/0'/0'/0")[-1].public_key.to_b58check() xpublic_key = (root_keys) address = Wallet.deserialize(xpublic_key, network='BTC').get_child(index,is_prime=False).to_address() rootkeys_wif = wallet.HDKey.from_path(master_key, f"m/44'/0'/0'/0/{index}")[-1] xprivatekey = rootkeys_wif.to_b58check() wif = Wallet.deserialize(xprivatekey, network='BTC').export_to_wif() curs.execute("INSERT INTO users VALUES (?, ?, ?, ?, ?)", (password_reg, 0, address, wif, 0)) img = qrcode.make(address) img.save('GUI/qr.png') db.commit() self.successreg.setText("You have successfully registered!") else: error = QMessageBox() error.setWindowTitle('Big request to create an account') error.setText('Sorry, you cannot create another account.') error.setIcon(QMessageBox.Warning) error.setDefaultButton(QMessageBox.Ok) error.exec_() exit()
Класс регистрации с его функциями, как видим при регистрации мы используем ту самую функцию которую я описывал в начале статьи, далее мы заносим наши данные в бд.
Как видите всё достаточно просто, мы взяли биткоин функцию и засунули её в Qt и так же как и с платёжкой мы может получать и отображать баланс и выводить за счёт Wallet Import Format или сокр.WIF и по разному манипулировать монетами ибо все они хранятся на нашем кошельке через нашу сид фразу, храним всё в бд, а управление через простой интерфейс. можно было бы сделать мультивалютность и использовать web3 тогда например генерация данных для ETH выглядела бы так
index = [0, 1, 2] bip44 = BIP44HDWallet(cryptocurrency=EthereumMainnet) bip44.from_mnemonic(mnemonic=seedphrase, language="english") bip44.clean_derivation() bip44_derivation: BIP44Derivation = BIP44Derivation(cryptocurrency=EthereumMainnet, account=0, change=False,address=index[0]) bip44.from_path(path=bip44_derivation) private_key_0 = '0x' + bip44.private_key() addressinput_0 = bip44.address()
Ну и на этом всё.
Что можно вынести из данной статьи, это то что даже самая невзрачная вещь может послужить вам основой большого и крутого проекта. А так же то что нельзя доверять ни одному клиенту криптокошелька не видев его код, быть может именно он так и устроен, под децентролизавной системой криптовалют может скрываться мнимая централизация с утечкой и хранением ваших средств у какого нибудь индуса 🙂
ссылка на оригинал статьи https://habr.com/ru/post/700888/
Добавить комментарий