You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
5.4 KiB
163 lines
5.4 KiB
import asyncio |
|
|
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject |
|
|
|
from electrum import mnemonic |
|
from electrum import keystore |
|
from electrum.i18n import _ |
|
from electrum.bip32 import is_bip32_derivation, xpub_type |
|
from electrum.logging import get_logger |
|
from electrum.slip39 import decode_mnemonic, Slip39Error |
|
from electrum.util import parse_URI, create_bip21_uri, InvalidBitcoinURI, get_asyncio_loop |
|
from electrum.transaction import tx_from_any |
|
|
|
from .qetypes import QEAmount |
|
|
|
class QEBitcoin(QObject): |
|
def __init__(self, config, parent=None): |
|
super().__init__(parent) |
|
self.config = config |
|
|
|
_logger = get_logger(__name__) |
|
|
|
generatedSeedChanged = pyqtSignal() |
|
generatedSeed = '' |
|
|
|
seedValidChanged = pyqtSignal() |
|
seedValid = False |
|
|
|
seedTypeChanged = pyqtSignal() |
|
seedType = '' |
|
|
|
validationMessageChanged = pyqtSignal() |
|
validationMessage = '' |
|
|
|
@pyqtProperty('QString', notify=generatedSeedChanged) |
|
def generated_seed(self): |
|
return self.generatedSeed |
|
|
|
@pyqtProperty(bool, notify=seedValidChanged) |
|
def seed_valid(self): |
|
return self.seedValid |
|
|
|
@pyqtProperty('QString', notify=seedTypeChanged) |
|
def seed_type(self): |
|
return self.seedType |
|
|
|
@pyqtProperty('QString', notify=validationMessageChanged) |
|
def validation_message(self): |
|
return self.validationMessage |
|
|
|
@validation_message.setter |
|
def validation_message(self, msg): |
|
if self.validationMessage != msg: |
|
self.validationMessage = msg |
|
self.validationMessageChanged.emit() |
|
|
|
@pyqtSlot() |
|
@pyqtSlot(str) |
|
@pyqtSlot(str,str) |
|
def generate_seed(self, seed_type='segwit', language='en'): |
|
self._logger.debug('generating seed of type ' + str(seed_type)) |
|
|
|
async def co_gen_seed(seed_type, language): |
|
self.generatedSeed = mnemonic.Mnemonic(language).make_seed(seed_type=seed_type) |
|
self._logger.debug('seed generated') |
|
self.generatedSeedChanged.emit() |
|
|
|
asyncio.run_coroutine_threadsafe(co_gen_seed(seed_type, language), get_asyncio_loop()) |
|
|
|
@pyqtSlot(str) |
|
@pyqtSlot(str,bool,bool) |
|
@pyqtSlot(str,bool,bool,str,str,str) |
|
def verify_seed(self, seed, bip39=False, slip39=False, wallet_type='standard', language='en'): |
|
self._logger.debug('bip39 ' + str(bip39)) |
|
self._logger.debug('slip39 ' + str(slip39)) |
|
|
|
seed_type = '' |
|
seed_valid = False |
|
self.validationMessage = '' |
|
|
|
if not (bip39 or slip39): |
|
seed_type = mnemonic.seed_type(seed) |
|
if seed_type != '': |
|
seed_valid = True |
|
elif bip39: |
|
is_checksum, is_wordlist = keystore.bip39_is_checksum_valid(seed) |
|
status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist' |
|
self.validationMessage = 'BIP39 (%s)' % status |
|
|
|
if is_checksum: |
|
seed_type = 'bip39' |
|
seed_valid = True |
|
|
|
elif slip39: # TODO: incomplete impl, this code only validates a single share. |
|
try: |
|
share = decode_mnemonic(seed) |
|
seed_type = 'slip39' |
|
self.validationMessage = 'SLIP39: share #%d in %dof%d scheme' % (share.group_index, share.group_threshold, share.group_count) |
|
except Slip39Error as e: |
|
self.validationMessage = 'SLIP39: %s' % str(e) |
|
seed_valid = False # for now |
|
|
|
# cosigning seed |
|
if wallet_type != 'standard' and seed_type not in ['standard', 'segwit']: |
|
seed_type = '' |
|
seed_valid = False |
|
|
|
self.seedType = seed_type |
|
self.seedTypeChanged.emit() |
|
|
|
if self.seedValid != seed_valid: |
|
self.seedValid = seed_valid |
|
self.seedValidChanged.emit() |
|
|
|
self._logger.debug('seed verified: ' + str(seed_valid)) |
|
|
|
@pyqtSlot(str, result=bool) |
|
@pyqtSlot(str, str, result=bool) |
|
def verify_master_key(self, key, wallet_type='standard'): |
|
self.validationMessage = '' |
|
if not keystore.is_master_key(key): |
|
self.validationMessage = _('Not a master key') |
|
return False |
|
|
|
if wallet_type == 'standard': |
|
# validation message? |
|
k = keystore.from_master_key(key) |
|
has_xpub = isinstance(k, keystore.Xpub) |
|
assert has_xpub |
|
t1 = xpub_type(k.xpub) |
|
if t1 not in ['standard', 'p2wpkh', 'p2wpkh-p2sh']: |
|
self.validationMessage = '%s: %s' % (_('Wrong key type'), t1) |
|
return False |
|
return True |
|
return False |
|
|
|
@pyqtSlot(str, result=bool) |
|
def verify_derivation_path(self, path): |
|
return is_bip32_derivation(path) |
|
|
|
@pyqtSlot(str, result='QVariantMap') |
|
def parse_uri(self, uri: str) -> dict: |
|
try: |
|
return parse_URI(uri) |
|
except InvalidBitcoinURI as e: |
|
return { 'error': str(e) } |
|
|
|
@pyqtSlot(str, QEAmount, str, int, int, result=str) |
|
def create_bip21_uri(self, address, satoshis, message, timestamp, expiry): |
|
extra_params = {} |
|
if expiry: |
|
extra_params['time'] = str(timestamp) |
|
extra_params['exp'] = str(expiry) |
|
|
|
return create_bip21_uri(address, satoshis.satsInt, message, extra_query_params=extra_params) |
|
|
|
@pyqtSlot(str, result=bool) |
|
def verify_raw_tx(self, rawtx): |
|
try: |
|
tx_from_any(rawtx) |
|
return True |
|
except: |
|
return False
|
|
|