|
| 1 | +import lvgl as lv |
| 2 | +from binascii import hexlify |
| 3 | +from io import BytesIO |
| 4 | + |
| 5 | +from app import BaseApp, AppError |
| 6 | +from embit import bip85 |
| 7 | +from gui.common import add_button, add_button_pair, align_button_pair |
| 8 | +from gui.decorators import on_release |
| 9 | +from gui.screens import Menu, NumericScreen, QRAlert, Alert |
| 10 | +from gui.screens.mnemonic import MnemonicScreen |
| 11 | +from helpers import SDCardFile |
| 12 | + |
| 13 | +class QRWithSD(QRAlert): |
| 14 | + SAVE = 1 |
| 15 | + def __init__(self, *args, **kwargs): |
| 16 | + super().__init__(*args, **kwargs) |
| 17 | + # add save button |
| 18 | + btn = add_button("Save to SD card", on_release(self.save), scr=self) |
| 19 | + btn.align(self.close_button, lv.ALIGN.OUT_TOP_MID, 0, -20) |
| 20 | + |
| 21 | + def save(self): |
| 22 | + self.set_value(self.SAVE) |
| 23 | + |
| 24 | +class Bip85MnemonicScreen(MnemonicScreen): |
| 25 | + QR = 1 |
| 26 | + SD = 2 |
| 27 | + LOAD = 3 |
| 28 | + def __init__(self, *args, **kwargs): |
| 29 | + super().__init__(*args, **kwargs) |
| 30 | + self.load_btn = add_button( |
| 31 | + text="Use now (load to device)", |
| 32 | + scr=self, |
| 33 | + callback=on_release(self.load) |
| 34 | + ) |
| 35 | + self.load_btn.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) |
| 36 | + self.show_qr_btn, self.save_sd_btn = add_button_pair( |
| 37 | + text1="Show QR code", |
| 38 | + callback1=on_release(self.show_qr), |
| 39 | + text2="Save to SD card", |
| 40 | + callback2=on_release(self.save_sd), |
| 41 | + scr=self, |
| 42 | + ) |
| 43 | + self.show_qr_btn.align(self.load_btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) |
| 44 | + self.save_sd_btn.align(self.load_btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) |
| 45 | + align_button_pair(self.show_qr_btn, self.save_sd_btn) |
| 46 | + |
| 47 | + def show_qr(self): |
| 48 | + self.set_value(self.QR) |
| 49 | + |
| 50 | + def save_sd(self): |
| 51 | + self.set_value(self.SD) |
| 52 | + |
| 53 | + def load(self): |
| 54 | + self.set_value(self.LOAD) |
| 55 | + |
| 56 | +class App(BaseApp): |
| 57 | + """ |
| 58 | + WalletManager class manages your wallets. |
| 59 | + It stores public information about the wallets |
| 60 | + in the folder and signs it with keystore's id key |
| 61 | + """ |
| 62 | + |
| 63 | + button = "Deterministic derivation (BIP-85)" |
| 64 | + name = "bip85" |
| 65 | + |
| 66 | + async def menu(self, show_screen): |
| 67 | + buttons = [ |
| 68 | + (None, "Mnemonics"), |
| 69 | + (0, "12-word mnemonic"), |
| 70 | + (1, "18-word mnemonic"), |
| 71 | + (2, "24-word mnemonic"), |
| 72 | + (None, "Other stuff"), |
| 73 | + (3, "WIF key (single private key)"), |
| 74 | + (4, "Master private key (xprv)"), |
| 75 | + (5, "Raw entropy (16-64 bytes)"), |
| 76 | + ] |
| 77 | + |
| 78 | + # wait for menu selection |
| 79 | + menuitem = await show_screen( |
| 80 | + Menu( |
| 81 | + buttons, |
| 82 | + last=(255, None), |
| 83 | + title="What do you want to derive?", |
| 84 | + note="", |
| 85 | + ) |
| 86 | + ) |
| 87 | + |
| 88 | + # process the menu button: |
| 89 | + # back button |
| 90 | + if menuitem == 255: |
| 91 | + return False |
| 92 | + # get derivation index |
| 93 | + index = await show_screen( |
| 94 | + NumericScreen(title="Enter derivation index", note="Default: 0") |
| 95 | + ) |
| 96 | + if index is None: |
| 97 | + return True # stay in the menu |
| 98 | + if index == "": |
| 99 | + index = 0 |
| 100 | + index = int(index) |
| 101 | + note = "index: %d" % index |
| 102 | + |
| 103 | + fgp = hexlify(self.keystore.fingerprint).decode() |
| 104 | + # mnemonic menu items |
| 105 | + if menuitem >= 0 and menuitem <=2: |
| 106 | + num_words = 12+6*menuitem |
| 107 | + mnemonic = bip85.derive_mnemonic( |
| 108 | + self.keystore.root, num_words=num_words, index=index |
| 109 | + ) |
| 110 | + title = "Derived %d-word mnemonic" % num_words |
| 111 | + action = await show_screen( |
| 112 | + Bip85MnemonicScreen(mnemonic=mnemonic, title=title, note=note) |
| 113 | + ) |
| 114 | + if action == Bip85MnemonicScreen.QR: |
| 115 | + await show_screen( |
| 116 | + QRAlert(title=title, message=mnemonic, note=note) |
| 117 | + ) |
| 118 | + elif action == Bip85MnemonicScreen.SD: |
| 119 | + fname = "bip85-%s-mnemonic-%d-%d.txt" % ( |
| 120 | + fgp, num_words, index |
| 121 | + ) |
| 122 | + with SDCardFile(fname, "w") as f: |
| 123 | + f.write(mnemonic) |
| 124 | + await show_screen( |
| 125 | + Alert( |
| 126 | + title="Success", |
| 127 | + message="Mnemonic is saved as\n\n%s" % fname, |
| 128 | + button_text="Close", |
| 129 | + ) |
| 130 | + ) |
| 131 | + elif action == Bip85MnemonicScreen.LOAD: |
| 132 | + await self.communicate( |
| 133 | + BytesIO(b"set_mnemonic "+mnemonic.encode()), app="", |
| 134 | + ) |
| 135 | + return False |
| 136 | + return True |
| 137 | + # other stuff |
| 138 | + if menuitem == 3: |
| 139 | + title = "Derived private key" |
| 140 | + res = bip85.derive_wif(self.keystore.root, index) |
| 141 | + file_suffix = "wif" |
| 142 | + elif menuitem == 4: |
| 143 | + title = "Derived master private key" |
| 144 | + res = bip85.derive_xprv(self.keystore.root, index) |
| 145 | + file_suffix = "xprv" |
| 146 | + elif menuitem == 5: |
| 147 | + num_bytes = await show_screen( |
| 148 | + NumericScreen( |
| 149 | + title="Number of bytes to generate", |
| 150 | + note="16 <= N <= 64. Default: 32", |
| 151 | + ) |
| 152 | + ) |
| 153 | + if num_bytes is None: |
| 154 | + return True |
| 155 | + if num_bytes == "": |
| 156 | + num_bytes = 32 |
| 157 | + num_bytes = int(num_bytes) |
| 158 | + if num_bytes < 16 or num_bytes > 64: |
| 159 | + raise AppError("Only 16-64 bytes can be generated with BIP-85") |
| 160 | + title = "Derived %d-byte entropy" % num_bytes |
| 161 | + raw = bip85.derive_hex(self.keystore.root, num_bytes, index) |
| 162 | + res = hexlify(raw).decode() |
| 163 | + file_suffix = "hex-%d" % num_bytes |
| 164 | + else: |
| 165 | + raise NotImplementedError("Not implemented") |
| 166 | + res = str(res) |
| 167 | + action = await show_screen( |
| 168 | + QRWithSD(title=title, message=res, note=note) |
| 169 | + ) |
| 170 | + if action == QRWithSD.SAVE: |
| 171 | + fname = "bip85-%s-%s-%d.txt" % (fgp, file_suffix, index) |
| 172 | + with SDCardFile(fname, "w") as f: |
| 173 | + f.write(res) |
| 174 | + await show_screen( |
| 175 | + Alert( |
| 176 | + title="Success", |
| 177 | + message="Data is saved as\n\n%s" % fname, |
| 178 | + button_text="Close", |
| 179 | + ) |
| 180 | + ) |
| 181 | + return True |
| 182 | + |
0 commit comments