Пример #1
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix == b"getlabel":
         label = self.get_label()
         obj = {
             "title": "Device's label is: %s" % label,
         }
         stream = BytesIO(label.encode())
         return stream, obj
     elif prefix == b"setlabel":
         label = stream.read().strip().decode('ascii')
         if not label:
             raise AppError('Device label cannot be empty')
         scr = Prompt("\n\nSet device label to: %s\n" % label,
                      "Current device label: %s" % self.get_label())
         res = await show_screen(scr)
         if res is False:
             return None
         self.set_label(label)
         obj = {
             "title": "New device label: %s" % label,
         }
         return BytesIO(label.encode()), obj
     else:
         raise AppError("Invalid command")
Пример #2
0
 async def menu(self, show_screen):
     buttons = [(None, "Your wallets")]
     buttons += [(w, w.name) for w in self.wallets if not w.is_watchonly]
     if len(buttons) != (len(self.wallets) + 1):
         buttons += [(None, "Watch only wallets")]
         buttons += [(w, w.name) for w in self.wallets if w.is_watchonly]
     menuitem = await show_screen(Menu(buttons, last=(255, None)))
     if menuitem == 255:
         # we are done
         return False
     else:
         w = menuitem
         # pass wallet and network
         self.show_loader(title="Loading wallet...")
         cmd = await w.show(self.network, show_screen)
         if cmd == DELETE:
             scr = Prompt(
                 "Delete wallet?",
                 'You are deleting wallet "%s".\n'
                 "Are you sure you want to do it?" % w.name,
             )
             conf = await show_screen(scr)
             if conf:
                 self.delete_wallet(w)
         elif cmd == EDIT:
             scr = InputScreen(title="Enter new wallet name",
                               note="",
                               suggestion=w.name)
             name = await show_screen(scr)
             if name is not None and name != w.name and name != "":
                 w.name = name
                 w.save(self.keystore)
         return True
Пример #3
0
    async def export_mnemonic(self):
        if await self.show(
                Prompt(
                    "Warning", "You need to confirm your PIN code "
                    "to export your recovery phrase.\n\n"
                    "Your recovery phrase will be saved "
                    "to the SD card as plain text.\n\n"
                    "Anybody who has access to this SD card "
                    "will be able to read your recovery phrase!\n\n"
                    "Continue?")):
            self.lock()
            await self.unlock()

            filename = "seed-export-%s.txt" % self.mnemonic.split()[0]
            filepath = "%s/%s" % (self.sdpath, filename)

            if not platform.is_sd_present():
                raise KeyStoreError("SD card is not present")

            platform.mount_sdcard()

            with open(filepath, "w") as f:
                f.write("bip39: ")
                f.write(self.mnemonic)

            platform.unmount_sdcard()

            await self.show(
                Alert("Success!",
                      "Your seed is exported.\n\nName: %s" % filename,
                      button_text="OK"))
Пример #4
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix != b"hello":
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     name = stream.read().decode()
     # ask the user if he really wants it
     # build a screen
     scr = Prompt(
         "Say hello?", "Are you sure you want to say hello to\n\n%s?\n\n"
         "Saying hello can compromise your security!" % name)
     # show screen and wait for result
     res = await show_screen(scr)
     # check if he confirmed
     if not res:
         return
     obj = {
         "title": "Hello!",
     }
     d = b"Hello " + name.encode()
     return BytesIO(d), obj
Пример #5
0
 async def menu(self, show_screen):
     buttons = [(None, "Your wallets")]
     buttons += [(w, w.name) for w in self.wallets]
     menuitem = await show_screen(Menu(buttons, last=(255, None)))
     if menuitem == 255:
         # we are done
         return False
     else:
         w = menuitem
         # pass wallet and network
         scr = WalletScreen(w, self.network, idx=w.unused_recv)
         cmd = await show_screen(scr)
         if cmd == DELETE:
             scr = Prompt(
                 "Delete wallet?", "You are deleting wallet \"%s\".\n"
                 "Are you sure you want to do it?" % w.name)
             conf = await show_screen(scr)
             if conf:
                 self.delete_wallet(w)
         elif cmd == EDIT:
             scr = InputScreen(title="Enter new wallet name",
                               note="",
                               suggestion=w.name)
             name = await show_screen(scr)
             if name is not None and name != w.name and name != "":
                 w.name = name
                 w.save(self.keystore)
         return True
Пример #6
0
    async def storage_menu(self):
        """Manage storage"""
        enabled = self.connection.isCardInserted()
        buttons = [
            # id, text
            (None, "Smartcard storage"),
            (0, "Save key to the card", enabled),
            (1, "Load key from the card", enabled),
            (2, "Delete key from the card", enabled),
            (3, "Use a different card", enabled)
        ]

        # we stay in this menu until back is pressed
        while True:
            note = "Card fingerprint: %s" % self.hexid
            # wait for menu selection
            menuitem = await self.show(
                Menu(buttons, note=note, last=(255, None)))
            # process the menu button:
            # back button
            if menuitem == 255:
                return
            elif menuitem == 0:
                await self.save_mnemonic()
                await self.show(
                    Alert(
                        "Success!",
                        "Your key is stored on the smartcard now.",
                        button_text="OK",
                    ))
            elif menuitem == 1:
                await self.load_mnemonic()
                await self.show(
                    Alert("Success!", "Your key is loaded.", button_text="OK"))
            elif menuitem == 2:
                await self.delete_mnemonic()
                await self.show(
                    Alert(
                        "Success!",
                        "Your key is deleted from the smartcard.",
                        button_text="OK",
                    ))
            elif menuitem == 3:
                if await self.show(
                        Prompt(
                            "Switching the smartcard",
                            "To use a different smartcard you need "
                            "to provide a PIN for current one first!\n\n"
                            "Continue?")):
                    self.lock()
                    await self.unlock()
                    self.lock()
                    self.applet.close_secure_channel()
                    await self.show(
                        Alert("Please swap the card",
                              "Now you can insert another card and set it up.",
                              button_text="Continue"))
                    await self.check_card(check_pin=True)
                    await self.unlock()
Пример #7
0
 async def show_mnemonic(self):
     if await self.show(
             Prompt(
                 "Warning", "You need to confirm your PIN code "
                 "to display your recovery phrase.\n\n"
                 "Continue?")):
         self.lock()
         await self.unlock()
         await self.show(MnemonicScreen(self.mnemonic))
Пример #8
0
 async def confirm_new_wallet(self, w, show_screen):
     keys = w.get_key_dicts(self.network)
     for k in keys:
         k["mine"] = self.keystore.owns(k["key"])
     if not any([k["mine"] for k in keys]):
         if not await show_screen(
                 Prompt("Warning!",
                        "None of the keys belong to the device.\n\n"
                        "Are you sure you still want to add the wallet?")):
             return False
     return await show_screen(ConfirmWalletScreen(w.name, w.full_policy, keys, w.is_miniscript))
Пример #9
0
 async def confirm_wallets(self, wallets, show_screen):
     # check if any inputs belong to unknown wallets
     # wallets is a dict: {wallet: amount}
     if None not in wallets:
         return True
     scr = Prompt(
         "Warning!",
         "\nUnknown wallet in inputs!\n\n\n"
         "The source wallet for some inputs is unknown! This means we can't verify change address.\n\n\n"
         "Hint:\nYou can cancel this transaction and import the wallet by scanning it's descriptor.\n\n\n"
         "Proceed to the transaction confirmation?",
     )
     proceed = await show_screen(scr)
     return proceed
Пример #10
0
 async def process_host_command(self, stream, show_fn):
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix not in self.prefixes:
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     mnemonic = stream.read().strip().decode()
     if not bip39.mnemonic_is_valid(mnemonic):
         raise AppError("Invalid mnemonic!")
     scr = Prompt("Load this mnemonic to memory?", "Mnemonic:")
     table = MnemonicTable(scr)
     table.align(scr.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 30)
     table.set_mnemonic(mnemonic)
     if await show_fn(scr):
         self.keystore.set_mnemonic(mnemonic)
     return None
Пример #11
0
 async def process_host_command(self, stream, show_screen):
     if self.keystore.is_locked:
         raise AppError("Device is locked")
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix == b"slip77":
         if not await show_screen(
                 Prompt(
                     "Confirm the action",
                     "Send master blinding private key\nto the host?\n\n"
                     "Host is requesting your\nSLIP-77 blinding key.\n\n"
                     "It will be able to watch your funds and unblind transactions."
                 )):
             return
         return BytesIO(self.keystore.slip77_key.wif(
             NETWORKS[self.network])), {}
     raise AppError("Unknown command")
Пример #12
0
 async def process_host_command(self, stream, show_screen):
     """
     If command with one of the prefixes is received
     it will be passed to this method.
     Should return a tuple: 
     - stream (file, BytesIO etc) 
     - meta object with title and note
     """
     # reads prefix from the stream (until first space)
     prefix = self.get_prefix(stream)
     if prefix != b"signmessage":
         # WTF? It's not our data...
         raise AppError("Prefix is not valid: %s" % prefix.decode())
     # data format: message to sign<space>derivation_path
     # read all and delete all crap at the end (if any)
     # also message should be utf-8 decodable
     data = stream.read().strip().decode()
     if " " not in data:
         raise AppError("Invalid data encoding")
     arr = data.split(" ")
     derivation_path = arr[-1]
     message = " ".join(arr[:-1])
     # if we have fingerprint
     if not derivation_path.startswith("m/"):
         fingerprint = unhexlify(derivation_path[:8])
         if fingerprint != self.keystore.fingerprint:
             raise AppError("Not my fingerprint")
         derivation_path = "m" + derivation_path[8:]
     derivation_path = bip32.parse_path(derivation_path)
     # ask the user if he really wants to sign this message
     scr = Prompt(
         "Sign message with private key at %s?" %
         bip32.path_to_str(derivation_path), "Message:\n%s" % message)
     res = await show_screen(scr)
     if res is False:
         return None
     sig = self.sign_message(derivation_path, message.encode())
     # for GUI we can also return an object with helpful data
     xpub = self.keystore.get_xpub(derivation_path)
     address = script.p2pkh(xpub.key).address()
     obj = {
         "title": "Signature for the message:",
         "note": "using address %s" % address
     }
     return BytesIO(sig), obj
Пример #13
0
    async def confirm_sighashes(self, meta, show_screen):
        """
        Checks if custom sighashes are used, warns the user and asks for confirmation.
        Returns one of the options:
        - None - sign with provided sighashes
        - self.DEFAULT_SIGHASH - sign only with default
        - False - interrupt signing process (user cancel)
        """
        sighash_name = self.get_sighash_info(self.DEFAULT_SIGHASH)["name"]
        # check if there are any custom sighashes
        used_custom_sighashes = any([
            inp.get("sighash", sighash_name) != sighash_name
            for inp in meta["inputs"]
        ])

        # no custom sighashes - just continue
        if not used_custom_sighashes:
            return None

        # ask the user if he wants to sign in case of non-default sighashes
        custom_sighashes = [
            ("Input %d: %s" % (i, inp.get("sighash", sighash_name)))
            for (i, inp) in enumerate(meta["inputs"])
            if inp.get("sighash", sighash_name) != sighash_name
        ]
        canceltxt = (("Only sign %s" % sighash_name)
                     if len(custom_sighashes) != len(meta["inputs"]) else
                     "Cancel")
        confirm = await show_screen(
            Prompt("Warning!",
                   "\nCustom SIGHASH flags are used!\n\n" +
                   "\n".join(custom_sighashes),
                   confirm_text="Proceed anyway",
                   cancel_text=canceltxt))
        if confirm:
            # we set sighash to None
            # if we want to use whatever sighash is provided in input
            return None
        # if we are forced to use default sighash - check
        # that not all inputs have custom sighashes
        if len(custom_sighashes) == len(meta["inputs"]):
            # nothing to sign
            return False
        return self.DEFAULT_SIGHASH
Пример #14
0
    async def save_mnemonic(self):
        if self.is_locked:
            raise KeyStoreError("Keystore is locked")
        if self.mnemonic is None:
            raise KeyStoreError("Recovery phrase is not loaded")

        path = await self.get_keypath(title="Where to save?",
                                      only_if_exist=False,
                                      note="Select media")
        if path is None:
            return
        filename = await self.get_input(suggestion=self.mnemonic.split()[0])
        if filename is None:
            return

        fullpath = "%s/%s.%s" % (path, self.fileprefix(path), filename)

        if fullpath.startswith(self.sdpath):
            if not platform.is_sd_present():
                raise KeyStoreError("SD card is not present")
            platform.mount_sdcard()

        if platform.file_exists(fullpath):
            scr = Prompt(
                "\n\nFile already exists: %s\n" % filename,
                "Would you like to overwrite this file?",
            )
            res = await self.show(scr)
            if res is False:
                if fullpath.startswith(self.sdpath):
                    platform.unmount_sdcard()
                return

        self.save_aead(fullpath,
                       plaintext=self.mnemonic.encode(),
                       key=self.enc_secret)
        if fullpath.startswith(self.sdpath):
            platform.unmount_sdcard()
        # check it's ok
        await self.load_mnemonic(fullpath)
        # return the full file name incl. prefix if saved to SD card, just the name if on flash
        return fullpath.split("/")[-1] if fullpath.startswith(
            self.sdpath) else filename
Пример #15
0
 async def save_mnemonic(self):
     await self.check_card(check_pin=True)
     encrypt = await self.show(
         Prompt("Encrypt the secret?",
                "\nIf you encrypt the secret on the card "
                "it will only work with this device.\n\n"
                "Otherwise it will be readable on any device "
                "after you enter the PIN code.\n\n"
                "Keep in mind that with encryption enabled "
                "wiping the device makes the secret unusable!",
                confirm_text="Yes, encrypt",
                cancel_text="Keep as plain text"))
     self.show_loader("Saving secret to the card...")
     d = self.serialize_data(
         {"entropy": bip39.mnemonic_to_bytes(self.mnemonic)},
         encrypt=encrypt,
     )
     self.applet.save_secret(d)
     self._is_key_saved = True
     # check it's ok
     await self.load_mnemonic()
Пример #16
0
    async def sign_psbt(self, stream, show_screen, encoding=BASE64_STREAM):
        if encoding == BASE64_STREAM:
            data = a2b_base64(stream.read())
            psbt = PSBT.parse(data)
        else:
            psbt = PSBT.read_from(stream)
        # check if all utxos are there and if there are custom sighashes
        sighash = SIGHASH.ALL
        custom_sighashes = []
        for i, inp in enumerate(psbt.inputs):
            if inp.witness_utxo is None:
                if inp.non_witness_utxo is None:
                    raise WalletError(
                        "Invalid PSBT - missing previous transaction")
            if inp.sighash_type and inp.sighash_type != SIGHASH.ALL:
                custom_sighashes.append((i, inp.sighash_type))

        if len(custom_sighashes) > 0:
            txt = [("Input %d: " % i) + SIGHASH_NAMES[sh]
                   for (i, sh) in custom_sighashes]
            canceltxt = "Only sign ALL" if len(custom_sighashes) != len(
                psbt.inputs) else "Cancel"
            confirm = await show_screen(
                Prompt("Warning!",
                       "\nCustom SIGHASH flags are used!\n\n" + "\n".join(txt),
                       confirm_text="Sign anyway",
                       cancel_text=canceltxt))
            if confirm:
                sighash = None
            else:
                if len(custom_sighashes) == len(psbt.inputs):
                    # nothing to sign
                    return
        wallets, meta = self.parse_psbt(psbt=psbt)
        # there is an unknown wallet
        # wallet is a list of tuples: (wallet, amount)
        if None in [w[0] for w in wallets]:
            scr = Prompt(
                "Warning!",
                "\nUnknown wallet in inputs!\n\n\n"
                "Wallet for some inpunts is unknown! This means we can't verify change addresses.\n\n\n"
                "Hint:\nYou can cancel this transaction and import the wallet by scanning it's descriptor.\n\n\n"
                "Proceed to the transaction confirmation?",
            )
            proceed = await show_screen(scr)
            if not proceed:
                return None
        spends = []
        for w, amount in wallets:
            if w is None:
                name = "Unknown wallet"
            else:
                name = w.name
            spends.append('%.8f BTC\nfrom "%s"' % (amount / 1e8, name))
        title = "Spending:\n" + "\n".join(spends)
        res = await show_screen(TransactionScreen(title, meta))
        if res:
            self.show_loader(title="Signing transaction...")
            sigsStart = 0
            for i, inp in enumerate(psbt.inputs):
                sigsStart += len(list(inp.partial_sigs.keys()))
            for w, _ in wallets:
                if w is None:
                    continue
                # fill derivation paths from proprietary fields
                w.update_gaps(psbt=psbt)
                w.save(self.keystore)
                w.fill_psbt(psbt, self.keystore.fingerprint)
                if w.has_private_keys:
                    w.sign_psbt(psbt, sighash)
            self.keystore.sign_psbt(psbt, sighash)
            # remove unnecessary stuff:
            out_psbt = PSBT(psbt.tx)
            sigsEnd = 0
            for i, inp in enumerate(psbt.inputs):
                sigsEnd += len(list(inp.partial_sigs.keys()))
                out_psbt.inputs[i].partial_sigs = inp.partial_sigs
            del psbt
            gc.collect()
            if sigsEnd == sigsStart:
                raise WalletError(
                    "We didn't add any signatures!\n\nMaybe you forgot to import the wallet?\n\nScan the wallet descriptor to import it."
                )
            if encoding == BASE64_STREAM:
                txt = b2a_base64(out_psbt.serialize()).decode().strip()
            else:
                txt = out_psbt.serialize()
            return BytesIO(txt)
Пример #17
0
    async def storage_menu(self):
        """Manage storage"""
        enabled = self.connection.isCardInserted()
        buttons = [
            # id, text, enabled, color
            (None, "Smartcard storage"),
            (0, "Save key to the card", enabled),
            (1, "Load key from the card", enabled and self.is_key_saved),
            (2, "Delete key from the card", enabled and self.is_key_saved),
            (3, "Use a different card", enabled),
            (4, lv.SYMBOL.SETTINGS + " Get card info", enabled),
            # (5, lv.SYMBOL.TRASH + " Wipe the card", enabled, 0x951E2D),
        ]

        # we stay in this menu until back is pressed
        while True:
            # check updated status
            buttons[2] = (1, "Load key from the card", enabled
                          and self.is_key_saved)
            buttons[3] = (2, "Delete key from the card", enabled
                          and self.is_key_saved)
            note = "Card fingerprint: %s" % self.hexid
            # wait for menu selection
            menuitem = await self.show(
                Menu(buttons, note=note, last=(255, None)))
            # process the menu button:
            # back button
            if menuitem == 255:
                return
            elif menuitem == 0:
                await self.save_mnemonic()
                await self.show(
                    Alert(
                        "Success!",
                        "Your key is stored on the smartcard now.",
                        button_text="OK",
                    ))
            elif menuitem == 1:
                await self.load_mnemonic()
                await self.show(
                    Alert("Success!", "Your key is loaded.", button_text="OK"))
            elif menuitem == 2:
                if await self.show(
                        Prompt("Are you sure?",
                               "\n\nDelete the key from the card?")):
                    await self.delete_mnemonic()
                    await self.show(
                        Alert(
                            "Success!",
                            "Your key is deleted from the smartcard.",
                            button_text="OK",
                        ))
            elif menuitem == 3:
                if await self.show(
                        Prompt(
                            "Switching the smartcard",
                            "To use a different smartcard you need "
                            "to provide a PIN for current one first!\n\n"
                            "Continue?")):
                    self.lock()
                    await self.unlock()
                    self.lock()
                    self.applet.close_secure_channel()
                    self._userkey = None
                    await self.show(
                        Alert("Please swap the card",
                              "Now you can insert another card and set it up.",
                              button_text="Continue"))
                    await self.check_card(check_pin=True)
                    await self.unlock()
            elif menuitem == 4:
                await self.show_card_info()
            else:
                raise KeyStoreError("Invalid menu")