def input_script(self, txin, estimate_size=False): type_ = txin.type() if type_ == ScriptType.P2PKH: return Transaction.get_preimage_script_bytes( txin).hex() if type_ == ScriptType.MULTISIG_P2SH: # Multisig verification has partial support, but is # disabled. This is the expected serialization though, so we # leave it here until we activate it. return '00' + \ push_script(Transaction.get_preimage_script_bytes(txin).hex()) raise RuntimeError(f'unsupported type {type_}')
def sign_transaction(self, tx: Transaction, password: str, tx_context: TransactionContext) -> None: if tx.is_complete(): return assert self.handler is not None client = self.get_client() inputs: List[YInput] = [] inputsPaths = [] chipInputs = [] redeemScripts = [] signatures = [] changePath = "" changeAmount = None output = None outputAmount = None pin = "" self.get_client( ) # prompt for the PIN before displaying the dialog if necessary # Fetch inputs of the transaction to sign foundP2SHSpend = False allSpendsAreP2SH = True for txin in tx.inputs: foundP2SHSpend = foundP2SHSpend or txin.type( ) == ScriptType.MULTISIG_P2SH allSpendsAreP2SH = allSpendsAreP2SH and txin.type( ) == ScriptType.MULTISIG_P2SH for i, x_pubkey in enumerate(txin.x_pubkeys): if self.is_signature_candidate(x_pubkey): txin_xpub_idx = i inputPath = "%s/%d/%d" % (self.get_derivation()[2:], *x_pubkey.bip32_path()) break else: self.give_error("No matching x_key for sign_transaction" ) # should never happen inputs.append( YInput(txin.value, Transaction.get_preimage_script_bytes(txin), txin_xpub_idx, txin.sequence)) inputsPaths.append(inputPath) # Sanity check if foundP2SHSpend and not allSpendsAreP2SH: self.give_error( "P2SH / regular input mixed in same transaction not supported") # Concatenate all the tx outputs as binary txOutput = pack_list(tx.outputs, XTxOutput.to_bytes) # Recognize outputs - only one output and one change is authorized if not foundP2SHSpend: keystore_fingerprint = self.get_fingerprint() assert tx.output_info is not None for tx_output, output_metadatas in zip(tx.outputs, tx.output_info): info = output_metadatas.get(keystore_fingerprint) if (info is not None) and len(tx.outputs) != 1: key_derivation, xpubs, m = info key_subpath = compose_chain_string(key_derivation)[1:] changePath = self.get_derivation()[2:] + key_subpath changeAmount = tx_output.value else: output = classify_tx_output(tx_output) outputAmount = tx_output.value self.handler.show_message( _("Confirm Transaction on your Ledger device...")) try: for i, utxo in enumerate(inputs): txin = tx.inputs[i] sequence = int_to_hex(utxo.sequence, 4) prevout_bytes = txin.prevout_bytes() value_bytes = prevout_bytes + pack_le_int64(utxo.value) chipInputs.append({ 'value': value_bytes, 'witness': True, 'sequence': sequence }) redeemScripts.append(utxo.script_sig) # Sign all inputs inputIndex = 0 rawTx = tx.serialize() self.get_client().enableAlternate2fa(False) self.get_client().startUntrustedTransaction( True, inputIndex, chipInputs, redeemScripts[inputIndex]) outputData = self.get_client().finalizeInputFull(txOutput) outputData['outputData'] = txOutput transactionOutput = outputData['outputData'] if outputData['confirmationNeeded']: outputData['address'] = cast(ScriptTemplate, output).to_string() self.handler.finished() # the authenticate dialog and returns pin auth_pin = self.handler.get_auth(self, outputData) if not auth_pin: raise UserWarning() pin = auth_pin self.handler.show_message( _("Confirmed. Signing Transaction...")) while inputIndex < len(inputs): singleInput = [chipInputs[inputIndex]] self.get_client().startUntrustedTransaction( False, 0, singleInput, redeemScripts[inputIndex]) inputSignature = self.get_client().untrustedHashSign( inputsPaths[inputIndex], pin, lockTime=tx.locktime, sighashType=tx.nHashType()) inputSignature[0] = 0x30 # force for 1.4.9+ signatures.append(inputSignature) inputIndex = inputIndex + 1 except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw == 0x6985: # cancelled by user return else: logger.exception("") self.give_error(e, True) except Exception as e: logger.exception("") self.give_error(e, True) finally: self.handler.finished() for txin, input, signature in zip(tx.inputs, inputs, signatures): txin.signatures[input.txin_xpub_idx] = signature