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()
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