diff --git a/plugins/digitalbitbox/digitalbitbox.py b/plugins/digitalbitbox/digitalbitbox.py index 97e5b7d41..24454fe2f 100644 --- a/plugins/digitalbitbox/digitalbitbox.py +++ b/plugins/digitalbitbox/digitalbitbox.py @@ -6,6 +6,7 @@ try: import electrum from electrum.bitcoin import TYPE_ADDRESS, push_script, var_int, msg_magic, Hash, verify_message, pubkey_from_signature, point_to_ser, public_key_to_p2pkh, EncodeAES, DecodeAES, MyVerifyingKey + from electrum.bitcoin import serialize_xpub, deserialize_xpub from electrum.transaction import Transaction from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore @@ -85,10 +86,17 @@ class DigitalBitbox_Client(): def get_xpub(self, bip32_path, xtype): - assert xtype == 'standard' + assert xtype in ('standard', 'p2wpkh-p2sh') reply = self._get_xpub(bip32_path) if reply: - return reply['xpub'] + xpub = reply['xpub'] + # Change type of xpub to the requested type. The firmware + # only ever returns the standard type, but it is agnostic + # to the type when signing. + if xtype != 'standard': + _, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub) + xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number) + return xpub else: raise BaseException('no reply') @@ -399,6 +407,10 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): 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 @@ -472,7 +484,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): return try: - p2shTransaction = False + p2pkhTransaction = True derivations = self.get_tx_derivations(tx) inputhasharray = [] hasharray = [] @@ -483,8 +495,8 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): if txin['type'] == 'coinbase': self.give_error("Coinbase not supported") # should never happen - if txin['type'] in ['p2sh']: - p2shTransaction = True + if txin['type'] != 'p2pkh': + p2pkhTransaction = False for x_pubkey in txin['x_pubkeys']: if x_pubkey in derivations: @@ -498,12 +510,6 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): else: self.give_error("No matching x_key for sign_transaction") # should never happen - # Sanity check - if p2shTransaction: - for txinput in tx.inputs(): - if txinput['type'] != 'p2sh': - self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen - # Build pubkeyarray from outputs for _type, address, amount in tx.outputs(): assert _type == TYPE_ADDRESS @@ -517,15 +523,22 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): # Special serialization of the unsigned transaction for # the mobile verification app. - 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': - return '00' + push_script(Transaction.get_preimage_script(txin)) - raise Exception("unsupported type %s" % txin['type']) - tx_dbb_serialized = CustomTXSerialization(tx.serialize()).serialize() + # 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() + else: + # We only need this for the signing echo / verification. + tx_dbb_serialized = None # Build sign command dbb_signatures = [] @@ -533,8 +546,15 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): for step in range(int(steps)): hashes = hasharray[step * self.maxInputs : (step + 1) * self.maxInputs] - msg = ('{"sign": {"meta":"%s", "data":%s, "checkpub":%s} }' % \ - (to_hexstr(Hash(tx_dbb_serialized)), json.dumps(hashes), json.dumps(pubkeyarray))).encode('utf8') + msg = { + "sign": { + "data": hashes, + "checkpub": pubkeyarray, + }, + } + if tx_dbb_serialized is not None: + msg["sign"]["meta"] = to_hexstr(Hash(tx_dbb_serialized)) + msg = json.dumps(msg).encode('ascii') dbb_client = self.plugin.get_client(self) if not dbb_client.is_paired(): @@ -547,8 +567,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): if 'echo' not in reply: raise Exception("Could not sign transaction.") - # multisig verification not working correctly yet - if self.plugin.is_mobile_paired() and not p2shTransaction: + if self.plugin.is_mobile_paired() and tx_dbb_serialized is not None: reply['tx'] = tx_dbb_serialized self.plugin.comserver_post_notification(reply) diff --git a/plugins/digitalbitbox/qt.py b/plugins/digitalbitbox/qt.py index d2dc63dc7..389dcdb5f 100644 --- a/plugins/digitalbitbox/qt.py +++ b/plugins/digitalbitbox/qt.py @@ -25,6 +25,9 @@ class Plugin(DigitalBitboxPlugin, QtPluginBase): if not self.is_mobile_paired(): return + if not keystore.is_p2pkh(): + return + if len(addrs) == 1: def show_address(): change, index = wallet.get_address_index(addrs[0])