def prepare_transfer_tx_bulk(self, caller, rewardsArray, dest_address, tx_fee, isTestnet=False): with self.lock: # 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 num_of_sigs = sum([len(mnode['utxos']) for mnode in rewardsArray]) curr_utxo_checked = 0 for mnode in rewardsArray: # Add proper HW path (for current device) on each utxo if isTestnet: mnode['path'] = MPATH_TESTNET + mnode['path'] else: mnode['path'] = MPATH + mnode['path'] # Create a TX input with each utxo for utxo in mnode['utxos']: self.append_inputs_to_TX(utxo, mnode['path']) # 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) for o in arg_outputs: output = bitcoinOutput() output.script = compose_tx_locking_script(o['address'], isTestnet) output.amount = int.to_bytes(o['valueSat'], 8, byteorder='little') self.new_transaction.outputs.append(output) 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)) 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 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, "")
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)