
Airgapped hardware wallet based on STM32F469-Discovery board using SD card to pass transaction data
Dependencies: mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI
main.cpp@4:73e20d662d73, 2019-07-30 (annotated)
- Committer:
- stepansnigirev
- Date:
- Tue Jul 30 19:25:52 2019 +0000
- Revision:
- 4:73e20d662d73
- Parent:
- 2:120e0ca2f3a7
comments
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
stepansnigirev | 0:f43431023689 | 1 | #include "mbed.h" |
stepansnigirev | 0:f43431023689 | 2 | #include "helpers.h" |
stepansnigirev | 0:f43431023689 | 3 | |
stepansnigirev | 0:f43431023689 | 4 | // bitcoin lib |
stepansnigirev | 4:73e20d662d73 | 5 | #include "Bitcoin.h" // Public, Private, HD keys, scripts, raw transactions and stuff |
stepansnigirev | 4:73e20d662d73 | 6 | #include "PSBT.h" // Partially Signed Bitcoin Transaction format |
stepansnigirev | 0:f43431023689 | 7 | |
stepansnigirev | 4:73e20d662d73 | 8 | /***************** bitcoin stuff ***************/ |
stepansnigirev | 0:f43431023689 | 9 | |
stepansnigirev | 1:b88ef7630eb3 | 10 | string mnemonic; |
stepansnigirev | 0:f43431023689 | 11 | HDPrivateKey root; // root private key |
stepansnigirev | 0:f43431023689 | 12 | HDPrivateKey account; // account master private key |
stepansnigirev | 0:f43431023689 | 13 | HDPublicKey xpub; // account master public key |
stepansnigirev | 0:f43431023689 | 14 | bool change = false; // internal or external address |
stepansnigirev | 0:f43431023689 | 15 | unsigned int child_index = 0; // current child index |
stepansnigirev | 0:f43431023689 | 16 | |
stepansnigirev | 0:f43431023689 | 17 | PSBT psbt; // psbt transaction we will be signing |
stepansnigirev | 0:f43431023689 | 18 | |
stepansnigirev | 4:73e20d662d73 | 19 | /****************** GUI elements ****************/ |
stepansnigirev | 4:73e20d662d73 | 20 | |
stepansnigirev | 4:73e20d662d73 | 21 | // some global scope GUI objects we will be changing in callbacks |
stepansnigirev | 4:73e20d662d73 | 22 | Label titleLbl; |
stepansnigirev | 4:73e20d662d73 | 23 | Label dataLbl; |
stepansnigirev | 4:73e20d662d73 | 24 | QR qr; |
stepansnigirev | 4:73e20d662d73 | 25 | |
stepansnigirev | 4:73e20d662d73 | 26 | Button btn; |
stepansnigirev | 4:73e20d662d73 | 27 | Button printBtn; |
stepansnigirev | 4:73e20d662d73 | 28 | Label lbl; |
stepansnigirev | 4:73e20d662d73 | 29 | |
stepansnigirev | 4:73e20d662d73 | 30 | /*********** forward declarations **************/ |
stepansnigirev | 4:73e20d662d73 | 31 | |
stepansnigirev | 4:73e20d662d73 | 32 | // handy function to display information to the user |
stepansnigirev | 4:73e20d662d73 | 33 | // with a title, a message and OK button that goes to the menu |
stepansnigirev | 4:73e20d662d73 | 34 | void showMessage(const string title, const string message); |
stepansnigirev | 4:73e20d662d73 | 35 | // signs transaction after user confirmation |
stepansnigirev | 4:73e20d662d73 | 36 | static lv_res_t signConfirmCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 37 | // derives keys from mnemonic and password |
stepansnigirev | 4:73e20d662d73 | 38 | void initKeys(const string mnemonic, const string password = ""); |
stepansnigirev | 4:73e20d662d73 | 39 | // if mnemonic is not present we can generate or recover it |
stepansnigirev | 4:73e20d662d73 | 40 | void showInitScreen(); |
stepansnigirev | 4:73e20d662d73 | 41 | // if mnemonic is there we go directly to the main menu |
stepansnigirev | 4:73e20d662d73 | 42 | void showMenu(); |
stepansnigirev | 4:73e20d662d73 | 43 | // some functions that handle button clicks |
stepansnigirev | 4:73e20d662d73 | 44 | static lv_res_t toMenuCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 45 | static lv_res_t showAddressesCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 46 | static lv_res_t showMnemonicCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 47 | static lv_res_t wipeCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 48 | static lv_res_t enterMnemonicCallback(lv_obj_t * btn); |
stepansnigirev | 4:73e20d662d73 | 49 | // shows address on the screen |
stepansnigirev | 4:73e20d662d73 | 50 | void showAddress(unsigned int child_index, bool change); |
stepansnigirev | 4:73e20d662d73 | 51 | |
stepansnigirev | 4:73e20d662d73 | 52 | /*********** functions to complete **************/ |
stepansnigirev | 4:73e20d662d73 | 53 | |
stepansnigirev | 4:73e20d662d73 | 54 | // generates a new mnemonic |
stepansnigirev | 4:73e20d662d73 | 55 | string generateNewMnemonic(){ |
stepansnigirev | 4:73e20d662d73 | 56 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 57 | // - generate random buffer (16 or 32 bytes) - getRandomBuffer(buf, size) |
stepansnigirev | 4:73e20d662d73 | 58 | // - create a new mnemonic from it - generateMnemonic(buf, size) |
stepansnigirev | 4:73e20d662d73 | 59 | // - save this mnemonic |
stepansnigirev | 4:73e20d662d73 | 60 | // - display it to the user |
stepansnigirev | 4:73e20d662d73 | 61 | |
stepansnigirev | 4:73e20d662d73 | 62 | uint8_t randomBuffer[16]; |
stepansnigirev | 4:73e20d662d73 | 63 | getRandomBuffer(randomBuffer, sizeof(randomBuffer)); |
stepansnigirev | 4:73e20d662d73 | 64 | string mnemonic = generateMnemonic(randomBuffer, sizeof(randomBuffer)); |
stepansnigirev | 4:73e20d662d73 | 65 | return mnemonic; |
stepansnigirev | 4:73e20d662d73 | 66 | } |
stepansnigirev | 4:73e20d662d73 | 67 | |
stepansnigirev | 4:73e20d662d73 | 68 | // checks if entered mnemonic is valid |
stepansnigirev | 4:73e20d662d73 | 69 | static lv_res_t checkMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 70 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 71 | // - check mnemonic |
stepansnigirev | 4:73e20d662d73 | 72 | // - if ok, init keys and show success message |
stepansnigirev | 4:73e20d662d73 | 73 | // - otherwise erase the whole mnemonic |
stepansnigirev | 4:73e20d662d73 | 74 | |
stepansnigirev | 4:73e20d662d73 | 75 | if(checkMnemonic(mnemonic.c_str())){ |
stepansnigirev | 4:73e20d662d73 | 76 | saveMnemonic(mnemonic); |
stepansnigirev | 4:73e20d662d73 | 77 | initKeys(mnemonic); |
stepansnigirev | 4:73e20d662d73 | 78 | showMessage("Mnemonic is ok", "Recovered sucessfully"); |
stepansnigirev | 4:73e20d662d73 | 79 | }else{ |
stepansnigirev | 4:73e20d662d73 | 80 | mnemonic = ""; |
stepansnigirev | 4:73e20d662d73 | 81 | dataLbl.text(mnemonic); |
stepansnigirev | 4:73e20d662d73 | 82 | } |
stepansnigirev | 4:73e20d662d73 | 83 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 84 | } |
stepansnigirev | 4:73e20d662d73 | 85 | |
stepansnigirev | 4:73e20d662d73 | 86 | // generates hd keys from the mnemonic |
stepansnigirev | 4:73e20d662d73 | 87 | void initKeys(const string mnemonic, const string password){ |
stepansnigirev | 4:73e20d662d73 | 88 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 89 | // - derive root key from the mnemonic and empty password |
stepansnigirev | 4:73e20d662d73 | 90 | // - derive account key using m/84'/1'/0'/ derivation path |
stepansnigirev | 4:73e20d662d73 | 91 | // - get account master public key |
stepansnigirev | 4:73e20d662d73 | 92 | |
stepansnigirev | 4:73e20d662d73 | 93 | root.fromMnemonic(mnemonic, password); |
stepansnigirev | 4:73e20d662d73 | 94 | account = root.derive("m/84'/1'/0'/"); |
stepansnigirev | 4:73e20d662d73 | 95 | xpub = account.xpub(); |
stepansnigirev | 4:73e20d662d73 | 96 | } |
stepansnigirev | 4:73e20d662d73 | 97 | |
stepansnigirev | 4:73e20d662d73 | 98 | void showAddress(unsigned int child_index, bool change){ |
stepansnigirev | 4:73e20d662d73 | 99 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 100 | // - derive an address from xpub according to the derivation |
stepansnigirev | 4:73e20d662d73 | 101 | // - set dataLbl text to the address |
stepansnigirev | 4:73e20d662d73 | 102 | // - set qr text to "bitcoin:address" |
stepansnigirev | 4:73e20d662d73 | 103 | // OPTIONAL: |
stepansnigirev | 4:73e20d662d73 | 104 | // - display both bech32 and nested segwit addresses |
stepansnigirev | 4:73e20d662d73 | 105 | |
stepansnigirev | 4:73e20d662d73 | 106 | stringstream title; |
stepansnigirev | 4:73e20d662d73 | 107 | title << "Your "; |
stepansnigirev | 4:73e20d662d73 | 108 | if(change){ |
stepansnigirev | 4:73e20d662d73 | 109 | title << "change "; |
stepansnigirev | 4:73e20d662d73 | 110 | }else{ |
stepansnigirev | 4:73e20d662d73 | 111 | title << "receiving "; |
stepansnigirev | 4:73e20d662d73 | 112 | } |
stepansnigirev | 4:73e20d662d73 | 113 | title << "address #" << child_index << ":"; |
stepansnigirev | 4:73e20d662d73 | 114 | titleLbl.text(title.str()); |
stepansnigirev | 4:73e20d662d73 | 115 | // generate the address here |
stepansnigirev | 4:73e20d662d73 | 116 | string address = xpub.child(change).child(child_index).address(); |
stepansnigirev | 4:73e20d662d73 | 117 | dataLbl.text(address); |
stepansnigirev | 4:73e20d662d73 | 118 | dataLbl.align(ALIGN_CENTER); |
stepansnigirev | 4:73e20d662d73 | 119 | qr.text(string("bitcoin:") + address); |
stepansnigirev | 4:73e20d662d73 | 120 | qr.align(ALIGN_CENTER); |
stepansnigirev | 4:73e20d662d73 | 121 | } |
stepansnigirev | 4:73e20d662d73 | 122 | |
stepansnigirev | 4:73e20d662d73 | 123 | // this will be called when we press "save master key" button |
stepansnigirev | 4:73e20d662d73 | 124 | static lv_res_t saveXpubCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 125 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 126 | // - check if SD card present |
stepansnigirev | 4:73e20d662d73 | 127 | // - save xpub to the "xpub.txt" |
stepansnigirev | 4:73e20d662d73 | 128 | // - check if write was sucessful |
stepansnigirev | 4:73e20d662d73 | 129 | // - show success / fail message |
stepansnigirev | 4:73e20d662d73 | 130 | // OPTIONAL: |
stepansnigirev | 4:73e20d662d73 | 131 | // - use [fingerprint/derivation]xpub format |
stepansnigirev | 4:73e20d662d73 | 132 | // - create bitcoin core descriptor for bitcoin-cli importmulti |
stepansnigirev | 4:73e20d662d73 | 133 | |
stepansnigirev | 4:73e20d662d73 | 134 | if(!SD.detected()){ |
stepansnigirev | 4:73e20d662d73 | 135 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 4:73e20d662d73 | 136 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 137 | } |
stepansnigirev | 4:73e20d662d73 | 138 | int res = SD.save("xpub.txt", xpub.toString()); |
stepansnigirev | 4:73e20d662d73 | 139 | if(res != SD_SUCCESS){ |
stepansnigirev | 4:73e20d662d73 | 140 | showMessage("Error", "Something wrong with SD card"); |
stepansnigirev | 4:73e20d662d73 | 141 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 142 | } |
stepansnigirev | 4:73e20d662d73 | 143 | showMessage("Success","Master public key is saved to the SD card"); |
stepansnigirev | 4:73e20d662d73 | 144 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 145 | } |
stepansnigirev | 4:73e20d662d73 | 146 | |
stepansnigirev | 4:73e20d662d73 | 147 | // displays confirmation screen for the transaction signing |
stepansnigirev | 4:73e20d662d73 | 148 | void showSignRequest(){ |
stepansnigirev | 4:73e20d662d73 | 149 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 150 | // display information of the transaction: |
stepansnigirev | 4:73e20d662d73 | 151 | // - go through all outputs |
stepansnigirev | 4:73e20d662d73 | 152 | // - detect if the address is the change address |
stepansnigirev | 4:73e20d662d73 | 153 | // - if not, show information in the form "address: amount" |
stepansnigirev | 4:73e20d662d73 | 154 | // - if it is change, hide or mark as a change |
stepansnigirev | 4:73e20d662d73 | 155 | // - display the transaction fee |
stepansnigirev | 4:73e20d662d73 | 156 | // OPTIONAL: |
stepansnigirev | 4:73e20d662d73 | 157 | // - verify that pubkey is actually used in the script |
stepansnigirev | 4:73e20d662d73 | 158 | // - do the same for bip49 and check redeem script |
stepansnigirev | 4:73e20d662d73 | 159 | // - check if derivation path is not weird (indexes are reasonable) |
stepansnigirev | 4:73e20d662d73 | 160 | |
stepansnigirev | 4:73e20d662d73 | 161 | stringstream ss; |
stepansnigirev | 4:73e20d662d73 | 162 | ss << "Sending:\n\n"; |
stepansnigirev | 4:73e20d662d73 | 163 | for(unsigned int i=0; i<psbt.tx.outputsNumber; i++){ |
stepansnigirev | 4:73e20d662d73 | 164 | bool mine = psbt.isMine(i, account.xpub()); |
stepansnigirev | 4:73e20d662d73 | 165 | ss << psbt.tx.txOuts[i].address(&Testnet); |
stepansnigirev | 4:73e20d662d73 | 166 | if(mine){ |
stepansnigirev | 4:73e20d662d73 | 167 | ss << " (change)"; |
stepansnigirev | 4:73e20d662d73 | 168 | } |
stepansnigirev | 4:73e20d662d73 | 169 | ss << ": " << (float(psbt.tx.txOuts[i].amount)/1e5) << " mBTC\n\n"; |
stepansnigirev | 4:73e20d662d73 | 170 | } |
stepansnigirev | 4:73e20d662d73 | 171 | ss << "Fee: " << (float(psbt.fee())/1e5) << " mBTC"; |
stepansnigirev | 4:73e20d662d73 | 172 | |
stepansnigirev | 4:73e20d662d73 | 173 | gui.clear(); |
stepansnigirev | 4:73e20d662d73 | 174 | titleLbl = Label("Sign transaction?"); |
stepansnigirev | 4:73e20d662d73 | 175 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 4:73e20d662d73 | 176 | titleLbl.position(0, 40); |
stepansnigirev | 4:73e20d662d73 | 177 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 4:73e20d662d73 | 178 | |
stepansnigirev | 4:73e20d662d73 | 179 | dataLbl = Label(ss.str()); |
stepansnigirev | 4:73e20d662d73 | 180 | dataLbl.size(gui.width()-100, 100); |
stepansnigirev | 4:73e20d662d73 | 181 | dataLbl.position(50, 300); |
stepansnigirev | 4:73e20d662d73 | 182 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 4:73e20d662d73 | 183 | |
stepansnigirev | 4:73e20d662d73 | 184 | Button btn(toMenuCallback, "Cancel"); |
stepansnigirev | 4:73e20d662d73 | 185 | btn.size(gui.width()/2-45, 80); |
stepansnigirev | 4:73e20d662d73 | 186 | btn.position(30, gui.height()-100); |
stepansnigirev | 4:73e20d662d73 | 187 | |
stepansnigirev | 4:73e20d662d73 | 188 | Button btn2(signConfirmCallback, "Confirm"); |
stepansnigirev | 4:73e20d662d73 | 189 | btn2.size(gui.width()/2-45, 80); |
stepansnigirev | 4:73e20d662d73 | 190 | btn2.position(gui.width()/2+30, gui.height()-100); |
stepansnigirev | 4:73e20d662d73 | 191 | } |
stepansnigirev | 4:73e20d662d73 | 192 | |
stepansnigirev | 4:73e20d662d73 | 193 | // reads unsigned transaction from SD card |
stepansnigirev | 4:73e20d662d73 | 194 | static lv_res_t signPSBTCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 195 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 196 | // - check if SD card is there |
stepansnigirev | 4:73e20d662d73 | 197 | // - read data from "unsigned.psbt" |
stepansnigirev | 4:73e20d662d73 | 198 | // - convert it from base64 to raw bytes |
stepansnigirev | 4:73e20d662d73 | 199 | // - parse psbt transaction |
stepansnigirev | 4:73e20d662d73 | 200 | // - call showSignRequest() |
stepansnigirev | 4:73e20d662d73 | 201 | // OPTIONAL |
stepansnigirev | 4:73e20d662d73 | 202 | // - also show signed transaction as a QR code |
stepansnigirev | 4:73e20d662d73 | 203 | |
stepansnigirev | 4:73e20d662d73 | 204 | if(!SD.detected()){ |
stepansnigirev | 4:73e20d662d73 | 205 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 4:73e20d662d73 | 206 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 207 | } |
stepansnigirev | 4:73e20d662d73 | 208 | string s = SD.read("unsigned.psbt"); |
stepansnigirev | 4:73e20d662d73 | 209 | if(s.length() == 0){ |
stepansnigirev | 4:73e20d662d73 | 210 | showMessage("Fail", "Can't read unsigned.psbt file"); |
stepansnigirev | 4:73e20d662d73 | 211 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 212 | } |
stepansnigirev | 4:73e20d662d73 | 213 | uint8_t * raw = new uint8_t[s.length()]; |
stepansnigirev | 4:73e20d662d73 | 214 | unsigned int len = fromBase64(s.c_str(), s.length(), raw, s.length()); |
stepansnigirev | 4:73e20d662d73 | 215 | if(len == 0){ |
stepansnigirev | 4:73e20d662d73 | 216 | delete [] raw; |
stepansnigirev | 4:73e20d662d73 | 217 | showMessage("Fail", "Can't convert from base64"); |
stepansnigirev | 4:73e20d662d73 | 218 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 219 | } |
stepansnigirev | 4:73e20d662d73 | 220 | psbt.reset(); |
stepansnigirev | 4:73e20d662d73 | 221 | len = psbt.parse(raw, len); |
stepansnigirev | 4:73e20d662d73 | 222 | delete [] raw; |
stepansnigirev | 4:73e20d662d73 | 223 | if(len == 0){ |
stepansnigirev | 4:73e20d662d73 | 224 | showMessage("Fail", "Can't parse PSBT"); |
stepansnigirev | 4:73e20d662d73 | 225 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 226 | } |
stepansnigirev | 4:73e20d662d73 | 227 | showSignRequest(); |
stepansnigirev | 4:73e20d662d73 | 228 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 229 | } |
stepansnigirev | 4:73e20d662d73 | 230 | |
stepansnigirev | 4:73e20d662d73 | 231 | // signs transaction |
stepansnigirev | 4:73e20d662d73 | 232 | static lv_res_t signConfirmCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 233 | // TODO: |
stepansnigirev | 4:73e20d662d73 | 234 | // - check if SD card is still there |
stepansnigirev | 4:73e20d662d73 | 235 | // - serialize the psbt to byte array |
stepansnigirev | 4:73e20d662d73 | 236 | // - convert to base64 |
stepansnigirev | 4:73e20d662d73 | 237 | // - save to "signed.psbt" |
stepansnigirev | 4:73e20d662d73 | 238 | // - show success message |
stepansnigirev | 4:73e20d662d73 | 239 | |
stepansnigirev | 4:73e20d662d73 | 240 | if(!SD.detected()){ |
stepansnigirev | 4:73e20d662d73 | 241 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 4:73e20d662d73 | 242 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 243 | } |
stepansnigirev | 4:73e20d662d73 | 244 | psbt.sign(account); |
stepansnigirev | 4:73e20d662d73 | 245 | |
stepansnigirev | 4:73e20d662d73 | 246 | uint8_t * raw = (uint8_t *)calloc(psbt.length(), sizeof(uint8_t)); |
stepansnigirev | 4:73e20d662d73 | 247 | size_t len = psbt.serialize(raw, psbt.length()); |
stepansnigirev | 4:73e20d662d73 | 248 | psbt = PSBT(); |
stepansnigirev | 4:73e20d662d73 | 249 | string b64 = toBase64(raw, len); |
stepansnigirev | 4:73e20d662d73 | 250 | |
stepansnigirev | 4:73e20d662d73 | 251 | int res = SD.save("signed.psbt", b64.c_str()); |
stepansnigirev | 4:73e20d662d73 | 252 | if(res != SD_SUCCESS){ |
stepansnigirev | 4:73e20d662d73 | 253 | showMessage("Error", "Something wrong with SD card"); |
stepansnigirev | 4:73e20d662d73 | 254 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 255 | } |
stepansnigirev | 4:73e20d662d73 | 256 | showMessage("Success", "Signed transaction saved to the SD card"); |
stepansnigirev | 4:73e20d662d73 | 257 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 258 | } |
stepansnigirev | 4:73e20d662d73 | 259 | |
stepansnigirev | 4:73e20d662d73 | 260 | /***************** GUI functions ***************/ |
stepansnigirev | 4:73e20d662d73 | 261 | |
stepansnigirev | 4:73e20d662d73 | 262 | static lv_res_t newMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 263 | string mnemonic = generateNewMnemonic(); |
stepansnigirev | 4:73e20d662d73 | 264 | saveMnemonic(mnemonic); |
stepansnigirev | 4:73e20d662d73 | 265 | showMessage("Write down your recovery phrase:", mnemonic); |
stepansnigirev | 4:73e20d662d73 | 266 | initKeys(mnemonic); |
stepansnigirev | 4:73e20d662d73 | 267 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 268 | } |
stepansnigirev | 4:73e20d662d73 | 269 | |
stepansnigirev | 4:73e20d662d73 | 270 | // show next address |
stepansnigirev | 4:73e20d662d73 | 271 | static lv_res_t nextCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 272 | child_index++; |
stepansnigirev | 4:73e20d662d73 | 273 | showAddress(child_index, change); |
stepansnigirev | 4:73e20d662d73 | 274 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 275 | } |
stepansnigirev | 4:73e20d662d73 | 276 | |
stepansnigirev | 4:73e20d662d73 | 277 | // show previous address |
stepansnigirev | 4:73e20d662d73 | 278 | static lv_res_t prevCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 279 | if(child_index > 0){ |
stepansnigirev | 4:73e20d662d73 | 280 | child_index--; |
stepansnigirev | 4:73e20d662d73 | 281 | showAddress(child_index, change); |
stepansnigirev | 4:73e20d662d73 | 282 | } |
stepansnigirev | 4:73e20d662d73 | 283 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 284 | } |
stepansnigirev | 4:73e20d662d73 | 285 | |
stepansnigirev | 4:73e20d662d73 | 286 | // switch to change addresses |
stepansnigirev | 4:73e20d662d73 | 287 | static lv_res_t changeCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 288 | change = !change; |
stepansnigirev | 4:73e20d662d73 | 289 | showAddress(child_index, change); |
stepansnigirev | 4:73e20d662d73 | 290 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 291 | } |
stepansnigirev | 4:73e20d662d73 | 292 | |
stepansnigirev | 4:73e20d662d73 | 293 | // show master public key |
stepansnigirev | 4:73e20d662d73 | 294 | static lv_res_t xpubCallback(lv_obj_t * btn){ |
stepansnigirev | 4:73e20d662d73 | 295 | titleLbl.text("Your master public key"); |
stepansnigirev | 4:73e20d662d73 | 296 | qr.text(xpub.toString()); |
stepansnigirev | 4:73e20d662d73 | 297 | dataLbl.text(xpub.toString()); |
stepansnigirev | 4:73e20d662d73 | 298 | qr.align(ALIGN_CENTER); |
stepansnigirev | 4:73e20d662d73 | 299 | dataLbl.align(ALIGN_CENTER); |
stepansnigirev | 4:73e20d662d73 | 300 | return LV_RES_OK; |
stepansnigirev | 4:73e20d662d73 | 301 | } |
stepansnigirev | 4:73e20d662d73 | 302 | |
stepansnigirev | 0:f43431023689 | 303 | /******************* Main part *****************/ |
stepansnigirev | 0:f43431023689 | 304 | |
stepansnigirev | 0:f43431023689 | 305 | int main(){ |
stepansnigirev | 0:f43431023689 | 306 | init(); |
stepansnigirev | 0:f43431023689 | 307 | |
stepansnigirev | 0:f43431023689 | 308 | string mnemonic = loadMnemonic(); |
stepansnigirev | 0:f43431023689 | 309 | if(mnemonic.length() == 0){ |
stepansnigirev | 0:f43431023689 | 310 | showInitScreen(); |
stepansnigirev | 0:f43431023689 | 311 | }else{ |
stepansnigirev | 0:f43431023689 | 312 | initKeys(mnemonic); |
stepansnigirev | 0:f43431023689 | 313 | showMenu(); |
stepansnigirev | 0:f43431023689 | 314 | } |
stepansnigirev | 0:f43431023689 | 315 | |
stepansnigirev | 0:f43431023689 | 316 | while(1){ |
stepansnigirev | 0:f43431023689 | 317 | gui.update(); |
stepansnigirev | 0:f43431023689 | 318 | } |
stepansnigirev | 0:f43431023689 | 319 | } |
stepansnigirev | 0:f43431023689 | 320 | |
stepansnigirev | 0:f43431023689 | 321 | /****************** GUI stuff *****************/ |
stepansnigirev | 4:73e20d662d73 | 322 | |
stepansnigirev | 0:f43431023689 | 323 | void showInitScreen(){ |
stepansnigirev | 0:f43431023689 | 324 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 325 | titleLbl = Label("Let's set it up!"); |
stepansnigirev | 0:f43431023689 | 326 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 327 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 328 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 329 | |
stepansnigirev | 0:f43431023689 | 330 | Button btn(newMnemonicCallback, "Generate new mnemonic"); |
stepansnigirev | 0:f43431023689 | 331 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 332 | btn.position(0, 200); |
stepansnigirev | 0:f43431023689 | 333 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 334 | |
stepansnigirev | 0:f43431023689 | 335 | Button btn2(enterMnemonicCallback, "Enter existing mnemonic"); |
stepansnigirev | 0:f43431023689 | 336 | btn2.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 337 | btn2.position(0, 300); |
stepansnigirev | 0:f43431023689 | 338 | btn2.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 339 | } |
stepansnigirev | 0:f43431023689 | 340 | |
stepansnigirev | 0:f43431023689 | 341 | void showMenu(){ |
stepansnigirev | 0:f43431023689 | 342 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 343 | titleLbl = Label("What do you want to do?"); |
stepansnigirev | 0:f43431023689 | 344 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 345 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 346 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 347 | |
stepansnigirev | 0:f43431023689 | 348 | Button btn(showAddressesCallback, "Show addresses"); |
stepansnigirev | 0:f43431023689 | 349 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 350 | btn.position(0, 100); |
stepansnigirev | 0:f43431023689 | 351 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 352 | |
stepansnigirev | 0:f43431023689 | 353 | Button btn2(saveXpubCallback, "Export xpub"); |
stepansnigirev | 0:f43431023689 | 354 | btn2.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 355 | btn2.position(0, 300); |
stepansnigirev | 0:f43431023689 | 356 | btn2.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 357 | |
stepansnigirev | 0:f43431023689 | 358 | Button btn3(signPSBTCallback, "Sign PSBT transaction"); |
stepansnigirev | 0:f43431023689 | 359 | btn3.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 360 | btn3.position(0, 400); |
stepansnigirev | 0:f43431023689 | 361 | btn3.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 362 | |
stepansnigirev | 0:f43431023689 | 363 | Button btn4(wipeCallback, "Wipe device"); |
stepansnigirev | 0:f43431023689 | 364 | btn4.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 365 | btn4.position(0, 600); |
stepansnigirev | 0:f43431023689 | 366 | btn4.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 367 | |
stepansnigirev | 0:f43431023689 | 368 | Button btn5(showMnemonicCallback, "Show mnemonic"); |
stepansnigirev | 0:f43431023689 | 369 | btn5.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 370 | btn5.position(0, 700); |
stepansnigirev | 0:f43431023689 | 371 | btn5.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 372 | } |
stepansnigirev | 0:f43431023689 | 373 | |
stepansnigirev | 0:f43431023689 | 374 | void showMessage(const string title, const string message){ |
stepansnigirev | 0:f43431023689 | 375 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 376 | titleLbl = Label(title); |
stepansnigirev | 0:f43431023689 | 377 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 378 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 379 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 380 | |
stepansnigirev | 0:f43431023689 | 381 | dataLbl = Label(message); |
stepansnigirev | 0:f43431023689 | 382 | dataLbl.size(gui.width()-100, 100); |
stepansnigirev | 0:f43431023689 | 383 | dataLbl.position(50, 300); |
stepansnigirev | 0:f43431023689 | 384 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 385 | |
stepansnigirev | 0:f43431023689 | 386 | Button btn(toMenuCallback, "OK"); |
stepansnigirev | 0:f43431023689 | 387 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 388 | btn.position(0, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 389 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 390 | } |
stepansnigirev | 0:f43431023689 | 391 | |
stepansnigirev | 0:f43431023689 | 392 | void showAddressScreen(){ |
stepansnigirev | 0:f43431023689 | 393 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 394 | titleLbl = Label("Your address"); |
stepansnigirev | 0:f43431023689 | 395 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 396 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 397 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 398 | |
stepansnigirev | 0:f43431023689 | 399 | dataLbl = Label(" "); |
stepansnigirev | 0:f43431023689 | 400 | dataLbl.size(gui.width()-100, 100); // full width |
stepansnigirev | 0:f43431023689 | 401 | dataLbl.position(50, gui.height()-300); |
stepansnigirev | 0:f43431023689 | 402 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 403 | |
stepansnigirev | 0:f43431023689 | 404 | qr = QR(" "); |
stepansnigirev | 0:f43431023689 | 405 | qr.size(gui.width()-100); |
stepansnigirev | 0:f43431023689 | 406 | qr.position(0, 100); |
stepansnigirev | 0:f43431023689 | 407 | qr.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 408 | |
stepansnigirev | 0:f43431023689 | 409 | Button btn(nextCallback, "Next address"); |
stepansnigirev | 0:f43431023689 | 410 | btn.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 411 | btn.position(gui.width()*2/3 + 10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 412 | |
stepansnigirev | 0:f43431023689 | 413 | Button btn2(prevCallback, "Previous address"); |
stepansnigirev | 0:f43431023689 | 414 | btn2.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 415 | btn2.position(10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 416 | |
stepansnigirev | 0:f43431023689 | 417 | Button btn3(changeCallback, "Toggle\nchange"); |
stepansnigirev | 0:f43431023689 | 418 | btn3.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 419 | btn3.position(gui.width()/3 + 10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 420 | |
stepansnigirev | 0:f43431023689 | 421 | Button btn4(xpubCallback, "Show xpub"); |
stepansnigirev | 0:f43431023689 | 422 | btn4.size(gui.width()/2-20, 80); |
stepansnigirev | 0:f43431023689 | 423 | btn4.position(gui.width()/2+10, gui.height()-200); |
stepansnigirev | 0:f43431023689 | 424 | |
stepansnigirev | 0:f43431023689 | 425 | Button btn5(toMenuCallback, "Menu"); |
stepansnigirev | 0:f43431023689 | 426 | btn5.size(gui.width()/2-20, 80); |
stepansnigirev | 0:f43431023689 | 427 | btn5.position(10, gui.height()-200); |
stepansnigirev | 0:f43431023689 | 428 | } |
stepansnigirev | 0:f43431023689 | 429 | |
stepansnigirev | 0:f43431023689 | 430 | static lv_res_t toMenuCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 431 | showMenu(); |
stepansnigirev | 0:f43431023689 | 432 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 433 | } |
stepansnigirev | 0:f43431023689 | 434 | |
stepansnigirev | 0:f43431023689 | 435 | static lv_res_t showAddressesCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 436 | showAddressScreen(); |
stepansnigirev | 0:f43431023689 | 437 | showAddress(child_index, change); |
stepansnigirev | 0:f43431023689 | 438 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 439 | } |
stepansnigirev | 0:f43431023689 | 440 | |
stepansnigirev | 0:f43431023689 | 441 | static lv_res_t wipeCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 442 | wipe(); |
stepansnigirev | 0:f43431023689 | 443 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 444 | } |
stepansnigirev | 0:f43431023689 | 445 | |
stepansnigirev | 0:f43431023689 | 446 | /*************** mnemonic stuff ***************/ |
stepansnigirev | 0:f43431023689 | 447 | |
stepansnigirev | 0:f43431023689 | 448 | static lv_res_t showMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 449 | string mnemonic = loadMnemonic(); |
stepansnigirev | 0:f43431023689 | 450 | showMessage("Here is your recovery phrase:", mnemonic); |
stepansnigirev | 0:f43431023689 | 451 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 452 | } |
stepansnigirev | 0:f43431023689 | 453 | |
stepansnigirev | 1:b88ef7630eb3 | 454 | static const char * keys[] = {"q","w","e","r","t","y","u","i","o","p","\n", |
stepansnigirev | 1:b88ef7630eb3 | 455 | "a","s","d","f","g","h","j","k","l","\n", |
stepansnigirev | 1:b88ef7630eb3 | 456 | " ","z","x","c","v","b","n","m","<",""}; |
stepansnigirev | 1:b88ef7630eb3 | 457 | |
stepansnigirev | 1:b88ef7630eb3 | 458 | static lv_res_t typeCallback(lv_obj_t * btn, const char * key){ |
stepansnigirev | 1:b88ef7630eb3 | 459 | if(key[0] == '<'){ |
stepansnigirev | 1:b88ef7630eb3 | 460 | if(mnemonic.length() > 0){ |
stepansnigirev | 1:b88ef7630eb3 | 461 | mnemonic = mnemonic.substr(0,mnemonic.length()-1); |
stepansnigirev | 1:b88ef7630eb3 | 462 | } |
stepansnigirev | 1:b88ef7630eb3 | 463 | }else{ |
stepansnigirev | 1:b88ef7630eb3 | 464 | mnemonic += key; |
stepansnigirev | 1:b88ef7630eb3 | 465 | } |
stepansnigirev | 1:b88ef7630eb3 | 466 | dataLbl.text(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 467 | return LV_RES_OK; |
stepansnigirev | 1:b88ef7630eb3 | 468 | } |
stepansnigirev | 1:b88ef7630eb3 | 469 | |
stepansnigirev | 0:f43431023689 | 470 | static lv_res_t enterMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 1:b88ef7630eb3 | 471 | gui.clear(); |
stepansnigirev | 1:b88ef7630eb3 | 472 | mnemonic = ""; |
stepansnigirev | 1:b88ef7630eb3 | 473 | titleLbl = Label("Enter your mnemonic"); |
stepansnigirev | 1:b88ef7630eb3 | 474 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 1:b88ef7630eb3 | 475 | titleLbl.position(0, 40); |
stepansnigirev | 1:b88ef7630eb3 | 476 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 1:b88ef7630eb3 | 477 | |
stepansnigirev | 1:b88ef7630eb3 | 478 | dataLbl = Label(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 479 | dataLbl.size(gui.width()-100, 50); |
stepansnigirev | 1:b88ef7630eb3 | 480 | dataLbl.position(50, 200); |
stepansnigirev | 1:b88ef7630eb3 | 481 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 1:b88ef7630eb3 | 482 | |
stepansnigirev | 1:b88ef7630eb3 | 483 | Keyboard kb(typeCallback, keys); |
stepansnigirev | 1:b88ef7630eb3 | 484 | kb.size(gui.width(), gui.height()/3); |
stepansnigirev | 1:b88ef7630eb3 | 485 | kb.position(0, gui.height()*2/3-150); |
stepansnigirev | 1:b88ef7630eb3 | 486 | |
stepansnigirev | 1:b88ef7630eb3 | 487 | Button btnx(checkMnemonicCallback, "Continue"); |
stepansnigirev | 1:b88ef7630eb3 | 488 | btnx.size(gui.width()-100, 80); |
stepansnigirev | 1:b88ef7630eb3 | 489 | btnx.position(50, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 490 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 491 | } |