def get_xpub(self, bip32_path, xtype): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit(): raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT) if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit(): raise Exception(MSG_NEEDS_FW_UPDATE_SEGWIT) splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub
def get_xpub(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") try: splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = XPUB_HEADER.decode('hex') + chr(depth) + self.i4b(fingerprint) + self.i4b(childnum) + str(nodeData['chainCode']) + str(publicKey) except Exception, e: #self.give_error(e, True) return None
def get_xpub(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") try: splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(0, str(nodeData['chainCode']), str(publicKey), depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub except Exception as e: print_error(e) return None
def get_public_key(self, bip32_path): # S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node # This only happens once so it's bearable self.get_client( ) # prompt for the PIN before displaying the dialog if necessary waitDialog.start("Computing master public key") try: splitPath = bip32_path.split('/') fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.get_client().getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.get_client().getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = "0488B21E".decode('hex') + chr(depth) + self.i4b( fingerprint) + self.i4b(childnum) + str( nodeData['chainCode']) + str(publicKey) except Exception, e: self.give_error(e, True)
def get_xpub(self, bip32_path, xtype): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len( lastChild) == 1 else 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub
def get_xpub(self, bip32_path, xtype): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit(): raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT) if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit(): raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT) splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0]) return BIP32Node(xtype=xtype, eckey=ecc.ECPubkey(publicKey), chaincode=nodeData['chainCode'], depth=depth, fingerprint=self.i4b(fingerprint), child_number=self.i4b(childnum)).to_xpub()
def get_master_public_key(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = bytes(compress_public_key(nodeData['publicKey'])) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0]) derivation = BIP32Derivation(chain_code=nodeData['chainCode'], depth=depth, parent_fingerprint=pack_be_uint32(fingerprint), n=childnum) return BIP32PublicKey(PublicKey.from_bytes(publicKey), derivation, Net.COIN)
def get_xpub(self, bip32_path, xtype): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit(): raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT) if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh'] and not self.supports_segwit(): raise UserFacingException(MSG_NEEDS_FW_UPDATE_SEGWIT) bip32_path = bip32.normalize_bip32_derivation(bip32_path) bip32_intpath = bip32.convert_bip32_path_to_list_of_uint32(bip32_path) bip32_path = bip32_path[2:] # cut off "m/" if len(bip32_intpath) >= 1: prevPath = bip32.convert_bip32_intpath_to_strpath(bip32_intpath[:-1])[2:] nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) fingerprint_bytes = hash_160(publicKey)[0:4] childnum_bytes = bip32_intpath[-1].to_bytes(length=4, byteorder="big") else: fingerprint_bytes = bytes(4) childnum_bytes = bytes(4) nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(bip32_intpath) return BIP32Node(xtype=xtype, eckey=ecc.ECPubkey(bytes(publicKey)), chaincode=nodeData['chainCode'], depth=depth, fingerprint=fingerprint_bytes, child_number=childnum_bytes).to_xpub()
def get_public_key(self, bip32_path): # S-L-O-W - we don't handle the fingerprint directly, so compute it manually from the previous node # This only happens once so it's bearable self.get_client() # prompt for the PIN before displaying the dialog if necessary waitDialog.start("Computing master public key") try: splitPath = bip32_path.split('/') fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.get_client().getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.get_client().getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = "0488B21E".decode('hex') + chr(depth) + self.i4b(fingerprint) + self.i4b(childnum) + str(nodeData['chainCode']) + str(publicKey) except Exception, e: self.give_error(e, True)
def get_xpub(self, bip32_path, xtype): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable # self.get_client() # prompt for the PIN before displaying the dialog if necessary # self.handler.show_message("Computing master public key") if xtype in ['p2wpkh', 'p2wsh'] and not self.supports_native_segwit(): raise Exception( "Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com" ) if xtype in ['p2wpkh-p2sh', 'p2wsh-p2sh' ] and not self.supports_segwit(): raise Exception( "Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com" ) splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) # print('self.dongleObject.dongle', self.dongleObject.dongle) # <btchip.btchipComm.HIDDongleHIDAPI object at 0x11e514fd0> try: nodeData = self.dongleObject.getWalletPublicKey(prevPath) except BTChipException as e: if e.sw == 0x6f04: raise BaseException( 'error 6f04, Please use Bitcoin mode on your ledger rather than Qtum mode' ) raise e publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len( lastChild) == 1 else 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub
def append_inputs_to_TX(self, utxo, bip32_path): self.amount += int(utxo['value']) raw_tx = bytearray.fromhex(utxo['raw_tx']) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['tx_ouput_n'] if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrect value of outputIndex for UTXO %s-%d' % (utxo['raw_tx'], utxo['tx_ouput_n'])) trusted_input = self.chip.getTrustedInput(prev_transaction, utxo_tx_index) self.trusted_inputs.append(trusted_input) # Hash check curr_pubkey = compress_public_key(self.chip.getWalletPublicKey(bip32_path)['publicKey']) pubkey_hash = bin_hash160(curr_pubkey) pubkey_hash_from_script = extract_pkh_from_locking_script(prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: text = "Error: The hashes for the public key for the BIP32 path, and the UTXO locking script do not match." text += "Your signed transaction will not be validated by the network.\n" text += "pubkey_hash: %s\n" % pubkey_hash.hex() text += "pubkey_hash_from_script: %s\n" % pubkey_hash_from_script.hex() printDbg(text) self.arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['tx_ouput_n']].script, 'pubkey': curr_pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo['tx_ouput_n'], 'txid': utxo['tx_hash'] })
def scanForBip32(self, account, address, starting_spath=0, spath_count=10, isTestnet=False): found = False spath = -1 printOK("Scanning for Bip32 path of address: %s" % address) for i in range(starting_spath, starting_spath+spath_count): curr_path = MPATH + "%d'/0/%d" % (account, i) printDbg("checking path... %s" % curr_path) self.lock.acquire() try: if not isTestnet: curr_addr = self.chip.getWalletPublicKey(curr_path).get('address')[12:-2] else: pubkey = compress_public_key(self.chip.getWalletPublicKey(curr_path).get('publicKey')).hex() curr_addr = pubkey_to_address(pubkey, isTestnet) if curr_addr == address: found = True spath = i break sleep(0.01) except Exception as e: err_msg = 'error in scanForBip32' printException(getCallerName(), getFunctionName(), err_msg, e.args) finally: self.lock.release() return (found, spath)
def get_xpub(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") try: if (os.getenv("LEDGER_NATIVE_SEGWIT") is not None) and self.supports_native_segwit(): xtype = 'segwit' elif bip32_path.startswith("m/49'/"): if not self.supports_segwit(): raise Exception( "Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com" ) xtype = 'segwit_p2sh' else: xtype = 'standard' splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len( lastChild) == 1 else 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub except Exception as e: traceback.print_exc(file=sys.stdout) #print_error(e) return None
def scanForPubKey(self, account, spath): result = None printOK("Scanning for PubKey of address n. %d on account n. %d" % (spath, account)) curr_path = MPATH + "%d'/0/%d" % (account, spath) with self.lock: nodeData = self.chip.getWalletPublicKey(curr_path) result = compress_public_key(nodeData.get('publicKey')).hex() return result
def get_address_and_pubkey(client, bip32_path): bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] nodedata = client.getWalletPublicKey(bip32_path) return { 'address': nodedata.get('address').decode('utf-8'), 'publicKey': compress_public_key(nodedata.get('publicKey')) }
def scanForAddress(self, hwAcc, spath, intExt=0, isTestnet=False): with self.lock: if not isTestnet: curr_path = MPATH + "%d'/%d/%d" % (hwAcc, intExt, spath) curr_addr = self.chip.getWalletPublicKey(curr_path).get('address')[12:-2] else: curr_path = MPATH_TESTNET + "%d'/%d/%d" % (hwAcc, intExt, spath) pubkey = compress_public_key(self.chip.getWalletPublicKey(curr_path).get('publicKey')).hex() curr_addr = pubkey_to_address(pubkey, isTestnet) return curr_addr
def scanForPubKey(self, account, spath, isTestnet=False): hwpath = "%d'/0/%d" % (account, spath) if isTestnet: curr_path = MPATH_TESTNET + hwpath else: curr_path = MPATH + hwpath with self.lock: nodeData = self.chip.getWalletPublicKey(curr_path) return compress_public_key(nodeData.get('publicKey')).hex()
def get_address_and_pubkey(client, bip32_path, show_display=False): bip32_path = clean_bip32_path(bip32_path) bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] nodedata = client.getWalletPublicKey(bip32_path, showOnScreen=show_display) return { 'address': nodedata.get('address').decode('utf-8'), 'publicKey': compress_public_key(nodedata.get('publicKey')) }
def main(): # dongle = getDongle(True) dongle = getDongle(False) app = btchip(dongle) mpath = "44'/5'/0'/0" for i in range(11): addr_path = mpath + '/' + str(i) nodedata = app.getWalletPublicKey(addr_path) publicKey = compress_public_key(nodedata.get('publicKey')) address = (nodedata.get('address')).decode("utf-8") print(addr_path, address)
def get_xpub(self, bip32_path): self.checkDevice() # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable #self.get_client() # prompt for the PIN before displaying the dialog if necessary #self.handler.show_message("Computing master public key") try: if (os.getenv("LEDGER_NATIVE_SEGWIT") is not None) and self.supports_native_segwit(): xtype = 'segwit' elif bip32_path.startswith("m/49'/"): if not self.supports_segwit(): raise Exception("Firmware version too old for Segwit support. Please update at https://www.ledgerwallet.com") xtype = 'segwit_p2sh' else: xtype = 'standard' splitPath = bip32_path.split('/') if splitPath[0] == 'm': splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0:len(splitPath) - 1]) nodeData = self.dongleObject.getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData['publicKey']) h = hashlib.new('ripemd160') h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.dongleObject.getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData['publicKey']) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split('\'') childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0]) xpub = bitcoin.serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum)) return xpub except Exception as e: traceback.print_exc(file=sys.stdout) #print_error(e) return None
def scanForAddress(self, account, spath, isTestnet=False): curr_addr = None curr_path = MPATH + "%d'/0/%d" % (account, spath) printOK("Scanning for Address n. %d on account n. %d" % (spath, account)) with self.lock: if not isTestnet: curr_addr = self.chip.getWalletPublicKey(curr_path).get('address')[12:-2] else: pubkey = compress_public_key(self.chip.getWalletPublicKey(curr_path).get('publicKey')).hex() curr_addr = pubkey_to_address(pubkey, isTestnet) return curr_addr
def scanForPubKey(self, account, spath): printOK("Scanning for Address of path_id %s on account n° %s" % (str(spath), str(account))) curr_path = MPATH + "%d'/0/%d" % (account, spath) try: nodeData = self.chip.getWalletPublicKey(curr_path) except Exception as e: err_msg = 'error in scanForPubKey' printException(getCallerName(), getFunctionName(), err_msg, e.args) return None return compress_public_key(nodeData.get('publicKey')).hex()
def get_public_key(self, bip32_path): # bip32_path is of the form 44'/0'/1' # S-L-O-W - we don't handle the fingerprint directly, so compute # it manually from the previous node # This only happens once so it's bearable self.get_client() # prompt for the PIN before displaying the dialog if necessary self.handler.show_message("Computing master public key") try: splitPath = bip32_path.split("/") if splitPath[0] == "m": splitPath = splitPath[1:] bip32_path = bip32_path[2:] fingerprint = 0 if len(splitPath) > 1: prevPath = "/".join(splitPath[0 : len(splitPath) - 1]) nodeData = self.get_client().getWalletPublicKey(prevPath) publicKey = compress_public_key(nodeData["publicKey"]) h = hashlib.new("ripemd160") h.update(hashlib.sha256(publicKey).digest()) fingerprint = unpack(">I", h.digest()[0:4])[0] nodeData = self.get_client().getWalletPublicKey(bip32_path) publicKey = compress_public_key(nodeData["publicKey"]) depth = len(splitPath) lastChild = splitPath[len(splitPath) - 1].split("'") if len(lastChild) == 1: childnum = int(lastChild[0]) else: childnum = 0x80000000 | int(lastChild[0]) xpub = ( "0488B21E".decode("hex") + chr(depth) + self.i4b(fingerprint) + self.i4b(childnum) + str(nodeData["chainCode"]) + str(publicKey) ) except Exception, e: self.give_error(e, True)
def get_address_and_pubkey(hw_session: HWSessionBase, bip32_path, show_display=False): bip32_path = clean_bip32_path(bip32_path) bip32_path.strip() if bip32_path.lower().find('m/') >= 0: bip32_path = bip32_path[2:] nodedata = hw_session.hw_client.getWalletPublicKey( bip32_path, showOnScreen=show_display) addr = _ledger_extract_address(nodedata.get('address')) return { 'address': addr, 'publicKey': compress_public_key(nodedata.get('publicKey')) }
def scanForPubKey(self, account, spath): self.lock.acquire() printOK("Scanning for PubKey of address n. %d on account n. %d" % (spath, account)) curr_path = MPATH + "%d'/0/%d" % (account, spath) try: nodeData = self.chip.getWalletPublicKey(curr_path) except Exception as e: err_msg = 'error in scanForPubKey' printException(getCallerName(), getFunctionName(), err_msg, e.args) return None finally: self.lock.release() return compress_public_key(nodeData.get('publicKey')).hex()
def scanForAddress(self, account, spath, isTestnet=False): printOK("Scanning for Address n. %d on account n. %d" % (spath, account)) curr_path = MPATH + "%d'/0/%d" % (account, spath) self.lock.acquire() try: if not isTestnet: curr_addr = self.chip.getWalletPublicKey(curr_path).get('address')[12:-2] else: pubkey = compress_public_key(self.chip.getWalletPublicKey(curr_path).get('publicKey')).hex() curr_addr = pubkey_to_address(pubkey, isTestnet) except Exception as e: err_msg = 'error in scanForAddress' printException(getCallerName(), getFunctionName(), err_msg, e.args) return None finally: self.lock.release() return curr_addr
def scanForBip32(self, account, address, starting_spath=0, spath_count=10, isTestnet=False): found = False spath = -1 printOK("Scanning for Bip32 path of address: %s" % address) for i in range(starting_spath, starting_spath+spath_count): curr_path = MPATH + "%d'/0/%d" % (account, i) printDbg("checking path... %s" % curr_path) with self.lock: if not isTestnet: curr_addr = self.chip.getWalletPublicKey(curr_path).get('address')[12:-2] else: pubkey = compress_public_key(self.chip.getWalletPublicKey(curr_path).get('publicKey')).hex() curr_addr = pubkey_to_address(pubkey, isTestnet) if curr_addr == address: found = True spath = i break sleep(0.01) return (found, spath)
def sign_transaction(self, tx, password): if tx.is_complete(): return client = self.get_client() self.signing = True inputs = [] inputsPaths = [] pubKeys = [] chipInputs = [] redeemScripts = [] signatures = [] preparedTrustedInputs = [] changePath = "" changeAmount = None output = None outputAmount = None p2shTransaction = False pin = "" self.get_client( ) # prompt for the PIN before displaying the dialog if necessary rawTx = tx.serialize() # Fetch inputs of the transaction to sign for txin in tx.inputs(): if txin.get('is_coinbase'): self.give_error( "Coinbase not supported") # should never happen redeemScript = None signingPos = -1 xpub, s = parse_xpubkey(txin['x_pubkeys'][0]) hwAddress = "%s/%d/%d" % (self.get_derivation()[2:], s[0], s[1]) if len(txin['pubkeys']) > 1: p2shTransaction = True if 'redeemScript' in txin: redeemScript = txin['redeemScript'] if p2shTransaction: chipPublicKey = compress_public_key( self.get_client().getWalletPublicKey( hwAddress)['publicKey']) for currentIndex, key in enumerate(txin['pubkeys']): if chipPublicKey == key.decode('hex'): signingPos = currentIndex break if signingPos == -1: self.give_error("No matching key for multisignature input" ) # should never happen inputs.append([ txin['prev_tx'].raw, txin['prevout_n'], redeemScript, txin['prevout_hash'], signingPos ]) inputsPaths.append(hwAddress) pubKeys.append(txin['pubkeys']) # Sanity check if p2shTransaction: for txinput in tx.inputs(): if len(txinput['pubkeys']) < 2: self.give_error( "P2SH / regular input mixed in same transaction not supported" ) # should never happen txOutput = var_int(len(tx.outputs())) for txout in tx.outputs(): output_type, addr, amount = txout txOutput += int_to_hex(amount, 8) script = tx.pay_script(output_type, addr) txOutput += var_int(len(script) / 2) txOutput += script txOutput = txOutput.decode('hex') # Recognize outputs - only one output and one change is authorized if not p2shTransaction: if len(tx.outputs()) > 2: # should never happen self.give_error( "Transaction with more than 2 outputs not supported") for i, (_type, address, amount) in enumerate(tx.outputs()): assert _type == TYPE_ADDRESS change, index = tx.output_info[i] if change: changePath = "%s/%d/%d" % (self.get_derivation()[2:], change, index) changeAmount = amount else: output = address outputAmount = amount self.handler.show_message( _("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: if not p2shTransaction: txtmp = bitcoinTransaction(bytearray( utxo[0].decode('hex'))) chipInputs.append(self.get_client().getTrustedInput( txtmp, utxo[1])) redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = utxo[3].decode('hex')[::-1].encode('hex') tmp += int_to_hex(utxo[1], 4) chipInputs.append({'value': tmp.decode('hex')}) redeemScripts.append(bytearray(utxo[2].decode('hex'))) # Sign all inputs firstTransaction = True inputIndex = 0 while inputIndex < len(inputs): self.get_client().startUntrustedTransaction( firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput if firstTransaction: transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: # TODO : handle different confirmation types. For the time being only supports keyboard 2FA self.handler.clear_dialog() if 'keycardData' in outputData: pin2 = "" for keycardIndex in range( len(outputData['keycardData'])): msg = "Do not enter your device PIN here !\r\n\r\n" + \ "Your Ledger Wallet wants to talk to you and tell you a unique second factor code.\r\n" + \ "For this to work, please match the character between stars of the output address using your security card\r\n\r\n" + \ "Output address : " for index in range(len(output)): if index == outputData['keycardData'][ keycardIndex]: msg = msg + "*" + output[index] + "*" else: msg = msg + output[index] msg = msg + "\r\n" confirmed, p, pin = self.password_dialog(msg) if not confirmed: raise Exception('Aborted by user') try: pin2 = pin2 + chr(int(pin[0], 16)) except: raise Exception('Invalid PIN character') pin = pin2 else: confirmed, p, pin = self.password_dialog() if not confirmed: raise Exception('Aborted by user') pin = pin.encode() #self.plugin.get_client(self, True, True) self.handler.show_message("Signing ...") else: # Sign input with the provided PIN inputSignature = self.get_client().untrustedHashSign( inputsPaths[inputIndex], pin) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 firstTransaction = False except BaseException as e: traceback.print_exc(file=sys.stdout) self.give_error(e, True) finally: self.handler.clear_dialog() # Reformat transaction inputIndex = 0 while inputIndex < len(inputs): if p2shTransaction: signaturesPack = [signatures[inputIndex]] * len( pubKeys[inputIndex]) inputScript = get_p2sh_input_script(redeemScripts[inputIndex], signaturesPack) preparedTrustedInputs.append([ ("\x00" * 4) + chipInputs[inputIndex]['value'], inputScript ]) else: inputScript = get_regular_input_script( signatures[inputIndex], pubKeys[inputIndex][0].decode('hex')) preparedTrustedInputs.append( [chipInputs[inputIndex]['value'], inputScript]) inputIndex = inputIndex + 1 updatedTransaction = format_transaction(transactionOutput, preparedTrustedInputs) updatedTransaction = hexlify(updatedTransaction) tx.update_signatures(updatedTransaction) self.signing = False
updatedTransaction = hexlify(updatedTransaction) tx.update(updatedTransaction) self.client.bad = use2FA self.signing = False def check_proper_device(self): pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:] if not self.device_checked: waitDialog.start("Checking device") try: nodeData = self.get_client().getWalletPublicKey("44'/0'/0'") except Exception, e: self.give_error(e, True) finally: waitDialog.emit(SIGNAL('dongle_done')) pubKeyDevice = compress_public_key(nodeData['publicKey']) self.device_checked = True if pubKey != pubKeyDevice: self.proper_device = False else: self.proper_device = True return self.proper_device def password_dialog(self, msg=None): if not msg: msg = _("Do not enter your device PIN here !\r\n\r\n" \ "Your BTChip wants to talk to you and tell you a unique second factor code.\r\n" \ "For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your BTChip and plug it back in.\r\n" \ "It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n" \ "Check that summary and then enter the second factor code here.\r\n" \
def prepare_transfer_tx(main_ui, utxos_to_spend, dest_address, tx_fee, rawtransactions): client = main_ui.hw_client # Each of the UTXOs will become an input in the new transaction. For each of those inputs, create # a Ledger's 'trusted input', that will be used by the the device to sign a transaction. trusted_inputs = [] # arg_inputs: list of dicts # { # 'locking_script': <Locking script of the UTXO used as an input. Used in the process of signing # transaction.>, # 'outputIndex': <index of the UTXO within the previus transaction>, # 'txid': <hash of the previus transaction>, # 'bip32_path': <BIP32 path of the HW key controlling UTXO's destination>, # 'pubkey': <Public key obtained from the HW using the bip32_path.> # 'signature' <Signature obtained as a result of processing the input. It will be used as a part of the # unlocking script.> # } # Why do we need a locking script of the previous transaction? When hashing a new transaction before creating its # signature, all placeholders for input's unlocking script has to be filled with locking script of the # corresponding UTXO. Look here for the details: # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) arg_inputs = [] # A dictionary mapping bip32 path to a pubkeys obtained from the Ledger device - used to avoid # reading it multiple times for the same bip32 path bip32_to_address = {} amount = 0 starting = True for idx, utxo in enumerate(utxos_to_spend): amount += utxo['satoshis'] raw_tx = bytearray.fromhex(rawtransactions[utxo['txid']]) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + rawtransactions[utxo['txid']]) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['outputIndex'] if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrent value of outputIndex for UTXO %s' % str(idx)) trusted_input = client.getTrustedInput(prev_transaction, utxo_tx_index) trusted_inputs.append(trusted_input) bip32_path = utxo['bip32_path'] bip32_path = clean_bip32_path(bip32_path) pubkey = bip32_to_address.get(bip32_path) if not pubkey: pubkey = compress_public_key( client.getWalletPublicKey(bip32_path)['publicKey']) bip32_to_address[bip32_path] = pubkey pubkey_hash = bitcoin.bin_hash160(pubkey) # verify if the public key hash of the wallet's bip32 path is the same as specified in the UTXO locking script # if they differ, signature and public key we produce and are going to include in the unlocking script won't # match the locking script conditions - transaction will be rejected by the network pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: logging.error( "Error: different public key hashes for the BIP32 path %s (UTXO %s) and the UTXO locking " "script. Your signed transaction will not be validated by the network." % (bip32_path, str(idx))) arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['outputIndex']].script, 'pubkey': pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo['outputIndex'], 'txid': utxo['txid'] }) amount -= int(tx_fee) amount = int(amount) arg_outputs = [{ 'address': dest_address, 'valueSat': amount }] # there will be multiple outputs soon new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address']) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') new_transaction.outputs.append(output) # join all outputs - will be used by Ledger for sigining transaction all_outputs_raw = new_transaction.serializeOutputs() # sign all inputs on Ledger and add inputs in the new_transaction object for serialization for idx, new_input in enumerate(arg_inputs): client.startUntrustedTransaction(starting, idx, trusted_inputs, new_input['locking_script']) client.finalizeInputFull(all_outputs_raw) sig = client.untrustedHashSign(new_input['bip32_path'], lockTime=0) new_input['signature'] = sig input = bitcoinInput() input.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + \ int.to_bytes(new_input['outputIndex'], 4, byteorder='little') input.script = bytearray([len(sig)]) + sig + bytearray( [0x21]) + new_input['pubkey'] input.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) new_transaction.inputs.append(input) starting = False new_transaction.lockTime = bytearray([0, 0, 0, 0]) tx_raw = bytearray(new_transaction.serialize()) return tx_raw, amount
updatedTransaction = hexlify(updatedTransaction) tx.update(updatedTransaction) self.client.bad = use2FA self.signing = False def check_proper_device(self): pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:] if not self.device_checked: self.plugin.handler.show_message("Checking device") try: nodeData = self.get_client().getWalletPublicKey("44'/0'/0'") except Exception, e: self.give_error(e, True) finally: self.plugin.handler.stop() pubKeyDevice = compress_public_key(nodeData['publicKey']) self.device_checked = True if pubKey != pubKeyDevice: self.proper_device = False else: self.proper_device = True return self.proper_device def password_dialog(self, msg=None): if not msg: msg = _("Do not enter your device PIN here !\r\n\r\n" \ "Your MUEhip wants to talk to you and tell you a unique second factor code.\r\n" \ "For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your MUEhip and plug it back in.\r\n" \ "It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n" \ "Check that summary and then enter the second factor code here.\r\n" \
def prepare_transfer_tx_bulk(self, caller, mnodes, dest_address, tx_fee, rawtransactions, useSwiftX=False): # For each UTXO create a Ledger 'trusted input' self.trusted_inputs = [] # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) self.arg_inputs = [] self.amount = 0 self.lock.acquire() num_of_sigs = sum([len(mnode['utxos']) for mnode in mnodes]) curr_utxo_checked = 0 try: for i, mnode in enumerate(mnodes): for idx, utxo in enumerate(mnode['utxos']): self.amount += int(utxo['value']) raw_tx = bytearray.fromhex(rawtransactions[utxo['tx_hash']]) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + rawtransactions[utxo['tx_hash']]) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['tx_ouput_n'] if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrect value of outputIndex for UTXO %s' % str(idx)) trusted_input = self.chip.getTrustedInput(prev_transaction, utxo_tx_index) self.trusted_inputs.append(trusted_input) # Hash check curr_pubkey = compress_public_key(self.chip.getWalletPublicKey(mnode['path'])['publicKey']) pubkey_hash = bin_hash160(curr_pubkey) pubkey_hash_from_script = extract_pkh_from_locking_script(prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: text = "Error: The hashes for the public key for the BIP32 path, and the UTXO locking script do not match." text += "Your signed transaction will not be validated by the network.\n" text += "pubkey_hash: %s\n" % pubkey_hash.hex() text += "pubkey_hash_from_script: %s\n" % pubkey_hash_from_script.hex() printDbg(text) self.arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['tx_ouput_n']].script, 'pubkey': curr_pubkey, 'bip32_path': mnode['path'], 'outputIndex': utxo['tx_ouput_n'], 'txid': utxo['tx_hash'] }) # completion percent emitted curr_utxo_checked += 1 completion = int(95*curr_utxo_checked / num_of_sigs) self.tx_progress.emit(completion) self.amount -= int(tx_fee) self.amount = int(self.amount) arg_outputs = [{'address': dest_address, 'valueSat': self.amount}] # there will be multiple outputs soon self.new_transaction = bitcoinTransaction() # new transaction object to be used for serialization at the last stage self.new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) self.tx_progress.emit(99) except Exception: raise finally: self.lock.release() try: for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address']) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') self.new_transaction.outputs.append(output) except Exception: raise self.tx_progress.emit(100) # join all outputs - will be used by Ledger for signing transaction self.all_outputs_raw = self.new_transaction.serializeOutputs() self.mBox2 = QMessageBox(caller) self.messageText = "<p>Confirm transaction on your device, with the following details:</p>" #messageText += "From bip32_path: <b>%s</b><br><br>" % str(bip32_path) self.messageText += "<p>Payment to:<br><b>%s</b></p>" % dest_address self.messageText += "<p>Net amount:<br><b>%s</b> PIV</p>" % str(round(self.amount / 1e8, 8)) if useSwiftX: self.messageText += "<p>Fees (SwiftX flat rate):<br><b>%s</b> PIV<p>" % str(round(int(tx_fee) / 1e8, 8)) else: self.messageText += "<p>Fees:<br><b>%s</b> PIV<p>" % str(round(int(tx_fee) / 1e8, 8)) messageText = self.messageText + "Signature Progress: 0 %" self.mBox2.setText(messageText) self.mBox2.setIconPixmap(caller.tabMain.ledgerImg.scaledToHeight(200, Qt.SmoothTransformation)) self.mBox2.setWindowTitle("CHECK YOUR LEDGER") self.mBox2.setStandardButtons(QMessageBox.NoButton) self.mBox2.setMaximumWidth(500) self.mBox2.show() ThreadFuns.runInThread(self.signTxSign, (), self.signTxFinish)
def signTx(self, device, bip32_path, utxos_to_spend, dest_address, tx_fee, rawtransactions): # For each UTXO create a Ledger 'trusted input' self.trusted_inputs = [] # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) self.arg_inputs = [] self.amount = 0 for idx, utxo in enumerate(utxos_to_spend): self.amount += int(utxo['value']) raw_tx = bytearray.fromhex(rawtransactions[utxo['tx_hash']]) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + rawtransactions[utxo['tx_hash']]) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['tx_ouput_n'] if utxo_tx_index < 0 or utxo_tx_index > len( prev_transaction.outputs): raise Exception('Incorrect value of outputIndex for UTXO %s' % str(idx)) trusted_input = self.device.chip.getTrustedInput( prev_transaction, utxo_tx_index) self.trusted_inputs.append(trusted_input) # Hash check curr_pubkey = compress_public_key( device.chip.getWalletPublicKey(bip32_path)['publicKey']) pubkey_hash = bin_hash160(curr_pubkey) pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: text = "Error: different public key hashes for the BIP32 path and the UTXO" text += "locking script. Your signed transaction will not be validated by the network.\n" text += "pubkey_hash: %s\n" % str(pubkey_hash) text += "pubkey_hash_from_script: %s\n" % str( pubkey_hash_from_script) print(text) self.arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['tx_ouput_n']].script, 'pubkey': curr_pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo['tx_ouput_n'], 'txid': utxo['tx_hash'] }) self.amount -= int(tx_fee) self.amount = int(self.amount) arg_outputs = [{ 'address': dest_address, 'valueSat': self.amount }] # there will be multiple outputs soon self.new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage self.new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) try: for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address']) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') self.new_transaction.outputs.append(output) except Exception: raise # join all outputs - will be used by Ledger for signing transaction self.all_outputs_raw = self.new_transaction.serializeOutputs() starting = True # sign all inputs on Ledger and add inputs in the self.new_transaction object for serialization for idx, new_input in enumerate(self.arg_inputs): device.chip.startUntrustedTransaction(starting, idx, self.trusted_inputs, new_input['locking_script']) device.chip.finalizeInputFull(self.all_outputs_raw) sig = device.chip.untrustedHashSign(new_input['bip32_path'], lockTime=0) new_input['signature'] = sig inputTx = bitcoinInput() inputTx.prevOut = bytearray.fromhex( new_input['txid'])[::-1] + int.to_bytes( new_input['outputIndex'], 4, byteorder='little') inputTx.script = bytearray([len(sig)]) + sig + bytearray( [0x21]) + new_input['pubkey'] inputTx.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) self.new_transaction.inputs.append(inputTx) starting = False self.new_transaction.lockTime = bytearray([0, 0, 0, 0]) self.tx_raw = bytearray(self.new_transaction.serialize()) if self.tx_raw is not None: return (self.tx_raw, str(round(self.amount / 1e8, 8))) else: # transaction refused by user return (None, "")
updatedTransaction = hexlify(updatedTransaction) tx.update(updatedTransaction) self.client.bad = use2FA self.signing = False def check_proper_device(self): pubKey = DecodeBase58Check(self.master_public_keys["x/0'"])[45:] if not self.device_checked: self.plugin.handler.show_message("Checking device") try: nodeData = self.get_client().getWalletPublicKey("44'/2'/0'") except Exception, e: self.give_error(e, True) finally: self.plugin.handler.stop() pubKeyDevice = compress_public_key(nodeData["publicKey"]) self.device_checked = True if pubKey != pubKeyDevice: self.proper_device = False else: self.proper_device = True return self.proper_device def password_dialog(self, msg=None): if not msg: msg = _( "Do not enter your device PIN here !\r\n\r\n" "Your Ledger Wallet wants to talk to you and tell you a unique second factor code.\r\n" "For this to work, please open a text editor (on a different computer / device if you believe this computer is compromised) and put your cursor into it, unplug your Ledger Wallet and plug it back in.\r\n" "It should show itself to your computer as a keyboard and output the second factor along with a summary of the transaction it is signing into the text-editor.\r\n\r\n"
def sign_tx(hw_session: HwSessionInfo, utxos_to_spend: List[wallet_common.UtxoType], tx_outputs: List[wallet_common.TxOutputType], tx_fee): client = hw_session.hw_client rawtransactions = {} decodedtransactions = {} # Each of the UTXOs will become an input in the new transaction. For each of those inputs, create # a Ledger's 'trusted input', that will be used by the the device to sign a transaction. trusted_inputs = [] # arg_inputs: list of dicts # { # 'locking_script': <Locking script of the UTXO used as an input. Used in the process of signing # transaction.>, # 'outputIndex': <index of the UTXO within the previus transaction>, # 'txid': <hash of the previus transaction>, # 'bip32_path': <BIP32 path of the HW key controlling UTXO's destination>, # 'pubkey': <Public key obtained from the HW using the bip32_path.> # 'signature' <Signature obtained as a result of processing the input. It will be used as a part of the # unlocking script.> # } # Why do we need a locking script of the previous transaction? When hashing a new transaction before creating its # signature, all placeholders for input's unlocking script has to be filled with locking script of the # corresponding UTXO. Look here for the details: # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) arg_inputs = [] # A dictionary mapping bip32 path to a pubkeys obtained from the Ledger device - used to avoid # reading it multiple times for the same bip32 path bip32_to_address = {} # read previous transactins for utxo in utxos_to_spend: if utxo.txid not in rawtransactions: tx = hw_session.crownd_intf.getrawtransaction(utxo.txid, 1, skip_cache=False) if tx and tx.get('hex'): tx_raw = tx.get('hex') else: tx_raw = hw_session.crownd_intf.getrawtransaction( utxo.txid, 0, skip_cache=False) if tx_raw: rawtransactions[utxo.txid] = tx_raw decodedtransactions[utxo.txid] = tx amount = 0 starting = True for idx, utxo in enumerate(utxos_to_spend): amount += utxo.satoshis raw_tx = rawtransactions.get(utxo.txid) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + utxo.txid) else: raw_tx = bytearray.fromhex(raw_tx) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) data = decodedtransactions[utxo.txid] dip2_type = data.get("type", 0) if data['version'] == 3 and dip2_type != 0: # It's a DIP2 special TX with payload if "extraPayloadSize" not in data or "extraPayload" not in data: raise ValueError("Payload data missing in DIP2 transaction") if data["extraPayloadSize"] * 2 != len(data["extraPayload"]): raise ValueError( "extra_data_len (%d) does not match calculated length (%d)" % (data["extraPayloadSize"], len(data["extraPayload"]) * 2)) prev_transaction.extra_data = crown_utils.num_to_varint( data["extraPayloadSize"]) + bytes.fromhex(data["extraPayload"]) else: prev_transaction.extra_data = bytes() utxo_tx_index = utxo.output_index if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs): raise Exception('Incorrent value of outputIndex for UTXO %s' % str(idx)) trusted_input = client.getTrustedInput(prev_transaction, utxo_tx_index) trusted_inputs.append(trusted_input) bip32_path = utxo.bip32_path bip32_path = clean_bip32_path(bip32_path) pubkey = bip32_to_address.get(bip32_path) if not pubkey: pubkey = compress_public_key( client.getWalletPublicKey(bip32_path)['publicKey']) bip32_to_address[bip32_path] = pubkey pubkey_hash = bitcoin.bin_hash160(pubkey) # verify if the public key hash of the wallet's bip32 path is the same as specified in the UTXO locking script # if they differ, signature and public key we produce and are going to include in the unlocking script won't # match the locking script conditions - transaction will be rejected by the network pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: logging.error( "Error: different public key hashes for the BIP32 path %s (UTXO %s) and the UTXO locking " "script. Your signed transaction will not be validated by the network." % (bip32_path, str(idx))) arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo.output_index].script, 'pubkey': pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo.output_index, 'txid': utxo.txid }) amount -= int(tx_fee) amount = int(amount) new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) for out in tx_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script( out.address, hw_session.app_config.crown_network) output.amount = int.to_bytes(out.satoshis, 8, byteorder='little') new_transaction.outputs.append(output) # join all outputs - will be used by Ledger for sigining transaction all_outputs_raw = new_transaction.serializeOutputs() # sign all inputs on Ledger and add inputs in the new_transaction object for serialization for idx, new_input in enumerate(arg_inputs): client.startUntrustedTransaction(starting, idx, trusted_inputs, new_input['locking_script']) client.finalizeInputFull(all_outputs_raw) sig = client.untrustedHashSign(new_input['bip32_path'], lockTime=0) new_input['signature'] = sig input = bitcoinInput() input.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + \ int.to_bytes(new_input['outputIndex'], 4, byteorder='little') input.script = bytearray([len(sig)]) + sig + bytearray( [0x21]) + new_input['pubkey'] input.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) new_transaction.inputs.append(input) starting = False new_transaction.lockTime = bytearray([0, 0, 0, 0]) tx_raw = bytearray(new_transaction.serialize()) return tx_raw, amount
def prepare_transfer_tx(self, caller, bip32_path, utxos_to_spend, dest_address, tx_fee, rawtransactions): # For each UTXO create a Ledger 'trusted input' self.trusted_inputs = [] # https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand) self.arg_inputs = [] self.amount = 0 for idx, utxo in enumerate(utxos_to_spend): self.amount += int(utxo['value']) raw_tx = bytearray.fromhex(rawtransactions[utxo['tx_hash']]) if not raw_tx: raise Exception("Can't find raw transaction for txid: " + rawtransactions[utxo['tx_hash']]) # parse the raw transaction, so that we can extract the UTXO locking script we refer to prev_transaction = bitcoinTransaction(raw_tx) utxo_tx_index = utxo['tx_ouput_n'] if utxo_tx_index < 0 or utxo_tx_index > len( prev_transaction.outputs): raise Exception('Incorrect value of outputIndex for UTXO %s' % str(idx)) trusted_input = self.chip.getTrustedInput(prev_transaction, utxo_tx_index) self.trusted_inputs.append(trusted_input) # Hash check curr_pubkey = compress_public_key( self.chip.getWalletPublicKey(bip32_path)['publicKey']) pubkey_hash = bin_hash160(curr_pubkey) pubkey_hash_from_script = extract_pkh_from_locking_script( prev_transaction.outputs[utxo_tx_index].script) if pubkey_hash != pubkey_hash_from_script: text = "Error: different public key hashes for the BIP32 path and the UTXO" text += "locking script. Your signed transaction will not be validated by the network.\n" text += "pubkey_hash: %s\n" % str(pubkey_hash) text += "pubkey_hash_from_script: %s\n" % str( pubkey_hash_from_script) printDbg(text) self.arg_inputs.append({ 'locking_script': prev_transaction.outputs[utxo['tx_ouput_n']].script, 'pubkey': curr_pubkey, 'bip32_path': bip32_path, 'outputIndex': utxo['tx_ouput_n'], 'txid': utxo['tx_hash'] }) self.amount -= int(tx_fee) self.amount = int(self.amount) arg_outputs = [{ 'address': dest_address, 'valueSat': self.amount }] # there will be multiple outputs soon self.new_transaction = bitcoinTransaction( ) # new transaction object to be used for serialization at the last stage self.new_transaction.version = bytearray([0x01, 0x00, 0x00, 0x00]) try: for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address']) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') self.new_transaction.outputs.append(output) except Exception: raise # join all outputs - will be used by Ledger for signing transaction self.all_outputs_raw = self.new_transaction.serializeOutputs() self.mBox2 = QMessageBox(caller) messageText = "Check display of your hardware device<br><br><br>" messageText += "From bip32_path: <b>%s</b><br><br>" % str(bip32_path) messageText += "To PIVX Address: <b>%s</b><br><br>" % dest_address messageText += "PIV amount: <b>%s</b><br>" % str( round(self.amount / 1e8, 8)) messageText += "plus PIV for fee: <b>%s</b><br><br>" % str( round(int(tx_fee) / 1e8, 8)) self.mBox2.setText(messageText) self.mBox2.setIconPixmap( caller.tabMain.ledgerImg.scaledToHeight(200, Qt.SmoothTransformation)) self.mBox2.setWindowTitle("CHECK YOUR LEDGER") self.mBox2.setStandardButtons(QMessageBox.NoButton) self.mBox2.setMaximumWidth(500) self.mBox2.show() ThreadFuns.runInThread(self.signTxSign, (), self.signTxFinish)