1
0

integrate PSBT support natively. WIP

This commit is contained in:
SomberNight
2019-10-23 17:09:41 +02:00
parent 6d12ebabbb
commit bafe8a2fff
61 changed files with 3405 additions and 3310 deletions

View File

@@ -14,20 +14,21 @@ import re
import struct
import sys
import time
import copy
from electrum.crypto import sha256d, EncodeAES_base64, EncodeAES_bytes, DecodeAES_bytes, hmac_oneshot
from electrum.bitcoin import (TYPE_ADDRESS, push_script, var_int, public_key_to_p2pkh,
is_address)
from electrum.bip32 import BIP32Node
from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath
from electrum import ecc
from electrum.ecc import msg_magic
from electrum.wallet import Standard_Wallet
from electrum import constants
from electrum.transaction import Transaction
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput
from electrum.i18n import _
from electrum.keystore import Hardware_KeyStore
from ..hw_wallet import HW_PluginBase
from electrum.util import to_string, UserCancelled, UserFacingException
from electrum.util import to_string, UserCancelled, UserFacingException, bfh
from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
from electrum.network import Network
from electrum.logging import get_logger
@@ -449,21 +450,13 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
hw_type = 'digitalbitbox'
device = 'DigitalBitbox'
plugin: 'DigitalBitboxPlugin'
def __init__(self, d):
Hardware_KeyStore.__init__(self, d)
self.force_watching_only = False
self.maxInputs = 14 # maximum inputs per single sign command
def get_derivation(self):
return str(self.derivation)
def is_p2pkh(self):
return self.derivation.startswith("m/44'/")
def give_error(self, message, clear_client = False):
if clear_client:
self.client = None
@@ -478,7 +471,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
sig = None
try:
message = message.encode('utf8')
inputPath = self.get_derivation() + "/%d/%d" % sequence
inputPath = self.get_derivation_prefix() + "/%d/%d" % sequence
msg_hash = sha256d(msg_magic(message))
inputHash = to_hexstr(msg_hash)
hasharray = []
@@ -540,58 +533,50 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
try:
p2pkhTransaction = True
derivations = self.get_tx_derivations(tx)
inputhasharray = []
hasharray = []
pubkeyarray = []
# Build hasharray from inputs
for i, txin in enumerate(tx.inputs()):
if txin['type'] == 'coinbase':
if txin.is_coinbase():
self.give_error("Coinbase not supported") # should never happen
if txin['type'] != 'p2pkh':
if txin.script_type != 'p2pkh':
p2pkhTransaction = False
for x_pubkey in txin['x_pubkeys']:
if x_pubkey in derivations:
index = derivations.get(x_pubkey)
inputPath = "%s/%d/%d" % (self.get_derivation(), index[0], index[1])
inputHash = sha256d(binascii.unhexlify(tx.serialize_preimage(i)))
hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}
hasharray.append(hasharray_i)
inputhasharray.append(inputHash)
break
else:
self.give_error("No matching x_key for sign_transaction") # should never happen
my_pubkey, inputPath = self.find_my_pubkey_in_txinout(txin)
if not inputPath:
self.give_error("No matching pubkey for sign_transaction") # should never happen
inputPath = convert_bip32_intpath_to_strpath(inputPath)
inputHash = sha256d(bfh(tx.serialize_preimage(i)))
hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}
hasharray.append(hasharray_i)
inputhasharray.append(inputHash)
# Build pubkeyarray from outputs
for o in tx.outputs():
assert o.type == TYPE_ADDRESS
info = tx.output_info.get(o.address)
if info is not None:
if info.is_change:
index = info.address_index
changePath = self.get_derivation() + "/%d/%d" % index
changePubkey = self.derive_pubkey(index[0], index[1])
pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath}
pubkeyarray.append(pubkeyarray_i)
for txout in tx.outputs():
assert txout.address
if txout.is_change:
changePubkey, changePath = self.find_my_pubkey_in_txinout(txout)
assert changePath
changePath = convert_bip32_intpath_to_strpath(changePath)
changePubkey = changePubkey.hex()
pubkeyarray_i = {'pubkey': changePubkey, 'keypath': changePath}
pubkeyarray.append(pubkeyarray_i)
# Special serialization of the unsigned transaction for
# the mobile verification app.
# At the moment, verification only works for p2pkh transactions.
if p2pkhTransaction:
class CustomTXSerialization(Transaction):
@classmethod
def input_script(self, txin, estimate_size=False):
if txin['type'] == 'p2pkh':
return Transaction.get_preimage_script(txin)
if txin['type'] == 'p2sh':
# Multisig verification has partial support, but is disabled. This is the
# expected serialization though, so we leave it here until we activate it.
return '00' + push_script(Transaction.get_preimage_script(txin))
raise Exception("unsupported type %s" % txin['type'])
tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize_to_network()
tx_copy = copy.deepcopy(tx)
# monkey-patch method of tx_copy instance to change serialization
def input_script(self, txin: PartialTxInput, *, estimate_size=False):
if txin.script_type == 'p2pkh':
return Transaction.get_preimage_script(txin)
raise Exception("unsupported type %s" % txin.script_type)
tx_copy.input_script = input_script.__get__(tx_copy, PartialTransaction)
tx_dbb_serialized = tx_copy.serialize_to_network()
else:
# We only need this for the signing echo / verification.
tx_dbb_serialized = None
@@ -656,12 +641,9 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
if len(dbb_signatures) != len(tx.inputs()):
raise Exception("Incorrect number of transactions signed.") # Should never occur
for i, txin in enumerate(tx.inputs()):
num = txin['num_sig']
for pubkey in txin['pubkeys']:
signatures = list(filter(None, txin['signatures']))
if len(signatures) == num:
break # txin is complete
ii = txin['pubkeys'].index(pubkey)
for pubkey_bytes in txin.pubkeys:
if txin.is_complete():
break
signed = dbb_signatures[i]
if 'recid' in signed:
# firmware > v2.1.1
@@ -673,20 +655,19 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
elif 'pubkey' in signed:
# firmware <= v2.1.1
pk = signed['pubkey']
if pk != pubkey:
if pk != pubkey_bytes.hex():
continue
sig_r = int(signed['sig'][:64], 16)
sig_s = int(signed['sig'][64:], 16)
sig = ecc.der_sig_from_r_and_s(sig_r, sig_s)
sig = to_hexstr(sig) + '01'
tx.add_signature_to_txin(i, ii, sig)
tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig)
except UserCancelled:
raise
except BaseException as e:
self.give_error(e, True)
else:
_logger.info("Transaction is_complete {tx.is_complete()}")
tx.raw = tx.serialize()
_logger.info(f"Transaction is_complete {tx.is_complete()}")
class DigitalBitboxPlugin(HW_PluginBase):
@@ -788,11 +769,11 @@ class DigitalBitboxPlugin(HW_PluginBase):
if not self.is_mobile_paired():
keystore.handler.show_error(_('This function is only available after pairing your {} with a mobile device.').format(self.device))
return
if not keystore.is_p2pkh():
if wallet.get_txin_type(address) != 'p2pkh':
keystore.handler.show_error(_('This function is only available for p2pkh keystores when using {}.').format(self.device))
return
change, index = wallet.get_address_index(address)
keypath = '%s/%d/%d' % (keystore.derivation, change, index)
keypath = '%s/%d/%d' % (keystore.get_derivation_prefix(), change, index)
xpub = self.get_client(keystore)._get_xpub(keypath)
verify_request_payload = {
"type": 'p2pkh',

View File

@@ -2,7 +2,7 @@ from functools import partial
from electrum.i18n import _
from electrum.plugin import hook
from electrum.wallet import Standard_Wallet
from electrum.wallet import Standard_Wallet, Abstract_Wallet
from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
from ..hw_wallet.plugin import only_hook_if_libraries_available
@@ -18,7 +18,7 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase):
@only_hook_if_libraries_available
@hook
def receive_menu(self, menu, addrs, wallet):
def receive_menu(self, menu, addrs, wallet: Abstract_Wallet):
if type(wallet) is not Standard_Wallet:
return
@@ -29,12 +29,12 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase):
if not self.is_mobile_paired():
return
if not keystore.is_p2pkh():
return
if len(addrs) == 1:
addr = addrs[0]
if wallet.get_txin_type(addr) != 'p2pkh':
return
def show_address():
keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
keystore.thread.add(partial(self.show_address, wallet, addr, keystore))
menu.addAction(_("Show on {}").format(self.device), show_address)