Example #1
0
class TxAnalyzer(BaseDock):

    tool_name = 'Transaction Analyzer'
    description = 'Deserializes transactions and verifies their inputs.'
    is_large = True
    category = Category.Tx

    def __init__(self, handler):
        super(TxAnalyzer, self).__init__(handler)
        self.raw_tx_edit.textChanged.emit()

    def init_data(self):
        self.tx = None

    def init_actions(self):
        deserialize = ('Deserialize', self.deserialize_raw)
        verify = ('Verify inputs', self.do_verify_inputs)
        self.advertised_actions[RAW_TX] = [deserialize, verify]

    def create_layout(self):
        form = QFormLayout()

        self.raw_tx_edit = QPlainTextEdit()
        self.raw_tx_edit.setTabChangesFocus(True)
        self.raw_tx_edit.setFont(monospace_font)
        self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu)
        self.raw_tx_edit.textChanged.connect(self.check_raw_tx)
        self.setFocusProxy(self.raw_tx_edit)

        self.raw_tx_invalid = QLabel('Cannot parse transaction.')
        self.raw_tx_invalid.setProperty('hasError', True)

        self.tabs = tabs = QTabWidget()
        tabs.addTab(self.create_deserialize_tab(), 'Deserialize')
        tabs.addTab(self.create_verify_tab(), 'Verify')

        tabs.setTabToolTip(0, 'View the transaction in human-readable form')
        tabs.setTabToolTip(1,
                           'Download previous transactions and verify inputs')

        form.addRow('Raw Tx:', self.raw_tx_edit)
        form.addRow(self.raw_tx_invalid)
        form.addRow(tabs)
        return form

    def create_deserialize_tab(self):
        form = QFormLayout()

        self.deserialize_button = QPushButton('Deserialize')
        self.deserialize_button.clicked.connect(self.deserialize)
        btn_hbox = floated_buttons([self.deserialize_button])

        self.tx_widget = TxWidget()
        self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(
            self.tx_widget.inputs_tree.customContextMenu)
        self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(
            self.inputs_context_menu)

        form.addRow(self.tx_widget)

        w = QWidget()
        w.setLayout(form)
        return w

    def create_verify_tab(self):
        form = QFormLayout()
        form.setRowWrapPolicy(QFormLayout.WrapLongRows)

        self.inputs_box = QSpinBox()

        self.verify_button = QPushButton('Verify')
        self.verify_button.clicked.connect(self.verify_input)

        self.verify_all_button = QPushButton('Verify All')
        self.verify_all_button.clicked.connect(self.verify_inputs)

        self.result_edit = QLineEdit()
        self.result_edit.setReadOnly(True)

        self.inputs_table = InputStatusTable()

        form.addRow('Verify Input:',
                    floated_buttons([self.inputs_box, self.verify_button]))
        form.addRow(floated_buttons([self.verify_all_button]))
        form.addRow('Result:', self.result_edit)
        form.addRow(self.inputs_table)

        w = QWidget()
        w.setLayout(form)
        return w

    def context_menu(self, position):
        menu = QMenu()
        if self.tx:
            txt = str(self.raw_tx_edit.toPlainText())
            self.handler.add_plugin_actions(self, menu, RAW_TX, txt)

        menu.exec_(self.mapToGlobal(position))

    def inputs_context_menu(self, position):
        inputs = self.tx_widget.inputs_tree

        def inputs_context_verify():
            self.tabs.setCurrentIndex(1)
            row = inputs.view.selectedIndexes()[0].row()
            self.do_verify_input(self.tx, row)

        menu = QMenu()
        if self.tx:
            menu = inputs.context_menu()
            menu.addAction('Verify script', inputs_context_verify)

        menu.exec_(inputs.view.viewport().mapToGlobal(position))

    def clear(self):
        self.result_edit.clear()
        self.tx_widget.clear()
        self.inputs_table.clear()

    def check_raw_tx(self):
        txt = str(self.raw_tx_edit.toPlainText())
        # Variable substitution
        if txt.startswith('$'):
            var_value = self.handler.get_plugin('Variables').dock.get_key(
                txt[1:])
            if var_value:
                self.raw_tx_edit.setPlainText(var_value)
            return
        tx = None
        valid = True
        try:
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            valid = False

        self.tx = tx
        self.deserialize_button.setEnabled(valid)
        self.inputs_box.setEnabled(valid)
        self.verify_button.setEnabled(valid)
        self.verify_all_button.setEnabled(valid)
        self.tx_widget.setEnabled(valid)
        self.clear()
        if valid:
            self.raw_tx_invalid.hide()
            self.inputs_box.setRange(0, len(tx.vin) - 1)
            self.deserialize()
        elif txt:
            self.raw_tx_invalid.show()
        else:
            self.raw_tx_invalid.hide()

    def deserialize_raw(self, txt):
        """Deserialize a raw transaction."""
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        self.deserialize()

    def deserialize(self):
        self.clear()
        self.tx_widget.set_tx(self.tx)
        self.inputs_table.set_tx(self.tx)
        self.status_message('Deserialized transaction {}'.format(
            bitcoin.core.b2lx(self.tx.GetHash())))

    def do_verify_input(self, tx, in_idx):
        raw_prev_tx = None
        tx_in = tx.vin[in_idx]
        txid = b2lx(tx_in.prevout.hash)
        prev_out_n = tx_in.prevout.n

        try:
            raw_prev_tx = self.handler.download_blockchain_data(
                'raw_transaction', txid)
        except Exception as e:
            self.status_message(str(e), True)
            return False

        try:
            prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex'))
            result = bitcoin.core.scripteval.VerifyScript(
                tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx,
                in_idx)
            self.result_edit.setText(
                'Successfully verified input {}'.format(in_idx))
            self.inputs_table.set_verified(in_idx, True)
        except Exception as e:
            self.result_edit.setText(str(e))
            self.inputs_table.set_verified(in_idx, False)
            self.status_message(str(e), True)
            return False

        return True

    def do_verify_inputs(self, txt):
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        tx = Transaction.deserialize(txt.decode('hex'))
        failed_inputs = []
        self.result_edit.setText('Verifying...')
        for i in range(len(tx.vin)):
            if not self.do_verify_input(tx, i):
                failed_inputs.append(i)

        result = 'Successfully verified all inputs.'
        ret_val = True
        if failed_inputs:
            result = 'Failed to verify inputs: {}'.format(failed_inputs)
            ret_val = False
        if len(tx.vin) == 0:
            result = 'Transaction has no inputs.'
        self.result_edit.setText(result)
        return ret_val

    def verify_input(self):
        tx = None
        try:
            txt = str(self.raw_tx_edit.toPlainText())
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            self.status_message('Could not deserialize transaction.', True)
            return
        in_idx = self.inputs_box.value()
        if in_idx >= len(tx.vin):
            self.status_message('Input {} does not exist.'.format(in_idx),
                                True)
            return

        self.do_verify_input(tx, in_idx)

    def verify_inputs(self):
        txt = str(self.raw_tx_edit.toPlainText())
        self.do_verify_inputs(txt)

    def refresh_data(self):
        if self.tx:
            self.deserialize()

    def on_option_changed(self, key):
        if key == 'chainparams':
            self.raw_tx_edit.textChanged.emit()
Example #2
0
class TxAnalyzer(BaseDock):
    tool_name = 'Transaction Analyzer'
    description = 'Deserializes transactions and verifies their inputs.'
    is_large = True
    category = Category.Tx

    TAB_DESERIALIZE = 0
    TAB_VERIFY = 1

    def __init__(self, handler):
        super(TxAnalyzer, self).__init__(handler)
        self.raw_tx_edit.textChanged.emit()

    @augmenter
    def item_actions(self, arg):
        actions = [
            ItemAction(self.tool_name, 'Transaction', 'Deserialize', self.deserialize_item),
            ItemAction(self.tool_name, 'Transaction', 'Verify inputs', self.verify_item_inputs)
        ]
        return actions

    def init_data(self):
        self.tx = None

    def create_layout(self):
        self.raw_tx_edit = QPlainTextEdit()
        self.raw_tx_edit.setWhatsThis('Enter a serialized transaction here. If you have a raw transaction stored in the Variables tool, you can enter the variable name preceded by a "$", and the variable value will be substituted automatically.')
        self.raw_tx_edit.setTabChangesFocus(True)
        self.raw_tx_edit.setFont(monospace_font)
        self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu)
        self.raw_tx_edit.textChanged.connect(self.check_raw_tx)
        self.handler.substitute_variables(self.raw_tx_edit)
        self.setFocusProxy(self.raw_tx_edit)

        self.raw_tx_invalid = QLabel('Cannot parse transaction.')
        self.raw_tx_invalid.setProperty('hasError', True)

        self.tabs = tabs = QTabWidget()
        tabs.addTab(self.create_deserialize_tab(), 'Deserialize')
        tabs.addTab(self.create_verify_tab(), 'Verify')

        tabs.setTabToolTip(self.TAB_DESERIALIZE, 'View the transaction in human-readable form')
        tabs.setTabToolTip(self.TAB_VERIFY, 'Download previous transactions and verify inputs')

        form = QFormLayout()
        form.setContentsMargins(0, 0, 0, 2)
        form.addRow('Raw Tx:', self.raw_tx_edit)
        vbox = QVBoxLayout()
        vbox.addLayout(form)
        vbox.addWidget(self.raw_tx_invalid)
        vbox.addWidget(tabs)
        return vbox

    def create_deserialize_tab(self):
        self.tx_widget = TxWidget()
        self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu)
        self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu)
        self.tx_widget.outputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.outputs_tree.customContextMenu)
        self.tx_widget.outputs_tree.view.customContextMenuRequested.connect(self.outputs_context_menu)

        vbox = QVBoxLayout()
        vbox.addWidget(self.tx_widget)

        w = QWidget()
        w.setLayout(vbox)
        return w

    def create_verify_tab(self):
        form = QFormLayout()
        form.setRowWrapPolicy(QFormLayout.WrapLongRows)

        self.inputs_box = QSpinBox()
        self.inputs_box.setToolTip('Input to verify')

        self.verify_button = QPushButton('Verify')
        self.verify_button.clicked.connect(self.verify_input)
        self.verify_button.setToolTip('Verify an input')
        self.verify_button.setWhatsThis('This button will attempt to verify an input.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.')

        self.verify_all_button = QPushButton('Verify All')
        self.verify_all_button.clicked.connect(self.verify_inputs)
        self.verify_all_button.setToolTip('Verify all inputs')
        self.verify_all_button.setWhatsThis('This button will attempt to verify all inputs.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.')

        self.result_edit = QLineEdit()
        self.result_edit.setToolTip('Verification result')
        self.result_edit.setWhatsThis('The result of verifying an input is shown here.')
        self.result_edit.setReadOnly(True)

        self.inputs_table_model = InputsStatusModel()
        self.inputs_table = QTableView()
        self.inputs_table.setModel(self.inputs_table_model)
        self.inputs_table.horizontalHeader().setResizeMode(0, QHeaderView.ResizeToContents)
        self.inputs_table.horizontalHeader().setResizeMode(2, QHeaderView.Stretch)
        self.inputs_table.verticalHeader().setVisible(False)
        for i in [self.inputs_table.horizontalHeader(), self.inputs_table.verticalHeader()]:
            i.setHighlightSections(False)
        self.inputs_table.setToolTip('Verification results')
        self.inputs_table.setWhatsThis('This table displays which inputs you have verified for the transaction being analyzed.')

        form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button]))
        form.addRow(floated_buttons([self.verify_all_button]))
        form.addRow('Result:', self.result_edit)
        form.addRow(self.inputs_table)

        w = QWidget()
        w.setLayout(form)
        return w

    def context_menu(self, position):
        menu = QMenu()
        if self.tx:
            txt = str(self.raw_tx_edit.toPlainText())
            self.handler.add_plugin_actions(self, menu, txt)

        menu.exec_(self.raw_tx_edit.viewport().mapToGlobal(position))

    def inputs_context_menu(self, position):
        inputs = self.tx_widget.inputs_tree
        if not len(inputs.view.selectedIndexes()) or not self.tx:
            return

        def inputs_context_verify():
            self.tabs.setCurrentIndex(self.TAB_VERIFY)
            row = inputs.view.selectedIndexes()[0].row()
            self.do_verify_input(self.tx, row)

        menu = inputs.context_menu()
        if not self.tx.is_coinbase():
            menu.addAction('Verify script', inputs_context_verify)
        self.handler.add_plugin_actions(self, menu, str(inputs.model.data(inputs.view.selectedIndexes()[2]).toString()))

        menu.exec_(inputs.view.viewport().mapToGlobal(position))

    def outputs_context_menu(self, position):
        outputs = self.tx_widget.outputs_tree
        if not len(outputs.view.selectedIndexes()) or not self.tx:
            return

        menu = outputs.context_menu()
        self.handler.add_plugin_actions(self, menu, str(outputs.model.data(outputs.view.selectedIndexes()[1]).toString()))
        menu.exec_(outputs.view.viewport().mapToGlobal(position))

    def clear(self):
        self.result_edit.clear()
        self.tx_widget.clear()
        self.inputs_table_model.clear()

    def check_raw_tx(self):
        txt = str(self.raw_tx_edit.toPlainText())
        # Variable substitution
        if txt.startswith('$'):
            return
        tx = None
        valid = True
        try:
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            valid = False

        self.tx = tx
        self.inputs_box.setEnabled(valid)
        self.verify_button.setEnabled(valid)
        self.verify_all_button.setEnabled(valid)
        self.tx_widget.setEnabled(valid)
        self.clear()
        if valid:
            self.raw_tx_invalid.hide()
            self.inputs_box.setRange(0, len(tx.vin) - 1)
            self.deserialize()
        elif txt:
            self.raw_tx_invalid.show()
        else:
            self.raw_tx_invalid.hide()

    def deserialize_raw(self, txt):
        """Deserialize a raw transaction."""
        self.needsFocus.emit()
        self.tabs.setCurrentIndex(self.TAB_DESERIALIZE)
        self.raw_tx_edit.setPlainText(txt)

    def deserialize_item(self, item):
        """Deserialize a Transaction item."""
        self.deserialize_raw(item.raw())

    def deserialize(self):
        self.tx_widget.set_tx(self.tx)
        self.inputs_table_model.set_tx(self.tx)
        self.info('Deserialized transaction %s' % bitcoin.core.b2lx(self.tx.GetHash()))

    def do_verify_input(self, tx, in_idx):
        if tx.is_coinbase():
            self.result_edit.setText('Error: Cannot verify coinbase transactions.')
            self.error('Attempted to verify coinbase transaction.')
            return False
        raw_prev_tx = None
        tx_in = tx.vin[in_idx]
        txid = b2lx(tx_in.prevout.hash)
        prev_out_n = tx_in.prevout.n

        try:
            raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid)
        except Exception as e:
            self.error(str(e))
            return False

        try:
            prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex'))
            result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx)
            self.result_edit.setText('Successfully verified input {}'.format(in_idx))
            self.inputs_table_model.set_verified(in_idx, True)
        except Exception as e:
            self.result_edit.setText(str(e))
            self.inputs_table_model.set_verified(in_idx, False)
            self.error(str(e))
            return False

        return True

    def do_verify_inputs(self, tx):
        if tx.is_coinbase():
            self.result_edit.setText('Error: Cannot verify coinbase transactions.')
            self.error('Attempted to verify coinbase transaction.')
            return False
        failed_inputs = []
        self.result_edit.setText('Verifying...')
        for i in range(len(tx.vin)):
            if not self.do_verify_input(tx, i):
                failed_inputs.append(i)

        result = 'Successfully verified all inputs.'
        ret_val = True
        if failed_inputs:
            result = 'Failed to verify inputs: {}'.format(failed_inputs)
            ret_val = False
        if len(tx.vin) == 0:
            result = 'Transaction has no inputs.'
        self.result_edit.setText(result)
        return ret_val

    def verify_input(self):
        in_idx = self.inputs_box.value()
        if in_idx >= len(self.tx.vin):
            self.error('Input {} does not exist.'.format(in_idx))
            return

        self.do_verify_input(self.tx, in_idx)

    def verify_inputs(self):
        self.do_verify_inputs(self.tx)

    def verify_item_inputs(self, item):
        self.deserialize_item(item)
        self.tabs.setCurrentIndex(self.TAB_VERIFY)
        self.verify_all_button.animateClick()

    def on_option_changed(self, key):
        if key == 'chainparams':
            self.check_raw_tx()
Example #3
0
class TxAnalyzer(BaseDock):

    tool_name = 'Transaction Analyzer'
    description = 'Deserializes transactions and verifies their inputs.'
    is_large = True
    category = Category.Tx

    def __init__(self, handler):
        super(TxAnalyzer, self).__init__(handler)
        self.raw_tx_edit.textChanged.emit()

    @augmenter
    def item_actions(self, arg):
        actions = [
            ItemAction(self.tool_name, 'Transaction', 'Deserialize', self.deserialize_item),
            ItemAction(self.tool_name, 'Transaction', 'Verify inputs', self.verify_item_inputs)
        ]
        return actions

    def init_data(self):
        self.tx = None

    def create_layout(self):
        form = QFormLayout()

        self.raw_tx_edit = QPlainTextEdit()
        self.raw_tx_edit.setWhatsThis('Enter a serialized transaction here. If you have a raw transaction stored in the Variables tool, you can enter the variable name preceded by a "$", and the variable value will be substituted automatically.')
        self.raw_tx_edit.setTabChangesFocus(True)
        self.raw_tx_edit.setFont(monospace_font)
        self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu)
        self.raw_tx_edit.textChanged.connect(self.check_raw_tx)
        self.setFocusProxy(self.raw_tx_edit)

        self.raw_tx_invalid = QLabel('Cannot parse transaction.')
        self.raw_tx_invalid.setProperty('hasError', True)

        self.tabs = tabs = QTabWidget()
        tabs.addTab(self.create_deserialize_tab(), 'Deserialize')
        tabs.addTab(self.create_verify_tab(), 'Verify')

        tabs.setTabToolTip(0, 'View the transaction in human-readable form')
        tabs.setTabToolTip(1, 'Download previous transactions and verify inputs')

        form.addRow('Raw Tx:', self.raw_tx_edit)
        form.addRow(self.raw_tx_invalid)
        form.addRow(tabs)
        return form

    def create_deserialize_tab(self):
        form = QFormLayout()

        self.deserialize_button = QPushButton('Deserialize')
        self.deserialize_button.clicked.connect(self.deserialize)
        btn_hbox = floated_buttons([self.deserialize_button])

        self.tx_widget = TxWidget()
        self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu)
        self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu)
        self.tx_widget.outputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.outputs_tree.customContextMenu)
        self.tx_widget.outputs_tree.view.customContextMenuRequested.connect(self.outputs_context_menu)

        form.addRow(self.tx_widget)

        w = QWidget()
        w.setLayout(form)
        return w

    def create_verify_tab(self):
        form = QFormLayout()
        form.setRowWrapPolicy(QFormLayout.WrapLongRows)

        self.inputs_box = QSpinBox()
        self.inputs_box.setToolTip('Input to verify')

        self.verify_button = QPushButton('Verify')
        self.verify_button.clicked.connect(self.verify_input)
        self.verify_button.setToolTip('Verify an input')
        self.verify_button.setWhatsThis('This button will attempt to verify an input.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.')

        self.verify_all_button = QPushButton('Verify All')
        self.verify_all_button.clicked.connect(self.verify_inputs)
        self.verify_all_button.setToolTip('Verify all inputs')
        self.verify_all_button.setWhatsThis('This button will attempt to verify all inputs.\n\nIf no plugin is available to retrieve blockchain data, such as the "Blockchain" or "Wallet RPC" plugins, this will not function. The plugin used to retrieve blockchain data can be changed in the Settings dialog.')

        self.result_edit = QLineEdit()
        self.result_edit.setToolTip('Verification result')
        self.result_edit.setWhatsThis('The result of verifying an input is shown here.')
        self.result_edit.setReadOnly(True)

        self.inputs_table = InputStatusTable()
        self.inputs_table.setToolTip('Verification results')
        self.inputs_table.setWhatsThis('This table displays which inputs you have verified for the transaction being analyzed.')

        form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button]))
        form.addRow(floated_buttons([self.verify_all_button]))
        form.addRow('Result:', self.result_edit)
        form.addRow(self.inputs_table)

        w = QWidget()
        w.setLayout(form)
        return w

    def context_menu(self, position):
        menu = QMenu()
        if self.tx:
            txt = str(self.raw_tx_edit.toPlainText())
            self.handler.add_plugin_actions(self, menu, txt)

        menu.exec_(self.raw_tx_edit.viewport().mapToGlobal(position))

    def inputs_context_menu(self, position):
        inputs = self.tx_widget.inputs_tree
        if not len(inputs.view.selectedIndexes()) or not self.tx:
            return

        def inputs_context_verify():
            self.tabs.setCurrentIndex(1)
            row = inputs.view.selectedIndexes()[0].row()
            self.do_verify_input(self.tx, row)

        menu = inputs.context_menu()
        if not self.tx.is_coinbase():
            menu.addAction('Verify script', inputs_context_verify)
        self.handler.add_plugin_actions(self, menu, str(inputs.model.data(inputs.view.selectedIndexes()[2]).toString()))

        menu.exec_(inputs.view.viewport().mapToGlobal(position))

    def outputs_context_menu(self, position):
        outputs = self.tx_widget.outputs_tree
        if not len(outputs.view.selectedIndexes()) or not self.tx:
            return

        menu = outputs.context_menu()
        self.handler.add_plugin_actions(self, menu, str(outputs.model.data(outputs.view.selectedIndexes()[1]).toString()))
        menu.exec_(outputs.view.viewport().mapToGlobal(position))

    def clear(self):
        self.result_edit.clear()
        self.tx_widget.clear()
        self.inputs_table.clear()

    def check_raw_tx(self):
        txt = str(self.raw_tx_edit.toPlainText())
        # Variable substitution
        if txt.startswith('$'):
            var_value = self.handler.get_plugin('Variables').ui.get_key(txt[1:])
            if var_value:
                self.raw_tx_edit.setPlainText(var_value)
            return
        tx = None
        valid = True
        try:
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            valid = False

        self.tx = tx
        self.deserialize_button.setEnabled(valid)
        self.inputs_box.setEnabled(valid)
        self.verify_button.setEnabled(valid)
        self.verify_all_button.setEnabled(valid)
        self.tx_widget.setEnabled(valid)
        self.clear()
        if valid:
            self.raw_tx_invalid.hide()
            self.inputs_box.setRange(0, len(tx.vin) - 1)
            self.deserialize()
        elif txt:
            self.raw_tx_invalid.show()
        else:
            self.raw_tx_invalid.hide()

    def deserialize_raw(self, txt):
        """Deserialize a raw transaction."""
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        self.deserialize()

    def deserialize_item(self, item):
        """Deserialize a Transaction item."""
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(item.raw())
        self.deserialize()

    def deserialize(self):
        self.clear()
        self.tx_widget.set_tx(self.tx)
        self.inputs_table.set_tx(self.tx)
        self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(self.tx.GetHash())))

    def do_verify_input(self, tx, in_idx):
        raw_prev_tx = None
        tx_in = tx.vin[in_idx]
        txid = b2lx(tx_in.prevout.hash)
        prev_out_n = tx_in.prevout.n

        try:
            raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid)
        except Exception as e:
            self.status_message(str(e), True)
            return False

        try:
            prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex'))
            result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx)
            self.result_edit.setText('Successfully verified input {}'.format(in_idx))
            self.inputs_table.set_verified(in_idx, True)
        except Exception as e:
            self.result_edit.setText(str(e))
            self.inputs_table.set_verified(in_idx, False)
            self.status_message(str(e), True)
            return False

        return True

    def do_verify_inputs(self, txt):
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        tx = Transaction.deserialize(txt.decode('hex'))
        failed_inputs = []
        self.result_edit.setText('Verifying...')
        for i in range(len(tx.vin)):
            if not self.do_verify_input(tx, i):
                failed_inputs.append(i)

        result = 'Successfully verified all inputs.'
        ret_val = True
        if failed_inputs:
            result = 'Failed to verify inputs: {}'.format(failed_inputs)
            ret_val = False
        if len(tx.vin) == 0:
            result = 'Transaction has no inputs.'
        self.result_edit.setText(result)
        return ret_val

    def verify_input(self):
        tx = None
        try:
            txt = str(self.raw_tx_edit.toPlainText())
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            self.status_message('Could not deserialize transaction.', True)
            return
        in_idx = self.inputs_box.value()
        if in_idx >= len(tx.vin):
            self.status_message('Input {} does not exist.'.format(in_idx), True)
            return

        self.do_verify_input(tx, in_idx)

    def verify_inputs(self):
        txt = str(self.raw_tx_edit.toPlainText())
        self.do_verify_inputs(txt)

    def verify_item_inputs(self, item):
        self.do_verify_inputs(item.raw())

    def refresh_data(self):
        if self.tx:
            self.deserialize()

    def on_option_changed(self, key):
        if key == 'chainparams':
            self.raw_tx_edit.textChanged.emit()
Example #4
0
class TxAnalyzer(BaseDock):

    tool_name = 'Transaction Analyzer'
    description = 'Deserializes transactions and verifies their inputs.'
    is_large = True
    category = Category.Tx

    def __init__(self, handler):
        super(TxAnalyzer, self).__init__(handler)
        self.raw_tx_edit.textChanged.emit()

    def init_data(self):
        self.tx = None

    def init_actions(self):
        deserialize = ('Deserialize', self.deserialize_raw)
        verify = ('Verify inputs', self.do_verify_inputs)
        self.advertised_actions[RAW_TX] = [deserialize, verify]

    def create_layout(self):
        form = QFormLayout()

        self.raw_tx_edit = QPlainTextEdit()
        self.raw_tx_edit.setTabChangesFocus(True)
        self.raw_tx_edit.setFont(monospace_font)
        self.raw_tx_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.raw_tx_edit.customContextMenuRequested.connect(self.context_menu)
        self.raw_tx_edit.textChanged.connect(self.check_raw_tx)
        self.setFocusProxy(self.raw_tx_edit)

        self.raw_tx_invalid = QLabel('Cannot parse transaction.')
        self.raw_tx_invalid.setProperty('hasError', True)

        self.tabs = tabs = QTabWidget()
        tabs.addTab(self.create_deserialize_tab(), 'Deserialize')
        tabs.addTab(self.create_verify_tab(), 'Verify')

        tabs.setTabToolTip(0, 'View the transaction in human-readable form')
        tabs.setTabToolTip(1, 'Download previous transactions and verify inputs')

        form.addRow('Raw Tx:', self.raw_tx_edit)
        form.addRow(self.raw_tx_invalid)
        form.addRow(tabs)
        return form

    def create_deserialize_tab(self):
        form = QFormLayout()

        self.deserialize_button = QPushButton('Deserialize')
        self.deserialize_button.clicked.connect(self.deserialize)
        btn_hbox = floated_buttons([self.deserialize_button])

        self.tx_widget = TxWidget()
        self.tx_widget.inputs_tree.view.customContextMenuRequested.disconnect(self.tx_widget.inputs_tree.customContextMenu)
        self.tx_widget.inputs_tree.view.customContextMenuRequested.connect(self.inputs_context_menu)

        form.addRow(self.tx_widget)

        w = QWidget()
        w.setLayout(form)
        return w

    def create_verify_tab(self):
        form = QFormLayout()
        form.setRowWrapPolicy(QFormLayout.WrapLongRows)

        self.inputs_box = QSpinBox()

        self.verify_button = QPushButton('Verify')
        self.verify_button.clicked.connect(self.verify_input)

        self.verify_all_button = QPushButton('Verify All')
        self.verify_all_button.clicked.connect(self.verify_inputs)

        self.result_edit = QLineEdit()
        self.result_edit.setReadOnly(True)

        self.inputs_table = InputStatusTable()

        form.addRow('Verify Input:', floated_buttons([self.inputs_box, self.verify_button]))
        form.addRow(floated_buttons([self.verify_all_button]))
        form.addRow('Result:', self.result_edit)
        form.addRow(self.inputs_table)

        w = QWidget()
        w.setLayout(form)
        return w

    def context_menu(self, position):
        menu = QMenu()
        if self.tx:
            txt = str(self.raw_tx_edit.toPlainText())
            self.handler.add_plugin_actions(self, menu, RAW_TX, txt)

        menu.exec_(self.mapToGlobal(position))

    def inputs_context_menu(self, position):
        inputs = self.tx_widget.inputs_tree
        def inputs_context_verify():
            self.tabs.setCurrentIndex(1)
            row = inputs.view.selectedIndexes()[0].row()
            self.do_verify_input(self.tx, row)

        menu = QMenu()
        if self.tx:
            menu = inputs.context_menu()
            menu.addAction('Verify script', inputs_context_verify)

        menu.exec_(inputs.view.viewport().mapToGlobal(position))

    def clear(self):
        self.result_edit.clear()
        self.tx_widget.clear()
        self.inputs_table.clear()

    def check_raw_tx(self):
        txt = str(self.raw_tx_edit.toPlainText())
        # Variable substitution
        if txt.startswith('$'):
            var_value = self.handler.get_plugin('Variables').dock.get_key(txt[1:])
            if var_value:
                self.raw_tx_edit.setPlainText(var_value)
            return
        tx = None
        valid = True
        try:
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            valid = False

        self.tx = tx
        self.deserialize_button.setEnabled(valid)
        self.inputs_box.setEnabled(valid)
        self.verify_button.setEnabled(valid)
        self.verify_all_button.setEnabled(valid)
        self.tx_widget.setEnabled(valid)
        self.clear()
        if valid:
            self.raw_tx_invalid.hide()
            self.inputs_box.setRange(0, len(tx.vin) - 1)
            self.deserialize()
        elif txt:
            self.raw_tx_invalid.show()
        else:
            self.raw_tx_invalid.hide()

    def deserialize_raw(self, txt):
        """Deserialize a raw transaction."""
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        self.deserialize()

    def deserialize(self):
        self.clear()
        self.tx_widget.set_tx(self.tx)
        self.inputs_table.set_tx(self.tx)
        self.status_message('Deserialized transaction {}'.format(bitcoin.core.b2lx(self.tx.GetHash())))

    def do_verify_input(self, tx, in_idx):
        raw_prev_tx = None
        tx_in = tx.vin[in_idx]
        txid = b2lx(tx_in.prevout.hash)
        prev_out_n = tx_in.prevout.n

        try:
            raw_prev_tx = self.handler.download_blockchain_data('raw_transaction', txid)
        except Exception as e:
            self.status_message(str(e), True)
            return False

        try:
            prev_tx = Transaction.deserialize(raw_prev_tx.decode('hex'))
            result = bitcoin.core.scripteval.VerifyScript(tx_in.scriptSig, prev_tx.vout[prev_out_n].scriptPubKey, tx, in_idx)
            self.result_edit.setText('Successfully verified input {}'.format(in_idx))
            self.inputs_table.set_verified(in_idx, True)
        except Exception as e:
            self.result_edit.setText(str(e))
            self.inputs_table.set_verified(in_idx, False)
            self.status_message(str(e), True)
            return False

        return True

    def do_verify_inputs(self, txt):
        self.needsFocus.emit()
        self.raw_tx_edit.setPlainText(txt)
        tx = Transaction.deserialize(txt.decode('hex'))
        failed_inputs = []
        self.result_edit.setText('Verifying...')
        for i in range(len(tx.vin)):
            if not self.do_verify_input(tx, i):
                failed_inputs.append(i)

        result = 'Successfully verified all inputs.'
        ret_val = True
        if failed_inputs:
            result = 'Failed to verify inputs: {}'.format(failed_inputs)
            ret_val = False
        if len(tx.vin) == 0:
            result = 'Transaction has no inputs.'
        self.result_edit.setText(result)
        return ret_val

    def verify_input(self):
        tx = None
        try:
            txt = str(self.raw_tx_edit.toPlainText())
            tx = Transaction.deserialize(txt.decode('hex'))
        except Exception:
            self.status_message('Could not deserialize transaction.', True)
            return
        in_idx = self.inputs_box.value()
        if in_idx >= len(tx.vin):
            self.status_message('Input {} does not exist.'.format(in_idx), True)
            return

        self.do_verify_input(tx, in_idx)

    def verify_inputs(self):
        txt = str(self.raw_tx_edit.toPlainText())
        self.do_verify_inputs(txt)

    def refresh_data(self):
        if self.tx:
            self.deserialize()

    def on_option_changed(self, key):
        if key == 'chainparams':
            self.raw_tx_edit.textChanged.emit()