def test_dsa_msg(self): msg = DashDsaMsg.from_hex(DSA_MSG) assert msg.nDenom == 2 assert type(msg.txCollateral) == str tx = Transaction(msg.txCollateral) assert type(tx) == Transaction assert bh2u(msg.serialize()) == DSA_MSG
def input_script(self, txin: PartialTxInput, *, estimate_size=False): if txin.script_type == 'p2pkh': return Transaction.get_preimage_script(txin) raise Exception("unsupported type %s" % txin.script_type)
def export_to_file(self, *, tx: Transaction = None): if tx is None: tx = self.tx if isinstance(tx, PartialTransaction): tx.finalize_psbt() if tx.is_complete(): name = 'signed_%s' % (tx.txid()[0:8]) extension = 'txn' default_filter = TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX else: name = self.wallet.basename() + time.strftime('-%Y%m%d-%H%M') extension = 'psbt' default_filter = TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX name = f'{name}.{extension}' fileName = self.main_window.getSaveFileName(_("Select where to save your transaction"), name, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, default_extension=extension, default_filter=default_filter) if not fileName: return if tx.is_complete(): # network tx hex with open(fileName, "w+") as f: network_tx_hex = tx.serialize_to_network() f.write(network_tx_hex + '\n') else: # if partial: PSBT bytes assert isinstance(tx, PartialTransaction) with open(fileName, "wb+") as f: f.write(tx.serialize_as_bytes()) self.show_message(_("Transaction exported successfully")) self.saved = True
def show_qr(self, *, tx: Transaction = None): if tx is None: tx = self.tx qr_data = tx.to_qr_data() try: self.main_window.show_qrcode(qr_data, 'Transaction', parent=self) except qrcode.exceptions.DataOverflowError: self.show_error(_('Failed to display QR code.') + '\n' + _('Transaction is too large in size.')) except Exception as e: self.show_error(_('Failed to display QR code.') + '\n' + repr(e))
def test_dsi_msg(self): msg = DashDsiMsg.from_hex(DSI_MSG) assert len(msg.vecTxDSIn) == 2 for txin in msg.vecTxDSIn: assert type(txin) == CTxIn assert type(msg.txCollateral) == str tx = Transaction(msg.txCollateral) assert type(tx) == Transaction assert len(msg.vecTxDSOut) == 2 for txout in msg.vecTxDSOut: assert type(txout) == CTxOut assert bh2u(msg.serialize()) == DSI_MSG
def tx_inputs(self, tx: Transaction, *, for_sig=False, keystore: 'TrezorKeyStore' = None): inputs = [] for txin in tx.inputs(): txinputtype = TxInputType() if txin.is_coinbase_input(): prev_hash = b"\x00" * 32 if txin.script_sig.startswith( b"\xc4") and txin.nsequence == 0xffffffff: prev_index = 1 else: prev_index = txin.nsequence else: if for_sig: assert isinstance(tx, PartialTransaction) assert isinstance(txin, PartialTxInput) assert keystore if len(txin.pubkeys) > 1: xpubs_and_deriv_suffixes = get_xpubs_and_der_suffixes_from_txinout( tx, txin) multisig = self._make_multisig( txin.num_sig, xpubs_and_deriv_suffixes) else: multisig = None script_type = self.get_trezor_input_script_type( txin.script_type) txinputtype = TxInputType(script_type=script_type, multisig=multisig) my_pubkey, full_path = keystore.find_my_pubkey_in_txinout( txin) if full_path: txinputtype.address_n = full_path prev_hash = txin.prevout.txid prev_index = txin.prevout.out_idx if txin.value_sats() is not None: txinputtype.amount = txin.value_sats() txinputtype.prev_hash = prev_hash txinputtype.prev_index = prev_index if txin.script_sig is not None: txinputtype.script_sig = txin.script_sig txinputtype.sequence = txin.nsequence inputs.append(txinputtype) return inputs
def export_to_file(self, *, tx: Transaction = None): if tx is None: tx = self.tx if isinstance(tx, PartialTransaction): tx.finalize_psbt() txid = tx.txid() suffix = txid[0:8] if txid is not None else time.strftime( '%Y%m%d-%H%M') if tx.is_complete(): extension = 'txn' default_filter = TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX else: extension = 'psbt' default_filter = TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX name = f'{self.wallet.basename()}-{suffix}.{extension}' fileName = getSaveFileName( parent=self, title=_("Select where to save your transaction"), filename=name, filter=TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, default_extension=extension, default_filter=default_filter, config=self.config, ) if not fileName: return if tx.is_complete(): # network tx hex with open(fileName, "w+") as f: network_tx_hex = tx.serialize_to_network() f.write(network_tx_hex + '\n') else: # if partial: PSBT bytes assert isinstance(tx, PartialTransaction) with open(fileName, "wb+") as f: f.write(tx.serialize_as_bytes()) self.show_message(_("Transaction exported successfully")) self.saved = True
def sign_transaction(self, tx, password): if tx.is_complete(): return inputs = [] inputsPaths = [] chipInputs = [] redeemScripts = [] changePath = "" output = None p2shTransaction = False pin = "" client_ledger = self.get_client( ) # prompt for the PIN before displaying the dialog if necessary client_electrum = self.get_client_electrum() assert client_electrum # Fetch inputs of the transaction to sign for txin in tx.inputs(): if txin.is_coinbase_input(): self.give_error( "Coinbase not supported") # should never happen if txin.script_type in ['p2sh']: p2shTransaction = True my_pubkey, full_path = self.find_my_pubkey_in_txinout(txin) if not full_path: self.give_error("No matching pubkey for sign_transaction" ) # should never happen full_path = convert_bip32_intpath_to_strpath(full_path)[2:] redeemScript = Transaction.get_preimage_script(txin) txin_prev_tx = txin.utxo if txin_prev_tx is None: raise UserFacingException( _('Missing previous tx for legacy input.')) txin_prev_tx_raw = txin_prev_tx.serialize( ) if txin_prev_tx else None txin_prev_tx.deserialize() tx_type = txin_prev_tx.tx_type extra_payload = txin_prev_tx.extra_payload extra_data = b'' if tx_type and extra_payload: extra_payload = extra_payload.serialize() extra_data = bfh(var_int(len(extra_payload))) + extra_payload inputs.append([ txin_prev_tx_raw, txin.prevout.out_idx, redeemScript, txin.prevout.txid.hex(), my_pubkey, txin.nsequence, txin.value_sats(), extra_data ]) inputsPaths.append(full_path) # Sanity check if p2shTransaction: for txin in tx.inputs(): if txin.script_type != 'p2sh': self.give_error( "P2SH / regular input mixed in same transaction not supported" ) # should never happen txOutput = var_int(len(tx.outputs())) for o in tx.outputs(): txOutput += int_to_hex(o.value, 8) script = o.scriptpubkey.hex() txOutput += var_int(len(script) // 2) txOutput += script txOutput = bfh(txOutput) if not client_electrum.supports_multi_output(): if len(tx.outputs()) > 2: self.give_error( "Transaction with more than 2 outputs not supported") for txout in tx.outputs(): if client_electrum.is_hw1( ) and txout.address and not is_b58_address(txout.address): self.give_error( _("This {} device can only send to base58 addresses."). format(self.device)) if not txout.address: if client_electrum.is_hw1(): self.give_error( _("Only address outputs are supported by {}").format( self.device)) # note: max_size based on https://github.com/LedgerHQ/ledger-app-btc/commit/3a78dee9c0484821df58975803e40d58fbfc2c38#diff-c61ccd96a6d8b54d48f54a3bc4dfa7e2R26 validate_op_return_output(txout, max_size=190) # Output "change" detection # - only one output and one change is authorized (for hw.1 and nano) # - at most one output can bypass confirmation (~change) (for all) if not p2shTransaction: has_change = False any_output_on_change_branch = is_any_tx_output_on_change_branch(tx) for txout in tx.outputs(): if txout.is_mine and len(tx.outputs()) > 1 \ and not has_change: # prioritise hiding outputs on the 'change' branch from user # because no more than one change address allowed if txout.is_change == any_output_on_change_branch: my_pubkey, changePath = self.find_my_pubkey_in_txinout( txout) assert changePath changePath = convert_bip32_intpath_to_strpath( changePath)[2:] has_change = True else: output = txout.address else: output = txout.address if not self.get_client_electrum().canAlternateCoinVersions: v, h = b58_address_to_hash160(output) if v == constants.net.ADDRTYPE_P2PKH: output = hash160_to_b58_address(h, 0) self.handler.show_message( _("Confirm Transaction on your Ledger device...")) try: # Get trusted inputs from the original transactions for utxo in inputs: sequence = int_to_hex(utxo[5], 4) if (not p2shTransaction ) or client_electrum.supports_multi_output(): txtmp = bitcoinTransaction(bfh(utxo[0])) txtmp.extra_data = utxo[7] trustedInput = client_ledger.getTrustedInput( txtmp, utxo[1]) trustedInput['sequence'] = sequence chipInputs.append(trustedInput) if p2shTransaction: redeemScripts.append(bfh(utxo[2])) else: redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = bfh(utxo[3])[::-1] tmp += bfh(int_to_hex(utxo[1], 4)) chipInputs.append({'value': tmp, 'sequence': sequence}) redeemScripts.append(bfh(utxo[2])) # Sign all inputs firstTransaction = True inputIndex = 0 rawTx = tx.serialize_to_network() client_ledger.enableAlternate2fa(False) while inputIndex < len(inputs): client_ledger.startUntrustedTransaction( firstTransaction, inputIndex, chipInputs, redeemScripts[inputIndex], version=tx.version) # we don't set meaningful outputAddress, amount and fees # as we only care about the alternateEncoding==True branch outputData = client_ledger.finalizeInput( b'', 0, 0, changePath, bfh(rawTx)) outputData['outputData'] = txOutput if outputData['confirmationNeeded']: outputData['address'] = output self.handler.finished() # do the authenticate dialog and get pin: pin = self.handler.get_auth(outputData, client=client_electrum) if not pin: raise UserWarning() self.handler.show_message( _("Confirmed. Signing Transaction...")) else: # Sign input with the provided PIN inputSignature = client_ledger.untrustedHashSign( inputsPaths[inputIndex], pin, lockTime=tx.locktime) inputSignature[0] = 0x30 # force for 1.4.9+ my_pubkey = inputs[inputIndex][4] tx.add_signature_to_txin(txin_idx=inputIndex, signing_pubkey=my_pubkey.hex(), sig=inputSignature.hex()) inputIndex = inputIndex + 1 firstTransaction = False except UserWarning: self.handler.show_error(_('Cancelled by user')) return except BTChipException as e: if e.sw in (0x6985, 0x6d00): # cancelled by user return elif e.sw == 0x6982: raise # pin lock. decorator will catch it else: self.logger.exception('') self.give_error(e, True) except BaseException as e: self.logger.exception('') self.give_error(e, True) finally: self.handler.finished()
def test_verify_ok_t_tx(self): """Actually mined 64 byte tx should not raise.""" t_tx = Transaction(VALID_64_BYTE_TX) t_tx_hash = t_tx.txid() self.assertEqual(MERKLE_ROOT, SPV.hash_merkle_root(MERKLE_BRANCH, t_tx_hash, 3))