diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index f1025f472..df05fdd0b 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -626,6 +626,9 @@ def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4, return EncodeBase58Check(xpub) +class InvalidMasterKeyVersionBytes(BitcoinException): pass + + def deserialize_xkey(xkey, prv, *, net=None): if net is None: net = constants.net @@ -640,8 +643,8 @@ def deserialize_xkey(xkey, prv, *, net=None): header = int('0x' + bh2u(xkey[0:4]), 16) headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS if header not in headers.values(): - raise BitcoinException('Invalid extended key format: {}' - .format(hex(header))) + raise InvalidMasterKeyVersionBytes('Invalid extended key format: {}' + .format(hex(header))) xtype = list(headers.keys())[list(headers.values()).index(header)] n = 33 if prv else 32 K_or_k = xkey[13+n:] diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py index fd307de2a..5c99991d5 100644 --- a/electrum/plugins/coldcard/coldcard.py +++ b/electrum/plugins/coldcard/coldcard.py @@ -8,10 +8,12 @@ import os, sys, time, io import traceback from electrum import bitcoin +from electrum.bitcoin import serialize_xpub, deserialize_xpub, InvalidMasterKeyVersionBytes +from electrum import constants from electrum.bitcoin import TYPE_ADDRESS, int_to_hex from electrum.i18n import _ from electrum.plugin import BasePlugin, Device -from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey +from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub from electrum.transaction import Transaction from electrum.wallet import Standard_Wallet from electrum.crypto import hash_160 @@ -85,7 +87,8 @@ class CKCCClient: # Challenge: I haven't found anywhere that defines a base class for this 'client', # nor an API (interface) to be met. Winging it. Gets called from lib/plugins.py mostly? - def __init__(self, handler, dev_path, is_simulator=False): + def __init__(self, plugin, handler, dev_path, is_simulator=False): + self.device = plugin.device self.handler = handler # if we know what the (xfp, xpub) "should be" then track it here @@ -183,9 +186,19 @@ class CKCCClient: return False def get_xpub(self, bip32_path, xtype): - # TODO: xtype? .. might not be able to support anything but classic p2pkh? + assert xtype in ColdcardPlugin.SUPPORTED_XTYPES print_error('[coldcard]', 'Derive xtype = %r' % xtype) - return self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000) + xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000) + # TODO handle timeout? + # change type of xpub to the requested type + try: + __, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub) + except InvalidMasterKeyVersionBytes: + raise Exception(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.') + .format(self.device)) from None + if xtype != 'standard': + xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number) + return xpub def ping_check(self): # check connection is working @@ -435,7 +448,11 @@ class Coldcard_KeyStore(Hardware_KeyStore): # global section: just the unsigned txn - unsigned = bfh(tx.serialize_to_network(blank_scripts=True)) + class CustomTXSerialization(Transaction): + @classmethod + def input_script(cls, txin, estimate_size=False): + return '' + unsigned = bfh(CustomTXSerialization(tx.serialize()).serialize_to_network(witness=False)) write_kv(PSBT_GLOBAL_UNSIGNED_TX, unsigned) # end globals section @@ -457,13 +474,13 @@ class Coldcard_KeyStore(Hardware_KeyStore): out_fd.write(b'\x00') # outputs section - for _type, address, amount in tx.outputs(): + for o in tx.outputs(): # can be empty, but must be present, and helpful to show change inputs # wallet.add_hw_info() adds some data about change outputs into tx.output_info - if address in tx.output_info: + if o.address in tx.output_info: # this address "is_mine" but might not be change (I like to sent to myself) - # - index, xpubs, _multisig = tx.output_info.get(address) + output_info = tx.output_info.get(o.address) + index, xpubs = output_info.address_index, output_info.sorted_xpubs if index[0] == 1 and len(index) == 2: # it is a change output (based on our standard derivation path) @@ -581,7 +598,6 @@ class ColdcardPlugin(HW_PluginBase): SUPPORTED_XTYPES = ('standard', 'p2wpkh') def __init__(self, parent, config, name): - self.segwit = config.get("segwit") HW_PluginBase.__init__(self, parent, config, name) if self.libraries_available: @@ -608,11 +624,11 @@ class ColdcardPlugin(HW_PluginBase): # Not sure why not we aren't just given a HID library handle, but # the 'path' is unabiguous, so we'll use that. try: - rv = CKCCClient(handler, device.path, + rv = CKCCClient(self, handler, device.path, is_simulator=(device.product_key[1] == CKCC_SIMULATED_PID)) return rv except: - print_error('[coldcard]', 'late failure connecting to device?') + self.print_error('late failure connecting to device?') return None def setup_device(self, device_info, wizard, purpose): diff --git a/electrum/transaction.py b/electrum/transaction.py index 119090299..fe0569853 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -1030,12 +1030,12 @@ class Transaction: else: return network_ser - def serialize_to_network(self, estimate_size=False, witness=True, blank_scripts=False): + def serialize_to_network(self, estimate_size=False, witness=True): nVersion = int_to_hex(self.version, 4) nLocktime = int_to_hex(self.locktime, 4) inputs = self.inputs() outputs = self.outputs() - txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size) if not blank_scripts else '') for txin in inputs) + txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs) txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) use_segwit_ser_for_estimate_size = estimate_size and self.is_segwit(guess_for_address=True) use_segwit_ser_for_actual_use = not estimate_size and \