Beispiel #1
0
class KeepkeyClient(HardwareWalletClient):
    def __init__(self, path, password=''):
        super(KeepkeyClient, self).__init__(path, password)
        devices = HidTransport.enumerate()
        transport = HidTransport((path.encode(), None))
        self.client = KeepKey(transport)

        # if it wasn't able to find a client, throw an error
        if not self.client:
            raise IOError("no Device")

        self.password = password
        os.environ['PASSPHRASE'] = password

    # 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', '\'')
        expanded_path = self.client.expand_path(path)
        output = self.client.get_public_node(expanded_path)
        if self.is_testnet:
            return {'xpub': xpub_main_2_test(output.xpub)}
        else:
            return {'xpub': output.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):

        # Get this devices master key fingerprint
        master_key = self.client.get_public_node([0])
        master_fp = get_xpub_fingerprint(master_key.xpub)

        # Prepare inputs
        inputs = []
        for psbt_in, txin in zip(tx.inputs, tx.tx.vin):
            txinputtype = proto.TxInputType()

            # Set the input stuff
            txinputtype.prev_hash = ser_uint256(txin.prevout.hash)[::-1]
            txinputtype.prev_index = txin.prevout.n
            txinputtype.sequence = txin.nSequence

            # Detrermine spend type
            if psbt_in.non_witness_utxo:
                txinputtype.script_type = 0
            elif psbt_in.witness_utxo:
                # Check if the output is p2sh
                if psbt_in.witness_utxo.is_p2sh():
                    txinputtype.script_type = 3
                else:
                    txinputtype.script_type = 4

            # Check for 1 key
            if len(psbt_in.hd_keypaths) == 1:
                # Is this key ours
                pubkey = list(psbt_in.hd_keypaths.keys())[0]
                fp = psbt_in.hd_keypaths[pubkey][0]
                keypath = psbt_in.hd_keypaths[pubkey][1:]
                if fp == master_fp:
                    # Set the keypath
                    txinputtype.address_n.extend(keypath)

            # Check for multisig (more than 1 key)
            elif len(psbt_in.hd_keypaths) > 1:
                raise TypeError("Cannot sign multisig yet")
            else:
                raise TypeError("All inputs must have a key for this device")

            # Set the amount
            if psbt_in.non_witness_utxo:
                txinputtype.amount = psbt_in.non_witness_utxo.vout[
                    txin.prevout.n].nValue
            elif psbt_in.witness_utxo:
                txinputtype.amount = psbt_in.witness_utxo.nValue

            # append to inputs
            inputs.append(txinputtype)

        # address version byte
        if self.is_testnet:
            p2pkh_version = b'\x6f'
            p2sh_version = b'\xc4'
        else:
            p2pkh_version = b'\x00'
            p2sh_version = b'\x05'

        # prepare outputs
        outputs = []
        for out in tx.tx.vout:
            txoutput = proto.TxOutputType()
            txoutput.amount = out.nValue
            if out.is_p2pkh():
                txoutput.address = to_address(out.scriptPubKey[3:23],
                                              p2pkh_version)
                txoutput.script_type = 0
            elif out.is_p2sh():
                txoutput.address = to_address(out.scriptPubKey[2:22],
                                              p2sh_version)
                txoutput.script_type = 1
            else:
                # TODO: Figure out what to do here. for now, just break
                break

            # append to outputs
            outputs.append(txoutput)
            logging.debug(txoutput)

        # Sign the transaction
        self.client.set_tx_api(TxAPIPSBT(tx))
        if self.is_testnet:
            signed_tx = self.client.sign_tx("Testnet", inputs, outputs,
                                            tx.tx.nVersion, tx.tx.nLockTime)
        else:
            signed_tx = self.client.sign_tx("Bitcoin", inputs, outputs,
                                            tx.tx.nVersion, tx.tx.nLockTime)

        signatures = signed_tx[0]
        logging.debug(binascii.hexlify(signed_tx[1]))
        for psbt_in in tx.inputs:
            for pubkey, sig in zip(psbt_in.hd_keypaths.keys(), signatures):
                fp = psbt_in.hd_keypaths[pubkey][0]
                keypath = psbt_in.hd_keypaths[pubkey][1:]
                if fp == master_fp:
                    psbt_in.partial_sigs[pubkey] = sig + b'\x01'

        return {'psbt': tx.serialize()}

    # Must return a base64 encoded string with the signed message
    # The message can be any string
    def sign_message(self, message, keypath):
        raise NotImplementedError(
            'The KeepKey does not currently implement signmessage')

    # 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 KeepKey does not currently implement displayaddress')

    # Setup a new device
    def setup_device(self, label='', passphrase=''):
        if self.client.features.initialized:
            raise DeviceAlreadyInitError(
                'Device is already initialized. Use wipe first and try again')
        self.client.reset_device(False, 256, bool(self.password), True, label,
                                 'english')
        return {'success': True}

    # Wipe this device
    def wipe_device(self):
        self.client.wipe_device()
        return {'success': True}

    # Restore device from mnemonic or xprv
    def restore_device(self, label=''):
        self.client.recovery_device(False, 24, bool(self.password), True,
                                    label, 'english')
        return {'success': True}

    # Begin backup process
    def backup_device(self, label='', passphrase=''):
        raise UnavailableActionError(
            'The Keepkey does not support creating a backup via software')

    # Close the device
    def close(self):
        self.client.close()
Beispiel #2
0
               0],  # yjJUQ42u8Z86s9LiUmNgvS9dSzhunWbuQR
    # amount=500000000
    prev_hash=binascii.unhexlify(
        '4a1f3f89d95dd162e30399386dd7748c7fa02ec958320f4542923cf3a63fde48'),
    prev_index=1,
)

out1 = proto_types.TxOutputType(
    address='yV7G6wcfkqfjw3SyykJzYnsL3fqJByqXYG',
    amount=500000000 - 10000,
    script_type=proto_types.PAYTOADDRESS,
)

(signatures, serialized_tx) = client.sign_tx('tDash', [
    inp1,
], [
    out1,
])
#(signatures, serialized_tx) = client.sign_tx('tDash', [inp1, ], [out1, ], None, True)

print(binascii.hexlify(signatures[0]))
print(binascii.hexlify(serialized_tx))

#   tpubDEfQ9V3njDmFrTrfzc5tNV3nGJcXh2zAfQk6fWMjSkmx2TAjAa8wNDx8MHnRpvSkAvgdySmi4PLiCdRGJaitwbWtEFHQik7yWHR88tWoJz2
#   tDASH address: 44'/165'/0'/0/0 yjJUQ42u8Z86s9LiUmNgvS9dSzhunWbuQR
#   tDASH address: 44'/165'/0'/0/1 yPuuLxGRtpa5R7xuEyagEXysUN6RmKtR89
#   tDASH address: 44'/165'/0'/0/2 yhQDFkFyoJZYEGAJ7nkC4KC8u9nRMS5i6h
#   tDASH address: 44'/165'/0'/0/3 yhQGvsHmezeyTBYsSj4JrD5F7hthiK8Day
#   tDASH address: 44'/165'/0'/0/4 yV7G6wcfkqfjw3SyykJzYnsL3fqJByqXYG
#   tDASH address: 44'/165'/0'/0/5 yehyqx9xJhkC2vh976E9pJoP2xpCYpPKNw
#   tDASH address: 44'/165'/0'/0/6 yXL8Nj6QEjBUiFc5W6FdBfruX4MWkbSkGE