示例#1
0
def showDialog(title, msg):
    icon = QtGui.QIcon()
    icon.addPixmap(QtGui.QPixmap("iconMusicAnalyzer"), QtGui.QIcon.Normal,
                   QtGui.QIcon.Off)
    msgBox = QMessageBox()
    msgBox.setWindowIcon(icon)
    msgBox.setText(msg)
    msgBox.setWindowTitle(title)
    msgBox.setStandardButtons(QMessageBox.Ok)

    returnValue = msgBox.exec()
    if returnValue == QMessageBox.Ok:
        msgBox.accept()
示例#2
0
    def addUserInDB(self, var1, var2, var3, var4, var5, var6, var7):

        if var7 == "":
            QMessageBox.critical(
                QWidget(), 'Ошибка!',
                "Please input birthday in format year-month-day!")
        else:
            var8 = datetime.strptime(var7, '%Y-%m-%d')
            db = postgresql.open("pq://*****:*****@localhost:5432/book")
            rowquery = db.prepare("select * from user_book")
            row = len(rowquery())
            row = row + 1
            query = db.prepare(
                "insert into user_book (id,first_name,second_name,patronymic,phone,department,email,date_of_birth) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)"
            )
            query(row, var1, var2, var3, var4, var5, var6, var8.date())
            QMessageBox.accept(QWidget(), 'Congratulations',
                               "USer are add in book")
            self.close()
class MessageBox:
    def __init__(self, type, message):
        if (type == 'error' or type == 'info'):
            self.message_box = QMessageBox()
            self.message_box.setInformativeText(message)
            if (type == 'error'):
                self.message_box.setIcon(QMessageBox.Critical)
                self.message_box.setText("Ошибка")
                self.message_box.setWindowTitle("Ошибка")
            elif (type == 'info'):
                self.message_box.setIcon(QMessageBox.Information)
                self.message_box.setText("Информация")
                self.message_box.setWindowTitle("Информация")
        else:
            raise Exception("Неизвестный тип сообщения")

    def show(self):
        self.message_box.show()
        self.message_box.exec()

    def hide(self):
        self.message_box.accept()
示例#4
0
class LedgerApi(QObject):
    # signal: sig1 (thread) is done - emitted by signMessageFinish
    sig1done = pyqtSignal(str)
    # signal: sigtx (thread) is done - emitted by signTxFinish
    sigTxdone = pyqtSignal(bytearray, str)
    # signal: sigtx (thread) is done (aborted) - emitted by signTxFinish
    sigTxabort = pyqtSignal()
    # signal: tx_progress percent - emitted by perepare_transfer_tx_bulk
    tx_progress = pyqtSignal(int)
    # signal: sig_progress percent - emitted by signTxSign
    sig_progress = pyqtSignal(int)
    # signal: sig_disconnected -emitted with DisconnectedException
    sig_disconnected = pyqtSignal(str)

    def __init__(self, main_wnd, *args, **kwargs):
        QObject.__init__(self, *args, **kwargs)
        self.main_wnd = main_wnd
        self.model = [x[0] for x in HW_devices].index("LEDGER Nano")
        self.messages = [
            'Device not initialized.',
            'Unable to connect to the device. Please check that the PIVX app on the device is open, and try again.',
            'Hardware device connected.'
        ]
        # Device Lock for threads
        self.lock = threading.RLock()
        self.status = 0
        self.dongle = None
        printDbg("Creating HW device class")

    @process_ledger_exceptions
    def initDevice(self):
        printDbg("Initializing Ledger")
        with self.lock:
            self.status = 0
            self.dongle = getDongle(False)
            printOK('Ledger Nano drivers found')
            self.chip = btchip(self.dongle)
            printDbg("Ledger Initialized")
            self.status = 1
            ver = self.chip.getFirmwareVersion()
            printOK("Ledger HW device connected [v. %s]" %
                    str(ver.get('version')))
            # Check device is unlocked
            bip32_path = MPATH + "%d'/0/%d" % (0, 0)
            _ = self.chip.getWalletPublicKey(bip32_path)
            self.status = 2
        self.sig_progress.connect(self.updateSigProgress)

    def closeDevice(self, message=''):
        printDbg("Closing LEDGER client")
        self.sig_disconnected.emit(message)
        self.status = 0
        with self.lock:
            if self.dongle is not None:
                try:
                    self.dongle.close()
                except:
                    pass
                self.dongle = None

    @process_ledger_exceptions
    def append_inputs_to_TX(self, utxo, bip32_path):
        self.amount += int(utxo['satoshis'])
        raw_tx = TxCache(self.main_wnd)[utxo['txid']]

        # parse the raw transaction, so that we can extract the UTXO locking script we refer to
        prev_transaction = bitcoinTransaction(bytearray.fromhex(raw_tx))

        utxo_tx_index = utxo['vout']
        if utxo_tx_index < 0 or utxo_tx_index > len(prev_transaction.outputs):
            raise Exception('Incorrect value of outputIndex for UTXO %s-%d' %
                            (utxo['txid'], utxo['vout']))

        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['vout']].script,
            'pubkey':
            curr_pubkey,
            'bip32_path':
            bip32_path,
            'outputIndex':
            utxo['vout'],
            'txid':
            utxo['txid'],
            'p2cs': (utxo['staker'] != "")
        })

    @process_ledger_exceptions
    def prepare_transfer_tx_bulk(self,
                                 caller,
                                 rewardsArray,
                                 dest_address,
                                 tx_fee,
                                 useSwiftX=False,
                                 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))
            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)

    @process_ledger_exceptions
    def scanForAddress(self, account, spath, isTestnet=False):
        with self.lock:
            if not isTestnet:
                curr_path = MPATH + "%d'/0/%d" % (account, spath)
                curr_addr = self.chip.getWalletPublicKey(curr_path).get(
                    'address')[12:-2]
            else:
                curr_path = MPATH_TESTNET + "%d'/0/%d" % (account, spath)
                pubkey = compress_public_key(
                    self.chip.getWalletPublicKey(curr_path).get(
                        'publicKey')).hex()
                curr_addr = pubkey_to_address(pubkey, isTestnet)

        return curr_addr

    @process_ledger_exceptions
    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()

    @process_ledger_exceptions
    def signMess(self, caller, hwpath, message, isTestnet=False):
        if isTestnet:
            path = MPATH_TESTNET + hwpath
        else:
            path = MPATH + hwpath
        # Ledger doesn't accept characters other that ascii printable:
        # https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_sign_message
        message = message.encode('ascii', 'ignore')
        message_sha = splitString(single_sha256(message).hex(), 32)

        # Connection pop-up
        mBox = QMessageBox(caller)
        warningText = "Another application (such as Ledger Wallet app) has probably taken over "
        warningText += "the communication with the Ledger device.<br><br>To continue, close that application and "
        warningText += "click the <b>Retry</b> button.\nTo cancel, click the <b>Abort</b> button"
        mBox.setText(warningText)
        mBox.setWindowTitle("WARNING")
        mBox.setStandardButtons(QMessageBox.Retry | QMessageBox.Abort)

        # Ask confirmation
        with self.lock:
            info = self.chip.signMessagePrepare(path, message)

            while info['confirmationNeeded'] and info['confirmationType'] == 34:
                ans = mBox.exec_()

                if ans == QMessageBox.Abort:
                    raise Exception("Reconnect HW device")

                # we need to reconnect the device
                self.initDevice()
                info = self.chip.signMessagePrepare(path, message)

            printOK('Signing Message')
            self.mBox = QMessageBox(caller)
            messageText = "Check display of your hardware device\n\n- message hash:\n\n%s\n\n-path:\t%s\n" % (
                message_sha, path)
            self.mBox.setText(messageText)
            self.mBox.setIconPixmap(
                caller.tabMain.ledgerImg.scaledToHeight(
                    200, Qt.SmoothTransformation))
            self.mBox.setWindowTitle("CHECK YOUR LEDGER")
            self.mBox.setStandardButtons(QMessageBox.NoButton)
            self.mBox.show()

        # Sign message
        ThreadFuns.runInThread(self.signMessageSign, (),
                               self.signMessageFinish)

    @process_ledger_exceptions
    def signMessageSign(self, ctrl):
        self.signature = None
        with self.lock:
            try:
                self.signature = self.chip.signMessageSign()
            except:
                pass

    def signMessageFinish(self):
        with self.lock:
            self.mBox.accept()
        if self.signature != None:
            if len(self.signature) > 4:
                rLength = self.signature[3]
                r = self.signature[4:4 + rLength]
                if len(self.signature) > 4 + rLength + 1:
                    sLength = self.signature[4 + rLength + 1]
                    if len(self.signature) > 4 + rLength + 2:
                        s = self.signature[4 + rLength + 2:]
                        if rLength == 33:
                            r = r[1:]
                        if sLength == 33:
                            s = s[1:]

                        work = bytes(chr(27 + 4 + (self.signature[0] & 0x01)),
                                     "utf-8") + r + s
                        printOK("Message signed")
                        sig1 = work.hex()
                    else:
                        printDbg(
                            'client.signMessageSign() returned invalid response (code 3): '
                            + self.signature.hex())
                        sig1 = "None"
                else:
                    printDbg(
                        'client.signMessageSign() returned invalid response (code 2): '
                        + self.signature.hex())
                    sig1 = "None"
            else:
                printDbg(
                    'client.signMessageSign() returned invalid response (code 1): '
                    + self.signature.hex())
                sig1 = "None"
        else:
            printOK("Signature refused by the user")
            sig1 = "None"

        self.sig1done.emit(sig1)

    @process_ledger_exceptions
    def signTxSign(self, ctrl):
        self.tx_raw = None
        with self.lock:
            starting = True
            curr_input_signed = 0
            # 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):
                try:
                    self.chip.startUntrustedTransaction(
                        starting, idx, self.trusted_inputs,
                        new_input['locking_script'])

                    self.chip.finalizeInputFull(self.all_outputs_raw)

                    sig = self.chip.untrustedHashSign(new_input['bip32_path'],
                                                      lockTime=0)
                except BTChipException as e:
                    if e.args[0] != "Invalid status 6985":
                        raise e
                    # Signature refused by the user
                    return

                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
                if new_input['p2cs']:
                    inputTx.script += bytearray([0x00])
                inputTx.script += bytearray([0x21]) + new_input['pubkey']

                inputTx.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF])

                self.new_transaction.inputs.append(inputTx)

                starting = False

                # signature percent emitted
                curr_input_signed += 1
                completion = int(100 * curr_input_signed /
                                 len(self.arg_inputs))
                self.sig_progress.emit(completion)

            self.new_transaction.lockTime = bytearray([0, 0, 0, 0])
            self.tx_raw = bytearray(self.new_transaction.serialize())
            self.sig_progress.emit(100)

    def signTxFinish(self):
        self.mBox2.accept()

        if self.tx_raw is not None:
            # Signal to be catched by FinishSend on TabRewards / dlg_sewwpAll
            self.sigTxdone.emit(self.tx_raw, str(round(self.amount / 1e8, 8)))
        else:
            printOK("Transaction refused by the user")
            self.sigTxabort.emit()

    def updateSigProgress(self, percent):
        messageText = self.messageText + "Signature Progress: <b style='color:red'>" + str(
            percent) + " %</b>"
        self.mBox2.setText(messageText)
        QApplication.processEvents()
示例#5
0
class TrezorApi(QObject):
    # signal: sig1 (thread) is done - emitted by signMessageFinish
    sig1done = pyqtSignal(str)
    # signal: sigtx (thread) is done - emitted by signTxFinish
    sigTxdone = pyqtSignal(bytearray, str)
    # signal: sigtx (thread) is done (aborted) - emitted by signTxFinish
    sigTxabort = pyqtSignal()
    # signal: tx_progress percent - emitted by perepare_transfer_tx_bulk
    tx_progress = pyqtSignal(int)
    # signal: sig_progress percent - emitted by signTxSign
    sig_progress = pyqtSignal(int)
    # signal: sig_disconnected -emitted with DisconnectedException
    sig_disconnected = pyqtSignal(str)

    def __init__(self, model, main_wnd, *args, **kwargs):
        QObject.__init__(self, *args, **kwargs)
        self.model = model  # index of HW_devices
        self.main_wnd = main_wnd
        self.messages = [
            'Trezor not initialized. Connect and unlock it',
            'Error setting up Trezor Client.', 'Hardware device connected.',
            "Wrong device model detected.", "Wrong PIN inserted"
        ]
        # Device Lock for threads
        self.lock = threading.RLock()
        self.status = 0
        self.client = None
        printDbg("Creating HW device class")
        self.sig_progress.connect(self.updateSigProgress)

    @process_trezor_exceptions
    def append_inputs_to_TX(self, utxo, bip32_path, inputs):
        # Update amount
        self.amount += int(utxo['satoshis'])
        # Add input
        address_n = parse_path(bip32_path)
        prev_hash = binascii.unhexlify(utxo['txid'])
        it = trezor_proto.TxInputType(address_n=address_n,
                                      prev_hash=prev_hash,
                                      prev_index=int(utxo['vout']))
        inputs.append(it)

    def checkModel(self, model):
        if HW_devices[self.model][0] == "TREZOR One":
            return model == "1"
        else:
            return model == "T"

    def closeDevice(self, message=''):
        printDbg("Closing TREZOR client")
        self.sig_disconnected.emit(message)
        self.status = 0
        with self.lock:
            if self.client is not None:
                try:
                    self.client.close()
                except:
                    pass
                self.client = None

    @process_trezor_exceptions
    def initDevice(self):
        printDbg("Initializing Trezor")
        with self.lock:
            self.status = 0
            devices = enumerate_devices()
            if not len(devices):
                # No device connected
                return
            # Use the first device for now
            d = devices[0]
            ui = TrezorUi()
            try:
                self.client = TrezorClient(d, ui)
            except IOError:
                raise Exception("TREZOR device is currently in use")
            printOK("Trezor HW device connected [v. %s.%s.%s]" %
                    (self.client.features.major_version,
                     self.client.features.minor_version,
                     self.client.features.patch_version))
            self.status = 1
            model = self.client.features.model or "1"
            if not self.checkModel(model):
                self.status = 3
                self.messages[
                    3] = "Wrong device model (%s) detected.\nLooking for model %s." % (
                        HW_devices[self.model][0], model)
                return
            required_version = MINIMUM_FIRMWARE_VERSION[model]
            printDbg("Current version is %s (minimum required: %s)" %
                     (str(self.client.version), str(required_version)))
            # Check device is unlocked
            bip32_path = parse_path(MPATH + "%d'/0/%d" % (0, 0))
            _ = btc.get_address(self.client, 'PIVX', bip32_path, False)
            self.status = 2

    def load_prev_txes(self, rewardsArray):
        curr_utxo_checked = 0
        txes = {}
        num_of_txes = sum([len(mnode['utxos']) for mnode in rewardsArray])
        for mn in rewardsArray:
            for utxo in mn['utxos']:
                prev_hash = bytes.fromhex(utxo["txid"])
                if prev_hash not in txes:
                    raw_tx = TxCache(self.main_wnd)[utxo['txid']]
                    json_tx = ParseTx(raw_tx)
                    txes[prev_hash] = self.json_to_tx(json_tx)

                # completion percent emitted
                curr_utxo_checked += 1
                completion = int(95 * curr_utxo_checked / num_of_txes)
                self.tx_progress.emit(completion)
        self.tx_progress.emit(100)
        return txes

    def json_to_tx(self, jtx):
        t = trezor_proto.TransactionType()
        t.version = jtx["version"]
        t.lock_time = jtx["locktime"]
        t.inputs = [self.json_to_input(input) for input in jtx["vin"]]
        t.bin_outputs = [
            self.json_to_bin_output(output) for output in jtx["vout"]
        ]
        return t

    def json_to_input(self, input):
        i = trezor_proto.TxInputType()
        if "coinbase" in input:
            i.prev_hash = b"\0" * 32
            i.prev_index = 0xFFFFFFFF  # signed int -1
            i.script_sig = bytes.fromhex(input["coinbase"])
        else:
            i.prev_hash = bytes.fromhex(input["txid"])
            i.prev_index = input["vout"]
            i.script_sig = bytes.fromhex(input["scriptSig"]["hex"])
        i.sequence = input["sequence"]
        return i

    def json_to_bin_output(self, output):
        o = trezor_proto.TxOutputBinType()
        o.amount = int(output["value"])
        o.script_pubkey = bytes.fromhex(output["scriptPubKey"]["hex"])
        return o

    def prepare_transfer_tx_bulk(self,
                                 caller,
                                 rewardsArray,
                                 dest_address,
                                 tx_fee,
                                 useSwiftX=False,
                                 isTestnet=False):
        inputs = []
        outputs = []
        c_name = "PIVX"
        if isTestnet:
            c_name += " Testnet"
        coin = coins.by_name[c_name]
        with self.lock:
            self.amount = 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'], inputs)

            self.amount = int(self.amount)
            self.amount -= int(tx_fee)
            if self.amount < 0:
                raise Exception('Invalid TX: inputs + fee != outputs')

            outputs.append(
                trezor_proto.TxOutputType(
                    address=dest_address,
                    address_n=None,
                    amount=self.amount,
                    script_type=trezor_proto.OutputScriptType.PAYTOSCRIPTHASH))

            txes = self.load_prev_txes(rewardsArray)

            self.mBox2 = QMessageBox(caller)
            self.messageText = "<p>Signing transaction...</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.setBoxIcon(self.mBox2, caller)
            self.mBox2.setWindowTitle("CHECK YOUR TREZOR")
            self.mBox2.setStandardButtons(QMessageBox.NoButton)
            self.mBox2.setMaximumWidth(500)
            self.mBox2.show()

        ThreadFuns.runInThread(self.signTxSign,
                               (inputs, outputs, txes, isTestnet),
                               self.signTxFinish)

    @process_trezor_exceptions
    def scanForAddress(self, account, spath, isTestnet=False):
        with self.lock:
            if not isTestnet:
                curr_path = parse_path(MPATH + "%d'/0/%d" % (account, spath))
                curr_addr = btc.get_address(self.client, 'PIVX', curr_path,
                                            False)
            else:
                curr_path = parse_path(MPATH_TESTNET + "%d'/0/%d" %
                                       (account, spath))
                curr_addr = btc.get_address(self.client, 'PIVX Testnet',
                                            curr_path, False)

        return curr_addr

    @process_trezor_exceptions
    def scanForPubKey(self, account, spath, isTestnet=False):
        hwpath = "%d'/0/%d" % (account, spath)
        if isTestnet:
            path = MPATH_TESTNET + hwpath
        else:
            path = MPATH + hwpath

        curr_path = parse_path(path)
        with self.lock:
            result = btc.get_public_node(self.client, curr_path)

        return result.node.public_key.hex()

    def setBoxIcon(self, box, caller):
        if HW_devices[self.model][0] == "TREZOR One":
            box.setIconPixmap(
                caller.tabMain.trezorOneImg.scaledToHeight(
                    200, Qt.SmoothTransformation))
        else:
            box.setIconPixmap(
                caller.tabMain.trezorImg.scaledToHeight(
                    200, Qt.SmoothTransformation))

    def signMess(self, caller, hwpath, message, isTestnet=False):
        if isTestnet:
            path = MPATH_TESTNET + hwpath
        else:
            path = MPATH + hwpath
        # Connection pop-up
        self.mBox = QMessageBox(caller)
        messageText = "Check display of your hardware device\n\n- message:\n\n%s\n\n-path:\t%s\n" % (
            splitString(message, 32), path)
        self.mBox.setText(messageText)
        self.setBoxIcon(self.mBox, caller)
        self.mBox.setWindowTitle("CHECK YOUR TREZOR")
        self.mBox.setStandardButtons(QMessageBox.NoButton)
        self.mBox.show()

        # Sign message
        ThreadFuns.runInThread(self.signMessageSign,
                               (path, message, isTestnet),
                               self.signMessageFinish)

    @process_trezor_exceptions
    def signMessageSign(self, ctrl, path, mess, isTestnet):
        self.signature = None
        if isTestnet:
            hw_coin = "PIVX Testnet"
        else:
            hw_coin = "PIVX"
        with self.lock:
            bip32_path = parse_path(path)
            signed_mess = btc.sign_message(self.client, hw_coin, bip32_path,
                                           mess)
            self.signature = signed_mess.signature

    def signMessageFinish(self):
        with self.lock:
            self.mBox.accept()
        if self.signature is None:
            printOK("Signature refused by the user")
            self.sig1done.emit("None")
        else:
            self.sig1done.emit(self.signature.hex())

    @process_trezor_exceptions
    def signTxSign(self, ctrl, inputs, outputs, txes, isTestnet=False):
        self.tx_raw = None
        if isTestnet:
            hw_coin = "PIVX Testnet"
        else:
            hw_coin = "PIVX"
        with self.lock:
            signed = sign_tx(self.sig_progress,
                             self.client,
                             hw_coin,
                             inputs,
                             outputs,
                             prev_txes=txes)

        self.tx_raw = bytearray(signed[1])
        self.sig_progress.emit(100)

    def signTxFinish(self):
        self.mBox2.accept()
        if self.tx_raw is not None:
            # Signal to be catched by FinishSend on TabRewards / dlg_sewwpAll
            self.sigTxdone.emit(self.tx_raw, str(round(self.amount / 1e8, 8)))

        else:
            printOK("Transaction refused by the user")
            self.sigTxabort.emit()

    def updateSigProgress(self, percent):
        # -1 simply adds a waiting message to the actual progress
        if percent == -1:
            t = self.mBox2.text()
            messageText = t + "<br>Please confirm action on your Trezor device..."
        else:
            messageText = self.messageText + "Signature Progress: <b style='color:red'>" + str(
                percent) + " %</b>"
        self.mBox2.setText(messageText)
        QApplication.processEvents()
class OvirtClient(QWidget):
    """
        This class will handle the main window where all user's VMs will be listed.
        This will be rendered only if authentication was successfull since it doesn't
        make sense otherwise. Additionally, a Thread will be started checking VM status
        changes and update the board accordingly.
    """

    stopThread = False  # Sentinel for stopping the Thread execution
    autologoutWarn = False  # Has the user been warned about autologout yet?
    openviewer_vms = []  # Initiated VMs in terms of the viewer
    updatesignal = pyqtSignal(
        int, str)  # Signal to update the status icons on status changes
    reloadsignal = pyqtSignal()  # Signal to reload the main widget
    warnlogoutsignal = pyqtSignal(
    )  # Signal to warn the user about an imminent autologout
    logoutsignal = pyqtSignal(
        bool
    )  # Signal to logout the current user and require credentials again
    lastclick = int(time(
    ))  # Timestamp of the last click. If a timeout policy is defined and

    # the time exceeds this value, an autologout will be performed.

    def __init__(self):
        QWidget.__init__(self)
        self.initUI()

    def vm_based_resize(self, vmnum):
        """
            Description: Depending on the number of VMs which the user has permissions on,
                         we need to resize the main window accordingly in height.The fewer
                         they are, the smaller the window will be. If MAXHEIGHT is reached,
                         a scrollbar will also be shown (that's processed in a different
                         place, though)
            Parameters: The number of VMs.
            Returns: The actual window height.
        """

        global MAXHEIGHT

        if not vmnum:
            # If user has no machines, resize window to the minimum
            winheight = 150
            no_machines = QLabel(_('no_vms'))
            no_machines.setWordWrap(True)
            no_machines.setAlignment(Qt.AlignCenter)
            self.grid.addWidget(no_machines, 0, 0)
            self.setMinimumHeight(winheight)
        else:
            # User has at least one VM
            if vmnum > 5:
                # More than 5 means resizing the window to the maximum
                winheight = MAXHEIGHT
                self.setFixedHeight(winheight)
            else:
                # Otherwise, resize depending on the number of VMs
                winheight = vmnum * 100 + 50
                self.setFixedHeight(winheight)
        return winheight

    def get_os_icon(self, os):
        """
            Description: Depending on the VM's OS, this method returns
                         which icon should be used to illustrate it.
            Arguments: oVirt-style string representing the VM's OS
            Returns: The file name under IMGDIR that should be shown.
        """

        if 'ubuntu' in os:
            return 'ubuntu'
        elif 'rhel' in os:
            return 'redhat'
        elif 'centos' in os:
            return 'centos'
        elif 'debian' in os:
            return 'debian'
        elif 'linux' in os:
            return 'linux'
        elif 'win' in os:
            return 'windows'
        return 'unknown'

    def generate_toolbar(self):
        """
            Description: There will be a toolbar in the main widget with some buttons,
                         this method will render them.
            Arguments: None
            Returns: Nothing
        """

        global IMGDIR, conf

        self.toolBar = QToolBar(self)

        refreshAction = QAction(QIcon(IMGDIR + 'refresh.png'), _('refresh'),
                                self)
        refreshAction.setShortcut('Ctrl+R')
        refreshAction.triggered.connect(self.refresh_grid)
        self.toolBar.addAction(refreshAction)

        self.forgetCredsAction = QAction(QIcon(IMGDIR + 'forget.png'),
                                         _('forget_credentials'), self)
        self.forgetCredsAction.setShortcut('Ctrl+F')
        self.forgetCredsAction.triggered.connect(self.forget_creds)
        if not isfile(conf.USERCREDSFILE):
            self.forgetCredsAction.setDisabled(True)
        self.toolBar.addAction(self.forgetCredsAction)

        aboutAction = QAction(QIcon(IMGDIR + 'about.png'), _('about'), self)
        aboutAction.setShortcut('Ctrl+I')
        aboutAction.triggered.connect(self.about)
        self.toolBar.addAction(aboutAction)

        exitAction = QAction(QIcon(IMGDIR + 'exit.png'), _('exit'), self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.triggered.connect(self.quit_button)
        self.toolBar.addAction(exitAction)

        self.grid.addWidget(self.toolBar, 0, 3, Qt.AlignRight)

    def make_button(self, filename, tooltip, alignment=Qt.AlignCenter):
        """
            Description: Creates a QLabel which will contain an image and will
                         simulate a button. No associated text will be shown.
            Arguments: 1. filename: The filename of the icon/image to show 
                       2. tooltip: Some text to show as a tooltip to the image
                       3. alignment: Alignment of the button (center by default)
            Returns: The created QLabel button
        """

        global STANDARDCELLCSS, IMGDIR

        filepath = '%s%s%s' % (IMGDIR, filename, '.png')
        icon = QImage(filepath)
        image = QLabel()
        image.setToolTip('<span style="color:#B9B900">%s</span>' % (tooltip))
        image.setStyleSheet(STANDARDCELLCSS)
        image.setPixmap(QPixmap.fromImage(icon))
        image.setAlignment(alignment)

        return image

    def compare_vms(self, vm1, vm2):
        """
            Description: VM list will be sorted by names. This method is the comparison
                         method for two VMs. We just sort them alphabetically.
            Arguments: Two VMs (oVirt VM objects)
            Returns: -1, 0 or 1 depending on the name-based sorting
        """

        if vm1.name.lower() < vm2.name.lower():
            return -1
        if vm1.name.lower() == vm2.name.lower():
            return 0
        return 1

    def p22p3_compare_vms(self, cmpfunct):
        """ Wrapper for Python 2 -> 3 conversion for the old cmp= method of sorted()"""
        class K:
            def __init__(self, obj, *args):
                self.obj = obj

            def __lt__(self, other):
                return cmpfunct(self.obj, other.obj) < 0

            def __gt__(self, other):
                return cmpfunct(self.obj, other.obj) > 0

            def __eq__(self, other):
                return cmpfunct(self.obj, other.obj) == 0

            def __le__(self, other):
                return cmpfunct(self.obj, other.obj) <= 0

            def __ge__(self, other):
                return cmpfunct(self.obj, other.obj) >= 0

            def __ne__(self, other):
                return cmpfunct(self.obj, other.obj) != 0

        return K

    def current_vm_status(self, vmstatus):
        """
            Description: Single translation between oVirt-like status to human-readable status
            Arguments: oVirt-like status
            Returns: Human-readable status
        """

        if vmstatus == 'up':
            hrstatus = _('up')
        elif vmstatus == 'down':
            hrstatus = _('down')
        elif vmstatus == 'powering_down':
            hrstatus = _('powering_down')
        elif vmstatus == 'wait_for_launch':
            hrstatus = _('wait_for_launch')
        elif vmstatus == 'powering_up':
            hrstatus = _('powering_up')
        elif vmstatus == 'reboot_in_progress':
            hrstatus = _('rebooting')
        else:
            hrstatus = 'unknown'
        return hrstatus

    def toggle_vm_action(self, vmstatus):
        """
            Description: Returns the available action for the current VM's status. If machine is up,
                         available action is turn it off and viceversa.
            Arguments: Current vm status
            Returns: Toggle action for the current status.
        """

        if vmstatus == 'up':
            vmaction = _('shut_down')
        if vmstatus == 'down':
            vmaction = _('power_on')
        return vmaction

    def toggle_action_text(self, vmstatus):
        """
            Description: One of the columns shows the current VM's status. This method returns
                         the toggle tooltip text so the user know what will happen if they click
                         on the status icon.
            Arguments: Current vm status
            Returns: The tooltip's informative text.
        """

        rettxt = '%s <b>%s</b>.' % (_('current_vm_status'),
                                    self.current_vm_status(vmstatus))

        if vmstatus == 'up':
            rettxt += ' %s %s' % (_('click_to_action'), _('shut_down'))
        if vmstatus == 'down':
            rettxt += ' %s %s' % (_('click_to_action'), _('power_on'))

        return rettxt

    def change_status(self, rowid):
        """
            Description: If the user clicks on the column which determines VM's status, we'll allow them
                         to change VM's status. This method shows a confirmation dialog and if accepted,
                         it will be notified to oVirt.
            Arguments: The row id that has been clicked. This relationship is stored using the VmData class.
            Returns: Nothing
        """

        global conf

        self.lastclick = int(time())  # Last click timestamp update

        curvmstatus = self.vmdata[rowid].vmstatus
        if curvmstatus != 'up' and curvmstatus != 'down':
            QMessageBox.warning(None,
                                _('apptitle') + ': ' + _('warning'),
                                _('vm_in_unchangeable_status'))
            return

        reply = QMessageBox.question(
            None,
            _('apptitle') + ': ' + _('confirm'),
            '%s <b>%s</b>. %s: <b>%s</b>.' %
            (_('current_vm_status'), self.current_vm_status(curvmstatus),
             _('confirm_vm_status_change'),
             self.toggle_vm_action(curvmstatus)),
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            try:
                vms_service = conf.OVIRTCONN.vms_service()
                vm = vms_service.list(search='id=%s' %
                                      (self.vmdata[rowid].vmid))[0]
            except Error:
                QMessageBox.critical(None,
                                     _('apptitle') + ': ' + _('error'),
                                     _('unexpected_connection_drop'))
                quit()

            if curvmstatus == 'up':
                try:
                    vm_service = vms_service.vm_service(
                        id=self.vmdata[rowid].vmid)
                    vm_service.shutdown()
                    QMessageBox.information(
                        None,
                        _('apptitle') + ': ' + _('success'),
                        _('shutting_down_vm'))
                except Error:
                    QMessageBox.warning(None,
                                        _('apptitle') + ': ' + _('warning'),
                                        _('vm_in_unchangeable_status'))
            if curvmstatus == 'down':
                try:
                    vm_service = vms_service.vm_service(
                        id=self.vmdata[rowid].vmid)
                    vm_service.start()
                    QMessageBox.information(
                        None,
                        _('apptitle') + ': ' + _('success'),
                        _('powering_up_vm'))
                except Error:
                    QMessageBox.warning(None,
                                        _('apptitle') + ': ' + _('warning'),
                                        _('vm_in_unchangeable_status'))

    def get_viewer_ticket(self, vmid):
        """
            Description: Connecting to the machine involves two steps, the first one is obtaining a 'ticket' string
                         for the connection request. This is done making a request to the oVirt API and then parsing
                         the resulting XML document to get the ticket hash. Also, the request may return more than
                         one ticket: One for SPICE and another for VNC. In this case, we'll return the one that
                         the user defined in the settings file (SPICE as default).
            Arguments: The VM UUID in oVirt-format
            Returns: The ticket hash string
        """

        global conf

        req = urllib.request.Request(
            '%s/%s/%s/%s' %
            (conf.CONFIG['ovirturl'], 'vms', vmid, 'graphicsconsoles'))
        # Python 2 -> 3 conversion: encodestring expects a byte-like string, not str
        base64str = encodestring(
            ('%s:%s' % (conf.USERNAME + '@' + conf.CONFIG['ovirtdomain'],
                        conf.PASSWORD)).encode()).decode().replace('\n', '')
        req.add_header('Authorization', 'Basic ' + base64str)
        req.add_header('filter', 'true')

        unverified_ctxt = SSLContext(PROTOCOL_TLSv1)
        tickethash = urllib.request.urlopen(req,
                                            context=unverified_ctxt).read()
        xmlcontent = ET.fromstring(tickethash)

        ticket = None
        for data in xmlcontent.findall('graphics_console'):
            proto = data.findall('protocol')[0]

            if proto.text.lower() == conf.CONFIG['prefproto'].lower():
                return data.get('id')
            else:
                ticket = data.get('id')

        return ticket

    def store_vv_file(self, vmid, ticket):
        """
            Description: Connecting to the machine involves two steps, the second one is obtaining a 'vv' file with the
                         connection parameters, which we can later pipe to virt-viewer and the connection will be opened.
            Arguments: 1. vmid: The VM UUID in oVirt-format.
                       2. ticket: The ticket obtained in the first step (method get_viewer_ticket)
            Returns: The temporary filename with all the parameters to connect to the machine (piped to virt-viewer)
        """

        global conf

        if not ticket:
            return False

        req = urllib.request.Request(
            '%s/%s/%s/%s/%s' %
            (conf.CONFIG['ovirturl'], 'vms', vmid, 'graphicsconsoles', ticket))
        # Python 2 -> 3 conversion: encodestring expects a byte-like string, not str
        base64str = encodestring(
            ('%s:%s' % (conf.USERNAME + '@' + conf.CONFIG['ovirtdomain'],
                        conf.PASSWORD)).encode()).decode().replace('\n', '')
        req.add_header('Authorization', 'Basic ' + base64str)
        req.add_header('Content-Type', 'application/xml')
        req.add_header('Accept', 'application/x-virt-viewer')
        req.add_header('filter', 'true')

        unverified_ctxt = SSLContext(PROTOCOL_TLSv1)
        try:
            contents = urllib.request.urlopen(req,
                                              context=unverified_ctxt).read()
            if conf.CONFIG['fullscreen'] == '1':
                contents = contents.replace('fullscreen=0', 'fullscreen=1')
            filename = '/tmp/viewer-' + str(randint(10000, 99999))
            f = open(filename, 'wb')
            f.write(contents)
            f.close()

            return filename
        except urllib.request.HTTPError as em:
            QMessageBox.critical(
                None,
                _('apptitle') + ': ' + _('error'),
                _('unexpected_request_error') + '(' + str(em.code) + '): ' +
                em.reason + '. ' + _('check_vm_config_updated'))
            return None

    def viewer_exit(self, vmname):
        self.openviewer_vms.remove(
            vmname)  # Remove the VM from the list of opened viewers
        self.reloadsignal.emit(
        )  # Enforce a reload signal to update the status icon ASAP

    def create_viewer_thread(self, vmname, filename):
        global conf

        def runInThread(vmname, onExit, popenArgs):
            viewer = Popen(popenArgs)
            viewer.wait()
            onExit(vmname)
            return

        thread = threading.Thread(target=runInThread,
                                  args=(vmname, self.viewer_exit, [
                                      conf.CONFIG['remote_viewer_path'], '-t',
                                      vmname, '-f', '--',
                                      'file://%s' % (filename)
                                  ]))
        thread.start()

        # Returns immediately after the thread starts
        return thread

    def connect2machine(self, vmid, vmname):
        """
            Description: Connecting to the machine involves two steps, this method does both and
                         makes sure everything is ok to call virt-viewer afterwards.
            Arguments: 1. vmid: The VM UUID in oVirt-format.
                       2. vmname: Just for displaying purposes, the VM name
            Returns: Nothing. Opens the view-viewer display.
        """

        viewer_ticket = self.get_viewer_ticket(vmid)
        filename = self.store_vv_file(vmid, viewer_ticket)

        if filename:
            self.create_viewer_thread(vmname, filename)
        else:
            if vmname in self.openviewer_vms:
                self.openviewer_vms.remove(
                    vmname)  # Remove the VM from the list of opened viewers

            QMessageBox.critical(None,
                                 _('apptitle') + ': ' + _('error'),
                                 _('no_viewer_file'))

    def connect(self, rowid):
        """
            Description: Whenever the user clicks on the 'connect' row, this method will make
                         sure the VM status is up and only then will call the connect2machine method.
            Arguments: The row id that has been clicked. This relationship is stored using the VmData class.
            Returns: Nothing
        """

        self.lastclick = int(time())  # Last click timestamp update

        vmid = self.vmdata[rowid].vmid
        vmname = self.vmdata[rowid].vmname
        vmstatus = self.vmdata[rowid].vmstatus

        if vmstatus != 'up':
            QMessageBox.warning(None,
                                _('apptitle') + ': ' + _('warning'),
                                _('cannot_connect_if_vm_not_up'))
            return

        if vmname in self.openviewer_vms:
            QMessageBox.critical(None,
                                 _('apptitle') + ': ' + _('error'),
                                 _('cannot_open_more_viewer_sessions'))
            return

        self.openviewer_vms.append(vmname)
        self.refresh_grid(
        )  # Enforce a dashboard reload to make the icon refresh

        self.connect2machine(vmid, vmname)

    def acquire_vm_from_vmpool(self, rowid):
        """
            Description: A machine will be acquired by a user if they click on the icon of a VmPool
            Arguments: The row id that has been clicked. This relationship is stored using the VmData class.
            Returns: Nothing
        """

        self.lastclick = int(time())  # Last click timestamp update

        vmtype = self.vmdata[rowid].vmtype

        if vmtype == 'vmpool':
            try:
                QMessageBox.information(None,
                                        _('apptitle') + ': ' + _('info'),
                                        _('acquiring_vm_from_pool'))
                vmpool_service = conf.OVIRTCONN.vm_pools_service()
                vmp = vmpool_service.pool_service(id=self.vmdata[rowid].vmid)
                vmp.allocate_vm()
                self.refresh_grid()
            except Error as e:
                QMessageBox.critical(None,
                                     _('apptitle') + ': ' + _('error'), str(e))
        else:
            QMessageBox.warning(None,
                                _('apptitle') + ': ' + _('warning'),
                                _('object_is_not_a_vmpool'))

    def refresh_grid(self):
        """
            Description: Invoked when the user clicks on the 'Refresh' button in the toolbar. Reloads the board.
            Arguments: None
            Returns: Nothing
        """

        self.lastclick = int(time())  # Last click timestamp update
        self.load_vms()

    def about(self):
        """
            Description: Invoked when the user clicks on the 'About' button in the toolbar.
            Arguments: None
            Returns: Nothing
        """

        self.lastclick = int(time())  # Last click timestamp update
        About()

    def forget_creds(self):
        """
            Description: Invoked when the user clicks on the 'Forget credentials' button in the toolbar.
            Arguments: None
            Returns: Nothing
        """

        global conf

        self.lastclick = int(time())  # Last click timestamp update

        reply = QMessageBox.question(None,
                                     _('apptitle') + ': ' + _('confirm'),
                                     _('confirm_forget_creds'),
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)

        if reply == QMessageBox.Yes:
            remove(conf.USERCREDSFILE)
            self.forgetCredsAction.setDisabled(True)
            QMessageBox.information(None,
                                    _('apptitle') + ': ' + _('success'),
                                    _('creds_forgotten'))

    def list_vmpools(self, row, delta, step, vmpools):
        """
            Description: Creates one row per VmPool that the user has access to.
            Arguments: 1. The index of the first row to loop over.
                       2. Delta step to sum to the progress bar.
                       3. The current step of the progress bar
                       4. The oVirt list of VmPools.
            Returns: The final step of the progress bar
        """

        # For cleanness reasons, we'll firstly show available VmPools
        for vm in vmpools:
            vmname = vm.name

            # OS icon
            ostype = 'vmpool'
            imageOsicon = self.make_button(ostype,
                                           '<b>' + _('vmpool') + '</b>')

            # Machine name
            gridvmname = QLabel(vmname)
            gridvmname.setStyleSheet(STANDARDCELLCSS)
            gridvmname.setAlignment(Qt.AlignCenter)

            # Acquire VM button
            connect = self.make_button('grab', _('grab_vm_vmpool'))
            connect.mousePressEvent = lambda x, r=row: self.acquire_vm_from_vmpool(
                r)

            # Fill row with known info
            self.grid.addWidget(imageOsicon, row, 0)
            self.grid.addWidget(gridvmname, row, 1)
            self.grid.addWidget(connect, row, 2)

            # Store the correspondence between row number <-> VMPool data
            vmd = VmData()
            vmd.vmid = vm.id
            vmd.vmname = vm.name
            vmd.vmstatus = None
            vmd.vmtype = 'vmpool'
            self.vmdata[row] = vmd

            row += 1

            step += delta
            self.pbar.setValue(step)

        return step

    def list_vms(self, row, delta, step, vms):
        """
            Description: Creates one row per VM that the user has access to.
            Arguments: 1. The index of the first row to loop over.
                       2. Delta step to sum to the progress bar.
                       3. The current step of the progress bar
                       4. The oVirt list of VMs.
            Returns: Nothing
        """
        # Now we'll show up the VMs
        for vm in vms:
            vmname = vm.name
            vmstatus = vm.status.value

            # OS icon
            ostype = self.get_os_icon(vm.os.type.lower())
            imageOsicon = self.make_button(
                ostype, '<b>%s</b> OS' % (ostype.capitalize()))

            # Machine name
            gridvmname = QLabel(vmname)
            gridvmname.setStyleSheet(STANDARDCELLCSS)
            gridvmname.setAlignment(Qt.AlignCenter)

            # Connect button. Depending on whether it has already been hit, a different icon
            # will be shown and the behavior will also be different.
            if vmname not in self.openviewer_vms:
                connect = self.make_button('connect', _('connect'))
                connect.mousePressEvent = lambda x, r=row: self.connect(r)
            else:
                connect = self.make_button('viewer',
                                           _('viewer_already_opened'))

            # Status icon
            curaction = self.current_vm_status(vmstatus)
            imageSticon = self.make_button(vmstatus,
                                           self.toggle_action_text(vmstatus))
            imageSticon.mousePressEvent = lambda x, r=row: self.change_status(r
                                                                              )

            # Fill row with known info
            self.grid.addWidget(imageOsicon, row, 0)
            self.grid.addWidget(gridvmname, row, 1)
            self.grid.addWidget(imageSticon, row, 2)
            self.grid.addWidget(connect, row, 3)

            # Store the correspondence between row number <-> VM data
            vmd = VmData()
            vmd.vmid = vm.id
            vmd.vmname = vm.name
            vmd.vmstatus = vmstatus
            vmd.vmtype = 'vm'
            self.vmdata[row] = vmd

            row += 1

            step += delta
            self.pbar.setValue(step)

    def load_vms(self):
        """
            Description: Main core VM loader method. Will connect to oVirt, get the VM list and render them.
            Arguments: None
            Returns: Nothing
        """

        global conf, MAXHEIGHT, BACKGROUNDCSS, STANDARDCELLCSS

        QObjectCleanupHandler().add(self.layout())
        if not conf.USERNAME:
            quit()

        # Used to store row <-> VM correspondence
        self.vmdata = {}

        step = 0

        self.pbarlayout = QGridLayout(self)
        self.pbar = QProgressBar(self)
        self.grid = QGridLayout()
        self.grid.setHorizontalSpacing(0)

        # Initially, set the layout to the progress bar
        self.pbar.setGeometry(250, 250, 200, 100)
        self.pbar.setValue(0)
        self.pbarlayout.addWidget(self.pbar, 0, 0)
        self.setLayout(self.pbarlayout)

        self.setStyleSheet(BACKGROUNDCSS)

        try:
            # Try getting the VM list from oVirt
            vms_serv = conf.OVIRTCONN.vms_service()
            vmpools_serv = conf.OVIRTCONN.vm_pools_service()
            vms = sorted(vms_serv.list(),
                         key=self.p22p3_compare_vms(self.compare_vms))
            vmpools = sorted(vmpools_serv.list(),
                             key=self.p22p3_compare_vms(self.compare_vms))
        except Error:
            QMessageBox.critical(None,
                                 _('apptitle') + ': ' + _('error'),
                                 _('unexpected_connection_drop'))
            quit()

        # Set the main widget height based on the number of VMs
        winheight = self.vm_based_resize(len(vms) + len(vmpools))
        if len(vms) + len(vmpools) > 0:
            delta = int(100 / (len(vms) + len(vmpools)))
        else:
            delta = int(100)

        if vmpools:
            step = self.list_vmpools(1, delta, step, vmpools)
        if vms:
            self.list_vms(len(vmpools) + 1, delta, step, vms)

        # Once loading has concluded, progress bar is dismissed and the layout set to the QGridLayout
        self.pbar.hide()
        QObjectCleanupHandler().add(self.layout())

        # First row is special: Number of VMs + Toolbar
        total_machines = QLabel(
            _('total_machines') + ': <font color="#AA8738">' + str(len(vms)) +
            '</font>, ' + _('total_vmpools') + ': <font color="#AA8738">' +
            str(len(vmpools)) + '</font>', self)
        self.grid.addWidget(total_machines, 0, 0, 1, 3, Qt.AlignCenter)
        self.generate_toolbar()

        # We wrap the main widget inside another widget with a vertical scrollbar
        wrapper = QWidget()
        wrapper.setLayout(self.grid)
        scroll = QScrollArea()
        scroll.setWidget(wrapper)
        scroll.setWidgetResizable(True)
        scroll.setFixedHeight(winheight)
        layout = QVBoxLayout()
        layout.addWidget(scroll)

        layout.setContentsMargins(0, 0, 0, 20)
        self.setLayout(layout)

    def update_status_icon(self, i, newstatus):
        """
            Description: Invoked when the background thread emits the signal announcing a status
                         change, so the corresponding VM status icon should be updated.
            Arguments: i: Row that has changed their status. The VM can be matched with VmData().
                       newstatus: The new status for the VM.
            Returns: Nothing
        """

        imageSticon = self.make_button(newstatus,
                                       self.toggle_action_text(newstatus))
        imageSticon.mousePressEvent = lambda x, r=i: self.change_status(r)
        self.grid.addWidget(imageSticon, i, 2)

    def logout_warn(self):
        """
            Description: Called if the warn_autologout setting has been set in the config. It
                         will warn user to reset their idle, otherwise an enforced logout will
                         be performed by calling the logout() method.
            Arguments: None
            Returns: Nothing
        """

        self.autologoutwarnwin = QMessageBox(None)
        self.autologoutwarnwin.setIcon(QMessageBox.Warning)

        self.autologoutwarnwin.setText(_('auto_logout_warn'))
        self.autologoutwarnwin.setWindowTitle(_('auto_logout_warn_title'))
        self.autologoutwarnwin.setStandardButtons(QMessageBox.Ok)
        self.autologoutwarnwin.buttonClicked.connect(
            self.autologoutwarn_accepted)
        self.autologoutwarnwin.exec_()

    def logout(self, reconnect=False):
        """
            Description: Invoked when the autologout parameter is set in the config and the
                         idle time is overreached. This should require authentication again.
            Arguments: None
            Returns: Nothing
        """

        if conf.OVIRTCONN:
            try:
                conf.SOCKOBJ.close()
            except Error:
                pass

        self.stopThread = True
        conf.USERNAME = None
        self.autologoutWarn = False

        # Hide the layout so next user doesn't see the previous content
        self.hide()

        try:
            self.autologoutwarnwin.accept()
        except AttributeError:
            # Usually when the notify_autologout setting has not been set
            pass

        # This will be called upon autologout events
        if reconnect:
            creds = Credentials(None)
            creds.finished.connect(self.start_vmpane)
            creds.exec_()

    def autologoutwarn_accepted(self):
        """
            Description: Callback issued when the user accepts the message box warning
                         about an imminent autologout event.
            Arguments: None
            Returns: Nothing
        """
        self.lastclick = int(time())
        self.autologoutWarn = False  # This will make the warning be shown next times as well

    def refresh_statuses(self):
        """
            Description: Background thread that will look for VM status changes and
                         send a signal to the main thread so the corresponding icons
                         are updated. Also, if there's a change in the number of VMs
                         that the user controls, the main Widgets will be reloaded.
                         It'll also check the autologout setting & act accordingly.
            Arguments: None
            Returns: Nothing ("infinite" loop)
        """

        global UPDATESLEEPINTERVAL

        autologout = False
        while 1 and not self.stopThread:
            if conf.OVIRTCONN:
                try:
                    vms_service = conf.OVIRTCONN.vms_service()
                    ovirt_num_machines = len(vms_service.list())
                except Error:
                    sys.exit('[ERROR] ' + _('unexpected_connection_drop'))

                if ovirt_num_machines != len(self.vmdata):
                    # If the number of VMs has changed, we should reload the main widget
                    self.reloadsignal.emit()
                else:
                    for i in self.vmdata:
                        vmid = self.vmdata[i].vmid
                        vmstatus = self.vmdata[i].vmstatus
                        try:
                            ovirtvm = vms_service.list(search='id=%s' %
                                                       (vmid))[0]
                        except Error:
                            sys.exit('[ERROR] ' +
                                     _('unexpected_connection_drop'))

                        if ovirtvm:
                            curstatus = ovirtvm.status.value
                            if vmstatus != curstatus:
                                # If there has been a status change, emit the signal to update icons
                                self.vmdata[i].vmstatus = curstatus
                                self.updatesignal.emit(i, curstatus)

                # If there is any currently open viewer, we'll reset the idle time so we don't close the session
                # while there still is any open session.
                if self.openviewer_vms:
                    self.lastclick = int(time())  # Last click timestamp update

                # If the autologout warning has not been shown yet and it's configured, we do so
                if conf.CONFIG['autologout'] != 0 and conf.CONFIG['notify_autologout'] != 0 and not self.autologoutWarn and \
                   (int(time() - self.lastclick) >= (conf.CONFIG['autologout'] - conf.CONFIG['notify_autologout']) * 60):
                    self.autologoutWarn = True
                    self.warnlogoutsignal.emit()

                # If there's no credentials file and autologout is set, we check for the last
                # click and if surpassed, a logout will be performed.
                if conf.CONFIG['autologout'] != 0 and not isfile(
                        conf.USERCREDSFILE):
                    if (int(time()) - self.lastclick) >= (
                            conf.CONFIG['autologout'] * 60):
                        self.stopThread = True
                        autologout = True

                sleep(UPDATESLEEPINTERVAL)
            else:
                return

        if autologout:
            self.logoutsignal.emit(True)

    def restart_thread(self):
        """
            Description: Simply starts or restarts the background thread
            Arguments: None
            Returns: Nothing
        """

        self.stopThread = False
        self.lastclick = int(time())
        self.thread = threading.Thread(target=self.refresh_statuses, args=())
        self.thread.daemon = True  # Daemonize thread
        self.thread.start()

    def start_vmpane(self):
        """
            Description: This method will be called when the Credentials dialog is closed.
                         This should happen on successful authentication. We should then
                         load the main widget and fill it with the VMs that the user has
                         permissions on. Additionally, a background thread will be started
                         to check VM status changed so the main widget is updated should
                         this happen.
            Arguments: None
            Returns: Nothing
        """

        self.show()

        self.load_vms()
        self.center()

        self.restart_thread()

    def confirm_quit(self):
        """
            Description: Asks for confirmation from the user's side to close the app.
            Arguments: None
            Returns: True if the user wants to close the app, False otherwise.
        """

        global conf

        reply = QMessageBox.question(None,
                                     _('apptitle') + ': ' + _('confirm'),
                                     _('confirm_quit'),
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.logout(reconnect=False)
            return True
        else:
            return False

    def quit_button(self):
        """
            Description: Triggered when the Exit button in the Toolbar is hit. Confirmation will
                         be required.
            Arguments: None
            Returns: Nothing, exits if user confirms.
        """

        self.lastclick = int(time())  # Last click timestamp update

        if self.confirm_quit():
            quit()

    def closeEvent(self, event):
        """
            Description: The red 'x'. If the user hits it, we'll ask for confirmation.
            Arguments: None
            Returns: Nothing, exits if users confirms.
        """

        if self.confirm_quit():
            event.accept()
        else:
            event.ignore()

    def initUI(self):
        """
            Description: Sets the size of the widget, the window title, centers
                         the window, connects signals to methods and opens the
                         Credentials dialog if needed.
            Arguments: None
            Returns: Nothing.
        """

        global conf, MAXWIDTH, MAXHEIGHT, IMGDIR, VERSION

        self.setFixedSize(MAXWIDTH, MAXHEIGHT)
        self.center()

        self.setWindowTitle(_('apptitle') + ' ' + VERSION)
        self.setWindowIcon(QIcon(IMGDIR + 'appicon.png'))
        self.show()

        self.updatesignal.connect(self.update_status_icon)
        self.logoutsignal.connect(self.logout)
        self.warnlogoutsignal.connect(self.logout_warn)
        self.reloadsignal.connect(self.load_vms)

        if not conf.USERNAME:
            creds = Credentials(self)
            creds.finished.connect(self.start_vmpane)
            creds.exec_()

    def center(self):
        """
            Description: Just centers the window
            Arguments: None
            Returns: Nothing
        """

        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
示例#7
0
文件: hwdevice.py 项目: damon000/SPMT
class HWdevice(QObject):
    
    # signal: sig1 (thread) is done - emitted by signMessageFinish
    sig1done = pyqtSignal(str)
    # signal: sigtx (thread) is done - emitted by signTxFinish
    sigTxdone = pyqtSignal(bytearray, str)
    # signal: sigtx (thread) is done (aborted) - emitted by signTxFinish
    sigTxabort = pyqtSignal()
    # signal: tx_progress percent - emitted by perepare_transfer_tx_bulk
    tx_progress = pyqtSignal(int)
    # signal: sig_progress percent - emitted by signTxSign
    sig_progress = pyqtSignal(str)
    
    def __init__(self, *args, **kwargs):
        QObject.__init__(self, *args, **kwargs)
        # Device Lock for threads
        self.lock = threading.Lock()
        printDbg("Creating HW device class")
        self.initDevice()
        # Connect signal
        self.sig_progress.connect(self.updateSigProgress)
        
        
    @process_ledger_exceptions
    def initDevice(self):
        try:
            self.lock.acquire()
            self.status = 0
            if hasattr(self, 'dongle'):
                self.dongle.close()
            self.dongle = getDongle(False)
            printOK('Ledger Nano S drivers found')
            self.chip = btchip(self.dongle)
            printDbg("Ledger Initialized")
            ver = self.chip.getFirmwareVersion()
            printOK("Ledger HW device connected [v. %s]" % str(ver.get('version')))
            self.status = 2
            
        except Exception as e:
            if hasattr(self, 'dongle'):
                self.status = 1
                self.dongle.close()
                
        finally:
            self.lock.release()
            
        
    
    # Status codes:
    # 0 - not connected
    # 1 - not in pivx app
    # 2 - fine
    def getStatus(self):
        messages = {
            0: 'Unable to connect to the device.',
            1: 'Unable to connect to the device. Please check that the PIVX app on the device is open, and try again.',
            2: 'Hardware device connected.'}
        return self.status, messages[self.status]
    
    
    
    
    @process_ledger_exceptions
    def prepare_transfer_tx(self, caller, bip32_path,  utxos_to_spend, 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()
        try:
            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)
                
                # completion percent emitted
                completion = int(45*idx / len(utxos_to_spend))
                self.tx_progress.emit(completion)
                
                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']
                })
                
                # completion percent emitted
                completion = int(95*idx / len(utxos_to_spend))
                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])
            
            # completion percent emitted
            self.tx_progress.emit(99)
        
        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
        
        # completion percent emitted
        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.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)
        
        
        
    @process_ledger_exceptions
    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 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)
            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 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()
    
    
    
    
    @process_ledger_exceptions        
    def signMess(self, caller, path, message):
        # Ledger doesn't accept characters other that ascii printable:
        # https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_sign_message
        message = message.encode('ascii', 'ignore')
        message_sha = splitString(single_sha256(message).hex(),32);
        
        # Connection pop-up
        mBox  = QMessageBox(caller.ui)
        warningText = "Another application (such as Ledger Wallet app) has probably taken over "
        warningText += "the communication with the Ledger device.<br><br>To continue, close that application and "
        warningText += "click the <b>Retry</b> button.\nTo cancel, click the <b>Abort</b> button"
        mBox.setText(warningText)
        mBox.setWindowTitle("WARNING")
        mBox.setStandardButtons(QMessageBox.Retry | QMessageBox.Abort);
        
        # Ask confirmation
        self.lock.acquire()
        info = self.chip.signMessagePrepare(path, message)
        self.lock.release()
        while info['confirmationNeeded'] and info['confirmationType'] == 34:
            ans = mBox.exec_()        
            
            if ans == QMessageBox.Abort:
                raise Exception("Reconnect HW device")
            
            # we need to reconnect the device
            self.dongle.close()
            self.initDevice()
            
            self.lock.acquire()
            info = self.chip.signMessagePrepare(path, message)
            self.lock.release()

        printOK('Signing Message')
        self.mBox = QMessageBox(caller.ui)
        messageText = "Check display of your hardware device\n\n" + "- masternode message hash:\n\n%s\n\n-path:\t%s\n" % (message_sha, path)
        self.mBox.setText(messageText)
        self.mBox.setIconPixmap(caller.ui.ledgerImg.scaledToHeight(200, Qt.SmoothTransformation))
        self.mBox.setWindowTitle("CHECK YOUR LEDGER")
        self.mBox.setStandardButtons(QMessageBox.NoButton)
        self.mBox.show()
        # Sign message
        ThreadFuns.runInThread(self.signMessageSign, (), self.signMessageFinish)



    
    @process_ledger_exceptions
    def signMessageSign(self, ctrl):
        self.lock.acquire()
        try:
            self.signature = self.chip.signMessageSign()
            
            
        except:
            self.signature = None
            
        finally:
            self.lock.release()
    
    
    
           
    def signMessageFinish(self):
        self.mBox.accept()
        if self.signature != None:
            if len(self.signature) > 4:
                rLength = self.signature[3]
                r = self.signature[4 : 4 + rLength]
                if len(self.signature) > 4 + rLength + 1:               
                    sLength = self.signature[4 + rLength + 1]
                    if len(self.signature) > 4 + rLength + 2: 
                        s = self.signature[4 + rLength + 2:]
                        if rLength == 33:
                            r = r[1:]
                        if sLength == 33:
                            s = s[1:]
            
                        work = bytes(chr(27 + 4 + (self.signature[0] & 0x01)), "utf-8") + r + s
                        printOK("Message signed")
                        sig1 = work.hex()
                    else:
                        printDbg('client.signMessageSign() returned invalid response (code 3): ' + self.signature.hex())
                        sig1 = "None"
                else:
                    printDbg('client.signMessageSign() returned invalid response (code 2): ' + self.signature.hex())
                    sig1 = "None"
            else:
                printDbg('client.signMessageSign() returned invalid response (code 1): ' + self.signature.hex())
                sig1 = "None"
        else:
            printOK("Signature refused by the user")
            sig1 = "None"
        
        self.sig1done.emit(sig1)
        
        
        
        
    def signTxSign(self, ctrl):
        self.lock.acquire()
        try:
            starting = True
            curr_input_signed = 0
            # 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):
                   
                self.chip.startUntrustedTransaction(starting, idx, self.trusted_inputs, new_input['locking_script'])
                 
                self.chip.finalizeInputFull(self.all_outputs_raw)
                
                sig = self.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
                
                # signature percent emitted
                curr_input_signed += 1
                completion = int(100*curr_input_signed / len(self.arg_inputs))
                self.sig_progress.emit(str(completion))
                
            self.new_transaction.lockTime = bytearray([0, 0, 0, 0])
            self.tx_raw = bytearray(self.new_transaction.serialize())
            self.sig_progress.emit("100")
            
        except Exception as e:
            if e.sw != 0x6985:
                self.status = 0
                printException(getCallerName(), getFunctionName(), e.message, e.args)
                
            self.tx_raw = None
    
        finally:
            self.lock.release()
            if self.status == 0:
                self.dongle.close()
                self.initDevice()
    
    
            
    def signTxFinish(self):
        self.mBox2.accept()
        try:
            if self.tx_raw is not None:
                # Signal to be catched by FinishSend on TabRewards / dlg_sewwpAll
                self.sigTxdone.emit(self.tx_raw, str(round(self.amount / 1e8, 8)))
            else:
                printOK("Transaction refused by the user")
                self.sigTxabort.emit()
                
        except Exception as e:    
            printDbg(e) 
                    
    
    
    def updateSigProgress(self, text):
        messageText = self.messageText + "Signature Progress: <b style='color:red'>" + text + " %</b>" 
        self.mBox2.setText(messageText)
        QApplication.processEvents()
示例#8
0
class HWdevice(QObject):
    # signal: sig1 (thread) is done - emitted by signMessageFinish
    sig1done = pyqtSignal(str)
    # signal: sigtx (thread) is done - emitted by signTxFinish
    sigTxdone = pyqtSignal(bytearray, str)

    def __init__(self, *args, **kwargs):
        QObject.__init__(self, *args, **kwargs)
        # Device Lock for threads
        printDbg("Creating HW device class")
        self.initDevice()

    @process_ledger_exceptions
    def initDevice(self):
        try:
            if hasattr(self, 'dongle'):
                self.dongle.close()
            self.dongle = getDongle(False)
            printOK('Ledger Nano S drivers found')
            self.chip = btchip(self.dongle)
            printDbg("Ledger Initialized")
            self.initialized = True
            ver = self.chip.getFirmwareVersion()
            printOK("Ledger HW device connected [v. %s]" %
                    str(ver.get('version')))

        except Exception as e:
            err_msg = 'error Initializing Ledger'
            printException(getCallerName(), getFunctionName(), err_msg, e.args)
            self.initialized = False
            if hasattr(self, 'dongle'):
                self.dongle.close()

    # Status codes:
    # 0 - not connected
    # 1 - not in pivx app
    # 2 - fine
    @process_ledger_exceptions
    def getStatusCode(self):
        try:
            if self.initialized:
                if not self.checkApp():
                    statusCode = 1
                else:
                    statusCode = 2
            else:
                statusCode = 0
        except Exception as e:
            err_msg = 'error in getStatusCode'
            printException(getCallerName(), getFunctionName(), err_msg, e.args)
            statusCode = 0
        return statusCode

    @process_ledger_exceptions
    def getStatusMess(self, statusCode=None):
        if statusCode == None or not statusCode in [0, 1, 2]:
            statusCode = self.getStatusCode()
        messages = {
            0: 'Unable to connect to the device',
            1: 'Open PIVX app on Ledger device',
            2: 'HW DEVICE CONNECTED!'
        }
        return messages[statusCode]

    @process_ledger_exceptions
    def checkApp(self):
        printDbg("Checking app")
        try:
            firstAddress = self.chip.getWalletPublicKey(MPATH + "0'/0/0").get(
                'address')[12:-2]
            if firstAddress[0] == 'D':
                printOK("found PIVX app on ledger device")
                return True
        except Exception as e:
            err_msg = 'error in checkApp'
            printException(getCallerName(), getFunctionName(), err_msg, e.args)
        return False

    @process_ledger_exceptions
    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)

    @process_ledger_exceptions
    def scanForAddress(self, account, spath, isTestnet=False):
        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:
            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
        return curr_addr

    @process_ledger_exceptions
    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)
            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)

        return (found, spath)

    @process_ledger_exceptions
    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()

    @process_ledger_exceptions
    def signMess(self, caller, path, message):
        # Ledger doesn't accept characters other that ascii printable:
        # https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_sign_message
        message = message.encode('ascii', 'ignore')
        message_sha = splitString(single_sha256(message).hex(), 32)

        # Connection pop-up
        mBox = QMessageBox(caller.ui)
        warningText = "Another application (such as Ledger Wallet app) has probably taken over "
        warningText += "the communication with the Ledger device.<br><br>To continue, close that application and "
        warningText += "click the <b>Retry</b> button.\nTo cancel, click the <b>Abort</b> button"
        mBox.setText(warningText)
        mBox.setWindowTitle("WARNING")
        mBox.setStandardButtons(QMessageBox.Retry | QMessageBox.Abort)

        # Ask confirmation
        info = self.chip.signMessagePrepare(path, message)
        while info['confirmationNeeded'] and info['confirmationType'] == 34:
            ans = mBox.exec_()
            # we need to reconnect the device
            self.dongle.close()
            self.initialized = False
            self.initDevice()

            if ans == QMessageBox.Abort:
                raise Exception('Message Signature failed')

            info = self.chip.signMessagePrepare(path, message)

        printOK('Signing Message')
        self.mBox = QMessageBox(caller.ui)
        messageText = "Check display of your hardware device\n\n" + "- masternode message hash:\n\n%s\n\n-path:\t%s\n" % (
            message_sha, path)
        self.mBox.setText(messageText)
        self.mBox.setIconPixmap(
            caller.ui.ledgerImg.scaledToHeight(200, Qt.SmoothTransformation))
        self.mBox.setWindowTitle("CHECK YOUR LEDGER")
        self.mBox.setStandardButtons(QMessageBox.NoButton)

        self.mBox.show()
        # Sign message
        ThreadFuns.runInThread(self.signMessageSign, (),
                               self.signMessageFinish)

    @process_ledger_exceptions
    def signMessageSign(self, ctrl):
        try:
            self.signature = self.chip.signMessageSign()

        except:
            self.signature = None

    @process_ledger_exceptions
    def signMessageFinish(self):
        self.mBox.accept()
        if self.signature != None:
            if len(self.signature) > 4:
                rLength = self.signature[3]
                r = self.signature[4:4 + rLength]
                if len(self.signature) > 4 + rLength + 1:
                    sLength = self.signature[4 + rLength + 1]
                    if len(self.signature) > 4 + rLength + 2:
                        s = self.signature[4 + rLength + 2:]
                        if rLength == 33:
                            r = r[1:]
                        if sLength == 33:
                            s = s[1:]

                        work = bytes(chr(27 + 4 + (self.signature[0] & 0x01)),
                                     "utf-8") + r + s
                        printOK("Message signed")
                        sig1 = work.hex()
                    else:
                        printDbg(
                            'client.signMessageSign() returned invalid response (code 3): '
                            + self.signature.hex())
                        sig1 = "None"
                else:
                    printDbg(
                        'client.signMessageSign() returned invalid response (code 2): '
                        + self.signature.hex())
                    sig1 = "None"
            else:
                printDbg(
                    'client.signMessageSign() returned invalid response (code 1): '
                    + self.signature.hex())
                sig1 = "None"
        else:
            printOK("Signature refused by the user")
            sig1 = "None"

        self.sig1done.emit(sig1)

    @process_ledger_exceptions
    def signTxSign(self, ctrl):
        try:
            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):
                self.chip.startUntrustedTransaction(
                    starting, idx, self.trusted_inputs,
                    new_input['locking_script'])

                self.chip.finalizeInputFull(self.all_outputs_raw)

                sig = self.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())

        except Exception as e:
            printException(getCallerName(), getFunctionName(),
                           "Signature Exception", e.args)
            self.tx_raw = None

    @process_ledger_exceptions
    def signTxFinish(self):
        self.mBox2.accept()
        try:
            if self.tx_raw is not None:
                # Signal to be catched by FinishSend on TabRewards
                self.sigTxdone.emit(self.tx_raw,
                                    str(round(self.amount / 1e8, 8)))
            else:
                printOK("Transaction refused by the user")

        except Exception as e:
            printDbg(e)