Ejemplo n.º 1
0
def get_device(optional=False):
    # Open connection to Coldcard as a context with auto-close
    try:
        device = ColdcardDevice(sn=force_serial, encrypt=not force_plaintext)
    except KeyError:
        if not optional:
            raise
        device = None

    yield device

    if device:
        device.close()
Ejemplo n.º 2
0
Archivo: coldcardi.py Proyecto: nvk/HWI
class ColdCardClient(HardwareWalletClient):

    # device is an HID device that has already been opened.
    def __init__(self, device):
        super(ColdCardClient, self).__init__(device)
        self.device = ColdcardDevice(dev=device)

    # Must return a dict with the xpub
    # Retrieves the public key at the specified BIP 32 derivation path
    def get_pubkey_at_path(self, path):
        path = path.replace('h', '\'')
        path = path.replace('H', '\'')
        xpub = self.device.send_recv(CCProtocolPacker.get_xpub(path),
                                     timeout=None)
        if self.is_testnet:
            return {'xpub': xpub_main_2_test(xpub)}
        else:
            return {'xpub': xpub}

    # Must return a hex string with the signed transaction
    # The tx must be in the combined unsigned transaction format
    def sign_tx(self, tx):
        self.device.check_mitm()

        # Get psbt in hex and then make binary
        fd = io.BytesIO(base64.b64decode(tx.serialize()))

        # learn size (portable way)
        offset = 0
        sz = fd.seek(0, 2)
        fd.seek(0)

        left = sz
        chk = sha256()
        for pos in range(0, sz, MAX_BLK_LEN):
            here = fd.read(min(MAX_BLK_LEN, left))
            if not here: break
            left -= len(here)
            result = self.device.send_recv(
                CCProtocolPacker.upload(pos, sz, here))
            assert result == pos
            chk.update(here)

        # do a verify
        expect = chk.digest()
        result = self.device.send_recv(CCProtocolPacker.sha256())
        assert len(result) == 32
        if result != expect:
            raise ValueError("Wrong checksum:\nexpect: %s\n   got: %s" %
                             (b2a_hex(expect).decode('ascii'),
                              b2a_hex(result).decode('ascii')))

        # start the signing process
        ok = self.device.send_recv(CCProtocolPacker.sign_transaction(
            sz, expect),
                                   timeout=None)
        assert ok == None

        print("Waiting for OK on the Coldcard...")

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_txn(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=1)
        return {'psbt': base64.b64encode(result).decode()}

    # Must return a base64 encoded string with the signed message
    # The message can be any string. keypath is the bip 32 derivation path for the key to sign with
    def sign_message(self, message, keypath):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Display address of specified type on the device. Only supports single-key based addresses.
    def display_address(self, keypath, p2sh_p2wpkh, bech32):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Setup a new device
    def setup_device(self):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Wipe this device
    def wipe_device(self):
        raise NotImplementedError(
            'The HardwareWalletClient base class does not '
            'implement this method')

    # Close the device
    def close(self):
        self.device.close()
Ejemplo n.º 3
0
class ColdcardClient(HardwareWalletClient):
    def __init__(self, path, password=''):
        super(ColdcardClient, self).__init__(path, password)
        # Simulator hard coded pipe socket
        if path == CC_SIMULATOR_SOCK:
            self.device = ColdcardDevice(sn=path)
        else:
            device = hid.device()
            device.open_path(path.encode())
            self.device = ColdcardDevice(dev=device)

    # Must return a dict with the xpub
    # Retrieves the public key at the specified BIP 32 derivation path
    def get_pubkey_at_path(self, path):
        self.device.check_mitm()
        path = path.replace('h', '\'')
        path = path.replace('H', '\'')
        xpub = self.device.send_recv(CCProtocolPacker.get_xpub(path),
                                     timeout=None)
        if self.is_testnet:
            return {'xpub': xpub_main_2_test(xpub)}
        else:
            return {'xpub': xpub}

    # Must return a hex string with the signed transaction
    # The tx must be in the combined unsigned transaction format
    def sign_tx(self, tx):
        self.device.check_mitm()

        # Get psbt in hex and then make binary
        fd = io.BytesIO(base64.b64decode(tx.serialize()))

        # learn size (portable way)
        offset = 0
        sz = fd.seek(0, 2)
        fd.seek(0)

        left = sz
        chk = sha256()
        for pos in range(0, sz, MAX_BLK_LEN):
            here = fd.read(min(MAX_BLK_LEN, left))
            if not here: break
            left -= len(here)
            result = self.device.send_recv(
                CCProtocolPacker.upload(pos, sz, here))
            assert result == pos
            chk.update(here)

        # do a verify
        expect = chk.digest()
        result = self.device.send_recv(CCProtocolPacker.sha256())
        assert len(result) == 32
        if result != expect:
            raise ValueError("Wrong checksum:\nexpect: %s\n   got: %s" %
                             (b2a_hex(expect).decode('ascii'),
                              b2a_hex(result).decode('ascii')))

        # start the signing process
        ok = self.device.send_recv(CCProtocolPacker.sign_transaction(
            sz, expect),
                                   timeout=None)
        assert ok == None
        if self.device.is_simulator:
            self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))

        print("Waiting for OK on the Coldcard...")

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_txn(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=1)
        return {'psbt': base64.b64encode(result).decode()}

    # Must return a base64 encoded string with the signed message
    # The message can be any string. keypath is the bip 32 derivation path for the key to sign with
    def sign_message(self, message, keypath):
        self.device.check_mitm()
        keypath = keypath.replace('h', '\'')
        keypath = keypath.replace('H', '\'')

        try:
            ok = self.device.send_recv(CCProtocolPacker.sign_message(
                message.encode(), keypath, AF_CLASSIC),
                                       timeout=None)
            assert ok == None
            if self.device.is_simulator:
                self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
        except CCProtoError as e:
            raise ValueError(str(e))

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_signed_msg(),
                                         timeout=None)
            if done == None:
                continue

            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        addr, raw = done

        sig = str(base64.b64encode(raw), 'ascii').replace('\n', '')
        return {"signature": sig}

    # Display address of specified type on the device. Only supports single-key based addresses.
    def display_address(self, keypath, p2sh_p2wpkh, bech32):
        self.device.check_mitm()
        keypath = keypath.replace('h', '\'')
        keypath = keypath.replace('H', '\'')

        if p2sh_p2wpkh:
            format = AF_P2WPKH_P2SH
        elif bech32:
            format = AF_P2WPKH
        else:
            format = AF_CLASSIC
        address = self.device.send_recv(CCProtocolPacker.show_address(
            keypath, format),
                                        timeout=None)
        if self.device.is_simulator:
            self.device.send_recv(CCProtocolPacker.sim_keypress(b'y'))
        return {'address': address}

    # Setup a new device
    def setup_device(self, label='', passphrase=''):
        raise UnavailableActionError(
            'The Coldcard does not support software setup')

    # Wipe this device
    def wipe_device(self):
        raise UnavailableActionError(
            'The Coldcard does not support wiping via software')

    # Restore device from mnemonic or xprv
    def restore_device(self, label=''):
        raise UnavailableActionError(
            'The Coldcard does not support restoring via software')

    # Begin backup process
    def backup_device(self, label='', passphrase=''):
        self.device.check_mitm()

        ok = self.device.send_recv(CCProtocolPacker.start_backup())
        assert ok == None

        while 1:
            time.sleep(0.250)
            done = self.device.send_recv(CCProtocolPacker.get_backup_file(),
                                         timeout=None)
            if done == None:
                continue
            break

        if len(done) != 2:
            raise ValueError('Failed: %r' % done)

        result_len, result_sha = done

        result = self.device.download_file(result_len,
                                           result_sha,
                                           file_number=0)
        filename = time.strftime('backup-%Y%m%d-%H%M.7z')
        open(filename, 'wb').write(result)
        return {
            'success': True,
            'message': 'The backup has be written to {}'.format(filename)
        }

    # Close the device
    def close(self):
        self.device.close()