
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@1:b88ef7630eb3, 2019-07-20 (annotated)
- Committer:
- stepansnigirev
- Date:
- Sat Jul 20 16:18:54 2019 +0000
- Revision:
- 1:b88ef7630eb3
- Parent:
- 0:f43431023689
- Child:
- 2:120e0ca2f3a7
recover mnemonic
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 | 0:f43431023689 | 5 | #include "Bitcoin.h" |
stepansnigirev | 0:f43431023689 | 6 | #include "PSBT.h" |
stepansnigirev | 0:f43431023689 | 7 | |
stepansnigirev | 0:f43431023689 | 8 | /*********** forward declarations **************/ |
stepansnigirev | 0:f43431023689 | 9 | |
stepansnigirev | 0:f43431023689 | 10 | // generates a new mnemonic |
stepansnigirev | 0:f43431023689 | 11 | string generateNewMnemonic(); |
stepansnigirev | 0:f43431023689 | 12 | |
stepansnigirev | 0:f43431023689 | 13 | // generates hd keys from the mnemonic |
stepansnigirev | 0:f43431023689 | 14 | void initKeys(const string mnemonic, const string password = ""); |
stepansnigirev | 0:f43431023689 | 15 | |
stepansnigirev | 0:f43431023689 | 16 | /****************** GUI classes ****************/ |
stepansnigirev | 0:f43431023689 | 17 | |
stepansnigirev | 0:f43431023689 | 18 | Label titleLbl; |
stepansnigirev | 0:f43431023689 | 19 | Label dataLbl; |
stepansnigirev | 0:f43431023689 | 20 | QR qr; |
stepansnigirev | 0:f43431023689 | 21 | |
stepansnigirev | 0:f43431023689 | 22 | Button btn; |
stepansnigirev | 0:f43431023689 | 23 | Button printBtn; |
stepansnigirev | 0:f43431023689 | 24 | Label lbl; |
stepansnigirev | 0:f43431023689 | 25 | |
stepansnigirev | 0:f43431023689 | 26 | /***************** GUI functions ***************/ |
stepansnigirev | 0:f43431023689 | 27 | |
stepansnigirev | 0:f43431023689 | 28 | // if mnemonic is not present we can generate or recover it |
stepansnigirev | 0:f43431023689 | 29 | void showInitScreen(); |
stepansnigirev | 0:f43431023689 | 30 | // if mnemonic is there we go directly to the main menu |
stepansnigirev | 0:f43431023689 | 31 | void showMenu(); |
stepansnigirev | 0:f43431023689 | 32 | // handy function to display information to the user |
stepansnigirev | 0:f43431023689 | 33 | // with a title, a message and OK button that goes to the menu |
stepansnigirev | 0:f43431023689 | 34 | void showMessage(const string title, const string message); |
stepansnigirev | 0:f43431023689 | 35 | static lv_res_t toMenuCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 36 | static lv_res_t showAddressesCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 37 | static lv_res_t saveXpubCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 38 | static lv_res_t signPSBTCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 39 | static lv_res_t showMnemonicCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 40 | static lv_res_t wipeCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 41 | static lv_res_t newMnemonicCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 42 | static lv_res_t enterMnemonicCallback(lv_obj_t * btn); |
stepansnigirev | 0:f43431023689 | 43 | void showAddress(unsigned int child_index, bool change); |
stepansnigirev | 0:f43431023689 | 44 | |
stepansnigirev | 0:f43431023689 | 45 | /***************** bitcoin keys ***************/ |
stepansnigirev | 0:f43431023689 | 46 | |
stepansnigirev | 1:b88ef7630eb3 | 47 | string mnemonic; |
stepansnigirev | 0:f43431023689 | 48 | HDPrivateKey root; // root private key |
stepansnigirev | 0:f43431023689 | 49 | HDPrivateKey account; // account master private key |
stepansnigirev | 0:f43431023689 | 50 | HDPublicKey xpub; // account master public key |
stepansnigirev | 0:f43431023689 | 51 | bool change = false; // internal or external address |
stepansnigirev | 0:f43431023689 | 52 | unsigned int child_index = 0; // current child index |
stepansnigirev | 0:f43431023689 | 53 | |
stepansnigirev | 0:f43431023689 | 54 | PSBT psbt; // psbt transaction we will be signing |
stepansnigirev | 0:f43431023689 | 55 | |
stepansnigirev | 0:f43431023689 | 56 | /******************* Main part *****************/ |
stepansnigirev | 0:f43431023689 | 57 | |
stepansnigirev | 0:f43431023689 | 58 | int main(){ |
stepansnigirev | 0:f43431023689 | 59 | init(); |
stepansnigirev | 0:f43431023689 | 60 | |
stepansnigirev | 0:f43431023689 | 61 | string mnemonic = loadMnemonic(); |
stepansnigirev | 0:f43431023689 | 62 | if(mnemonic.length() == 0){ |
stepansnigirev | 0:f43431023689 | 63 | showInitScreen(); |
stepansnigirev | 0:f43431023689 | 64 | }else{ |
stepansnigirev | 0:f43431023689 | 65 | initKeys(mnemonic); |
stepansnigirev | 0:f43431023689 | 66 | showMenu(); |
stepansnigirev | 0:f43431023689 | 67 | } |
stepansnigirev | 0:f43431023689 | 68 | |
stepansnigirev | 0:f43431023689 | 69 | while(1){ |
stepansnigirev | 0:f43431023689 | 70 | gui.update(); |
stepansnigirev | 0:f43431023689 | 71 | } |
stepansnigirev | 0:f43431023689 | 72 | } |
stepansnigirev | 0:f43431023689 | 73 | |
stepansnigirev | 0:f43431023689 | 74 | /****************** GUI stuff *****************/ |
stepansnigirev | 0:f43431023689 | 75 | void showInitScreen(){ |
stepansnigirev | 0:f43431023689 | 76 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 77 | titleLbl = Label("Let's set it up!"); |
stepansnigirev | 0:f43431023689 | 78 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 79 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 80 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 81 | |
stepansnigirev | 0:f43431023689 | 82 | Button btn(newMnemonicCallback, "Generate new mnemonic"); |
stepansnigirev | 0:f43431023689 | 83 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 84 | btn.position(0, 200); |
stepansnigirev | 0:f43431023689 | 85 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 86 | |
stepansnigirev | 0:f43431023689 | 87 | Button btn2(enterMnemonicCallback, "Enter existing mnemonic"); |
stepansnigirev | 0:f43431023689 | 88 | btn2.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 89 | btn2.position(0, 300); |
stepansnigirev | 0:f43431023689 | 90 | btn2.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 91 | } |
stepansnigirev | 0:f43431023689 | 92 | |
stepansnigirev | 0:f43431023689 | 93 | void showMenu(){ |
stepansnigirev | 0:f43431023689 | 94 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 95 | titleLbl = Label("What do you want to do?"); |
stepansnigirev | 0:f43431023689 | 96 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 97 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 98 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 99 | |
stepansnigirev | 0:f43431023689 | 100 | Button btn(showAddressesCallback, "Show addresses"); |
stepansnigirev | 0:f43431023689 | 101 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 102 | btn.position(0, 100); |
stepansnigirev | 0:f43431023689 | 103 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 104 | |
stepansnigirev | 0:f43431023689 | 105 | Button btn2(saveXpubCallback, "Export xpub"); |
stepansnigirev | 0:f43431023689 | 106 | btn2.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 107 | btn2.position(0, 300); |
stepansnigirev | 0:f43431023689 | 108 | btn2.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 109 | |
stepansnigirev | 0:f43431023689 | 110 | Button btn3(signPSBTCallback, "Sign PSBT transaction"); |
stepansnigirev | 0:f43431023689 | 111 | btn3.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 112 | btn3.position(0, 400); |
stepansnigirev | 0:f43431023689 | 113 | btn3.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 114 | |
stepansnigirev | 0:f43431023689 | 115 | Button btn4(wipeCallback, "Wipe device"); |
stepansnigirev | 0:f43431023689 | 116 | btn4.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 117 | btn4.position(0, 600); |
stepansnigirev | 0:f43431023689 | 118 | btn4.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 119 | |
stepansnigirev | 0:f43431023689 | 120 | Button btn5(showMnemonicCallback, "Show mnemonic"); |
stepansnigirev | 0:f43431023689 | 121 | btn5.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 122 | btn5.position(0, 700); |
stepansnigirev | 0:f43431023689 | 123 | btn5.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 124 | } |
stepansnigirev | 0:f43431023689 | 125 | |
stepansnigirev | 0:f43431023689 | 126 | void showMessage(const string title, const string message){ |
stepansnigirev | 0:f43431023689 | 127 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 128 | titleLbl = Label(title); |
stepansnigirev | 0:f43431023689 | 129 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 130 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 131 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 132 | |
stepansnigirev | 0:f43431023689 | 133 | dataLbl = Label(message); |
stepansnigirev | 0:f43431023689 | 134 | dataLbl.size(gui.width()-100, 100); |
stepansnigirev | 0:f43431023689 | 135 | dataLbl.position(50, 300); |
stepansnigirev | 0:f43431023689 | 136 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 137 | |
stepansnigirev | 0:f43431023689 | 138 | Button btn(toMenuCallback, "OK"); |
stepansnigirev | 0:f43431023689 | 139 | btn.size(gui.width()-100, 80); |
stepansnigirev | 0:f43431023689 | 140 | btn.position(0, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 141 | btn.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 142 | } |
stepansnigirev | 0:f43431023689 | 143 | |
stepansnigirev | 0:f43431023689 | 144 | static lv_res_t nextCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 145 | child_index++; |
stepansnigirev | 0:f43431023689 | 146 | showAddress(child_index, change); |
stepansnigirev | 0:f43431023689 | 147 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 148 | } |
stepansnigirev | 0:f43431023689 | 149 | static lv_res_t prevCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 150 | if(child_index > 0){ |
stepansnigirev | 0:f43431023689 | 151 | child_index--; |
stepansnigirev | 0:f43431023689 | 152 | showAddress(child_index, change); |
stepansnigirev | 0:f43431023689 | 153 | } |
stepansnigirev | 0:f43431023689 | 154 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 155 | } |
stepansnigirev | 0:f43431023689 | 156 | static lv_res_t changeCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 157 | change = !change; |
stepansnigirev | 0:f43431023689 | 158 | showAddress(child_index, change); |
stepansnigirev | 0:f43431023689 | 159 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 160 | } |
stepansnigirev | 0:f43431023689 | 161 | static lv_res_t xpubCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 162 | titleLbl.text("Your master public key"); |
stepansnigirev | 0:f43431023689 | 163 | qr.text(xpub.toString()); |
stepansnigirev | 0:f43431023689 | 164 | dataLbl.text(xpub.toString()); |
stepansnigirev | 0:f43431023689 | 165 | qr.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 166 | dataLbl.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 167 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 168 | } |
stepansnigirev | 0:f43431023689 | 169 | |
stepansnigirev | 0:f43431023689 | 170 | void showAddress(unsigned int child_index, bool change){ |
stepansnigirev | 0:f43431023689 | 171 | stringstream title; |
stepansnigirev | 0:f43431023689 | 172 | title << "Your "; |
stepansnigirev | 0:f43431023689 | 173 | if(change){ |
stepansnigirev | 0:f43431023689 | 174 | title << "change "; |
stepansnigirev | 0:f43431023689 | 175 | }else{ |
stepansnigirev | 0:f43431023689 | 176 | title << "receiving "; |
stepansnigirev | 0:f43431023689 | 177 | } |
stepansnigirev | 0:f43431023689 | 178 | title << "address #" << child_index << ":"; |
stepansnigirev | 0:f43431023689 | 179 | titleLbl.text(title.str()); |
stepansnigirev | 0:f43431023689 | 180 | string address = xpub.child(change).child(child_index).address(); |
stepansnigirev | 0:f43431023689 | 181 | dataLbl.text(address); |
stepansnigirev | 0:f43431023689 | 182 | dataLbl.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 183 | qr.text(string("bitcoin:") + address); |
stepansnigirev | 0:f43431023689 | 184 | qr.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 185 | } |
stepansnigirev | 0:f43431023689 | 186 | |
stepansnigirev | 0:f43431023689 | 187 | void showAddressScreen(){ |
stepansnigirev | 0:f43431023689 | 188 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 189 | titleLbl = Label("Your address"); |
stepansnigirev | 0:f43431023689 | 190 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 191 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 192 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 193 | |
stepansnigirev | 0:f43431023689 | 194 | dataLbl = Label(" "); |
stepansnigirev | 0:f43431023689 | 195 | dataLbl.size(gui.width()-100, 100); // full width |
stepansnigirev | 0:f43431023689 | 196 | dataLbl.position(50, gui.height()-300); |
stepansnigirev | 0:f43431023689 | 197 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 198 | |
stepansnigirev | 0:f43431023689 | 199 | qr = QR(" "); |
stepansnigirev | 0:f43431023689 | 200 | qr.size(gui.width()-100); |
stepansnigirev | 0:f43431023689 | 201 | qr.position(0, 100); |
stepansnigirev | 0:f43431023689 | 202 | qr.align(ALIGN_CENTER); |
stepansnigirev | 0:f43431023689 | 203 | |
stepansnigirev | 0:f43431023689 | 204 | Button btn(nextCallback, "Next address"); |
stepansnigirev | 0:f43431023689 | 205 | btn.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 206 | btn.position(gui.width()*2/3 + 10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 207 | |
stepansnigirev | 0:f43431023689 | 208 | Button btn2(prevCallback, "Previous address"); |
stepansnigirev | 0:f43431023689 | 209 | btn2.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 210 | btn2.position(10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 211 | |
stepansnigirev | 0:f43431023689 | 212 | Button btn3(changeCallback, "Toggle\nchange"); |
stepansnigirev | 0:f43431023689 | 213 | btn3.size(gui.width()/3-20, 80); |
stepansnigirev | 0:f43431023689 | 214 | btn3.position(gui.width()/3 + 10, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 215 | |
stepansnigirev | 0:f43431023689 | 216 | Button btn4(xpubCallback, "Show xpub"); |
stepansnigirev | 0:f43431023689 | 217 | btn4.size(gui.width()/2-20, 80); |
stepansnigirev | 0:f43431023689 | 218 | btn4.position(gui.width()/2+10, gui.height()-200); |
stepansnigirev | 0:f43431023689 | 219 | |
stepansnigirev | 0:f43431023689 | 220 | Button btn5(toMenuCallback, "Menu"); |
stepansnigirev | 0:f43431023689 | 221 | btn5.size(gui.width()/2-20, 80); |
stepansnigirev | 0:f43431023689 | 222 | btn5.position(10, gui.height()-200); |
stepansnigirev | 0:f43431023689 | 223 | } |
stepansnigirev | 0:f43431023689 | 224 | |
stepansnigirev | 0:f43431023689 | 225 | static lv_res_t toMenuCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 226 | showMenu(); |
stepansnigirev | 0:f43431023689 | 227 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 228 | } |
stepansnigirev | 0:f43431023689 | 229 | |
stepansnigirev | 0:f43431023689 | 230 | static lv_res_t showAddressesCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 231 | showAddressScreen(); |
stepansnigirev | 0:f43431023689 | 232 | showAddress(child_index, change); |
stepansnigirev | 0:f43431023689 | 233 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 234 | } |
stepansnigirev | 0:f43431023689 | 235 | static lv_res_t saveXpubCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 236 | // TODO: |
stepansnigirev | 0:f43431023689 | 237 | // - check if SD card present |
stepansnigirev | 0:f43431023689 | 238 | // - save xpub to the "xpub.txt" |
stepansnigirev | 0:f43431023689 | 239 | // - check if write was sucessful |
stepansnigirev | 0:f43431023689 | 240 | // - show success / fail message |
stepansnigirev | 0:f43431023689 | 241 | if(!SD.detected()){ |
stepansnigirev | 0:f43431023689 | 242 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 0:f43431023689 | 243 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 244 | } |
stepansnigirev | 0:f43431023689 | 245 | int res = SD.save("xpub.txt", xpub.toString()); |
stepansnigirev | 0:f43431023689 | 246 | if(res != SD_SUCCESS){ |
stepansnigirev | 0:f43431023689 | 247 | showMessage("Error", "Something wrong with SD card"); |
stepansnigirev | 0:f43431023689 | 248 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 249 | } |
stepansnigirev | 0:f43431023689 | 250 | showMessage("Success","Master public key is saved to the SD card"); |
stepansnigirev | 0:f43431023689 | 251 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 252 | } |
stepansnigirev | 0:f43431023689 | 253 | static lv_res_t signConfirmCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 254 | if(!SD.detected()){ |
stepansnigirev | 0:f43431023689 | 255 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 0:f43431023689 | 256 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 257 | } |
stepansnigirev | 0:f43431023689 | 258 | psbt.sign(account); |
stepansnigirev | 0:f43431023689 | 259 | |
stepansnigirev | 0:f43431023689 | 260 | uint8_t * raw = (uint8_t *)calloc(psbt.length(), sizeof(uint8_t)); |
stepansnigirev | 0:f43431023689 | 261 | size_t len = psbt.serialize(raw, psbt.length()); |
stepansnigirev | 0:f43431023689 | 262 | psbt = PSBT(); |
stepansnigirev | 0:f43431023689 | 263 | // cout << len << endl; |
stepansnigirev | 0:f43431023689 | 264 | char * b64 = (char *)calloc(2*len+1, sizeof(char)); |
stepansnigirev | 0:f43431023689 | 265 | len = toBase64(raw, len, b64, 2*len+1); |
stepansnigirev | 0:f43431023689 | 266 | free(raw); |
stepansnigirev | 0:f43431023689 | 267 | |
stepansnigirev | 0:f43431023689 | 268 | int res = SD.save("signed.psbt", b64); |
stepansnigirev | 0:f43431023689 | 269 | free(b64); |
stepansnigirev | 0:f43431023689 | 270 | if(res != SD_SUCCESS){ |
stepansnigirev | 0:f43431023689 | 271 | showMessage("Error", "Something wrong with SD card"); |
stepansnigirev | 0:f43431023689 | 272 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 273 | } |
stepansnigirev | 0:f43431023689 | 274 | showMessage("Success", "Signed transaction saved to the SD card"); |
stepansnigirev | 0:f43431023689 | 275 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 276 | } |
stepansnigirev | 0:f43431023689 | 277 | void showSignRequest(){ |
stepansnigirev | 0:f43431023689 | 278 | stringstream ss; |
stepansnigirev | 0:f43431023689 | 279 | ss << "Sending:\n\n"; |
stepansnigirev | 0:f43431023689 | 280 | for(unsigned int i=0; i<psbt.tx.outputsNumber; i++){ |
stepansnigirev | 0:f43431023689 | 281 | // TODO: better to put it into PSBT method like check_output or something |
stepansnigirev | 0:f43431023689 | 282 | bool mine = false; |
stepansnigirev | 0:f43431023689 | 283 | if(psbt.txOutsMeta[i].derivationsLen > 0){ |
stepansnigirev | 0:f43431023689 | 284 | for(unsigned int j=0; j<psbt.txOutsMeta[i].derivationsLen; j++){ |
stepansnigirev | 0:f43431023689 | 285 | HDPrivateKey prv = account.derive(psbt.txOutsMeta[i].derivations[j].derivation, psbt.txOutsMeta[i].derivations[j].derivationLen); |
stepansnigirev | 0:f43431023689 | 286 | PublicKey pub = prv.publicKey(); |
stepansnigirev | 0:f43431023689 | 287 | // TODO: fix public key comparison |
stepansnigirev | 0:f43431023689 | 288 | if(pub.toString() == psbt.txOutsMeta[i].derivations[j].pubkey.toString()){ |
stepansnigirev | 0:f43431023689 | 289 | mine = true; |
stepansnigirev | 0:f43431023689 | 290 | } |
stepansnigirev | 0:f43431023689 | 291 | } |
stepansnigirev | 0:f43431023689 | 292 | } |
stepansnigirev | 0:f43431023689 | 293 | ss << psbt.tx.txOuts[i].address(&Testnet); |
stepansnigirev | 0:f43431023689 | 294 | if(mine){ |
stepansnigirev | 0:f43431023689 | 295 | ss << " (change)"; |
stepansnigirev | 0:f43431023689 | 296 | } |
stepansnigirev | 0:f43431023689 | 297 | ss << ": " << (float(psbt.tx.txOuts[i].amount)/1e5) << " mBTC\n\n"; |
stepansnigirev | 0:f43431023689 | 298 | } |
stepansnigirev | 0:f43431023689 | 299 | ss << "Fee: " << (float(psbt.fee())/1e5) << " mBTC"; |
stepansnigirev | 0:f43431023689 | 300 | |
stepansnigirev | 0:f43431023689 | 301 | gui.clear(); |
stepansnigirev | 0:f43431023689 | 302 | titleLbl = Label("Sign transaction?"); |
stepansnigirev | 0:f43431023689 | 303 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 0:f43431023689 | 304 | titleLbl.position(0, 40); |
stepansnigirev | 0:f43431023689 | 305 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 306 | |
stepansnigirev | 0:f43431023689 | 307 | dataLbl = Label(ss.str()); |
stepansnigirev | 0:f43431023689 | 308 | dataLbl.size(gui.width()-100, 100); |
stepansnigirev | 0:f43431023689 | 309 | dataLbl.position(50, 300); |
stepansnigirev | 0:f43431023689 | 310 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 0:f43431023689 | 311 | |
stepansnigirev | 0:f43431023689 | 312 | Button btn(toMenuCallback, "Cancel"); |
stepansnigirev | 0:f43431023689 | 313 | btn.size(gui.width()/2-45, 80); |
stepansnigirev | 0:f43431023689 | 314 | btn.position(30, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 315 | |
stepansnigirev | 0:f43431023689 | 316 | Button btn2(signConfirmCallback, "Confirm"); |
stepansnigirev | 0:f43431023689 | 317 | btn2.size(gui.width()/2-45, 80); |
stepansnigirev | 0:f43431023689 | 318 | btn2.position(gui.width()/2+30, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 319 | } |
stepansnigirev | 0:f43431023689 | 320 | static lv_res_t signPSBTCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 321 | if(!SD.detected()){ |
stepansnigirev | 0:f43431023689 | 322 | showMessage("Error", "SD card is not present"); |
stepansnigirev | 0:f43431023689 | 323 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 324 | } |
stepansnigirev | 0:f43431023689 | 325 | string s = SD.read("unsigned.psbt"); |
stepansnigirev | 0:f43431023689 | 326 | if(s.length() == 0){ |
stepansnigirev | 0:f43431023689 | 327 | showMessage("Fail", "Can't read unsigned.psbt file"); |
stepansnigirev | 0:f43431023689 | 328 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 329 | } |
stepansnigirev | 0:f43431023689 | 330 | uint8_t raw[1000]; |
stepansnigirev | 0:f43431023689 | 331 | unsigned int len = fromBase64(s.c_str(), s.length(), raw, sizeof(raw)); |
stepansnigirev | 0:f43431023689 | 332 | if(len == 0){ |
stepansnigirev | 0:f43431023689 | 333 | showMessage("Fail", "Can't convert from base64"); |
stepansnigirev | 0:f43431023689 | 334 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 335 | } |
stepansnigirev | 0:f43431023689 | 336 | psbt.reset(); |
stepansnigirev | 0:f43431023689 | 337 | len = psbt.parse(raw, len); |
stepansnigirev | 0:f43431023689 | 338 | if(len == 0){ |
stepansnigirev | 0:f43431023689 | 339 | showMessage("Fail", "Can't parse PSBT"); |
stepansnigirev | 0:f43431023689 | 340 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 341 | } |
stepansnigirev | 0:f43431023689 | 342 | showSignRequest(); |
stepansnigirev | 0:f43431023689 | 343 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 344 | } |
stepansnigirev | 0:f43431023689 | 345 | static lv_res_t wipeCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 346 | wipe(); |
stepansnigirev | 0:f43431023689 | 347 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 348 | } |
stepansnigirev | 0:f43431023689 | 349 | |
stepansnigirev | 0:f43431023689 | 350 | /*************** mnemonic stuff ***************/ |
stepansnigirev | 0:f43431023689 | 351 | |
stepansnigirev | 0:f43431023689 | 352 | static lv_res_t newMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 353 | // TODO: |
stepansnigirev | 0:f43431023689 | 354 | // - generate random buffer (16 or 32 bytes) |
stepansnigirev | 0:f43431023689 | 355 | // - create a new mnemonic from it |
stepansnigirev | 0:f43431023689 | 356 | // - save this mnemonic |
stepansnigirev | 0:f43431023689 | 357 | // - display it to the user |
stepansnigirev | 0:f43431023689 | 358 | uint8_t randomBuffer[16]; |
stepansnigirev | 0:f43431023689 | 359 | getRandomBuffer(randomBuffer, sizeof(randomBuffer)); |
stepansnigirev | 0:f43431023689 | 360 | string mnemonic = generateMnemonic(randomBuffer, sizeof(randomBuffer)); |
stepansnigirev | 0:f43431023689 | 361 | saveMnemonic(mnemonic); |
stepansnigirev | 0:f43431023689 | 362 | showMessage("Write down your recovery phrase:", mnemonic); |
stepansnigirev | 0:f43431023689 | 363 | initKeys(mnemonic); |
stepansnigirev | 0:f43431023689 | 364 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 365 | } |
stepansnigirev | 0:f43431023689 | 366 | |
stepansnigirev | 0:f43431023689 | 367 | static lv_res_t showMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 0:f43431023689 | 368 | string mnemonic = loadMnemonic(); |
stepansnigirev | 0:f43431023689 | 369 | showMessage("Here is your recovery phrase:", mnemonic); |
stepansnigirev | 0:f43431023689 | 370 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 371 | } |
stepansnigirev | 0:f43431023689 | 372 | |
stepansnigirev | 1:b88ef7630eb3 | 373 | static const char * keys[] = {"q","w","e","r","t","y","u","i","o","p","\n", |
stepansnigirev | 1:b88ef7630eb3 | 374 | "a","s","d","f","g","h","j","k","l","\n", |
stepansnigirev | 1:b88ef7630eb3 | 375 | " ","z","x","c","v","b","n","m","<",""}; |
stepansnigirev | 1:b88ef7630eb3 | 376 | |
stepansnigirev | 1:b88ef7630eb3 | 377 | static lv_res_t typeCallback(lv_obj_t * btn, const char * key){ |
stepansnigirev | 1:b88ef7630eb3 | 378 | if(key[0] == '<'){ |
stepansnigirev | 1:b88ef7630eb3 | 379 | if(mnemonic.length() > 0){ |
stepansnigirev | 1:b88ef7630eb3 | 380 | mnemonic = mnemonic.substr(0,mnemonic.length()-1); |
stepansnigirev | 1:b88ef7630eb3 | 381 | } |
stepansnigirev | 1:b88ef7630eb3 | 382 | }else{ |
stepansnigirev | 1:b88ef7630eb3 | 383 | mnemonic += key; |
stepansnigirev | 1:b88ef7630eb3 | 384 | } |
stepansnigirev | 1:b88ef7630eb3 | 385 | dataLbl.text(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 386 | return LV_RES_OK; |
stepansnigirev | 1:b88ef7630eb3 | 387 | } |
stepansnigirev | 1:b88ef7630eb3 | 388 | |
stepansnigirev | 1:b88ef7630eb3 | 389 | static lv_res_t checkMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 1:b88ef7630eb3 | 390 | if(checkMnemonic(mnemonic.c_str())){ |
stepansnigirev | 1:b88ef7630eb3 | 391 | saveMnemonic(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 392 | initKeys(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 393 | showMessage("Mnemonic is ok", "Recovered sucessfully"); |
stepansnigirev | 1:b88ef7630eb3 | 394 | }else{ |
stepansnigirev | 1:b88ef7630eb3 | 395 | mnemonic = ""; |
stepansnigirev | 1:b88ef7630eb3 | 396 | dataLbl.text(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 397 | } |
stepansnigirev | 1:b88ef7630eb3 | 398 | return LV_RES_OK; |
stepansnigirev | 1:b88ef7630eb3 | 399 | } |
stepansnigirev | 1:b88ef7630eb3 | 400 | |
stepansnigirev | 0:f43431023689 | 401 | static lv_res_t enterMnemonicCallback(lv_obj_t * btn){ |
stepansnigirev | 1:b88ef7630eb3 | 402 | gui.clear(); |
stepansnigirev | 1:b88ef7630eb3 | 403 | mnemonic = ""; |
stepansnigirev | 1:b88ef7630eb3 | 404 | titleLbl = Label("Enter your mnemonic"); |
stepansnigirev | 1:b88ef7630eb3 | 405 | titleLbl.size(gui.width(), 20); |
stepansnigirev | 1:b88ef7630eb3 | 406 | titleLbl.position(0, 40); |
stepansnigirev | 1:b88ef7630eb3 | 407 | titleLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 1:b88ef7630eb3 | 408 | |
stepansnigirev | 1:b88ef7630eb3 | 409 | dataLbl = Label(mnemonic); |
stepansnigirev | 1:b88ef7630eb3 | 410 | dataLbl.size(gui.width()-100, 50); |
stepansnigirev | 1:b88ef7630eb3 | 411 | dataLbl.position(50, 200); |
stepansnigirev | 1:b88ef7630eb3 | 412 | dataLbl.alignText(ALIGN_TEXT_CENTER); |
stepansnigirev | 1:b88ef7630eb3 | 413 | |
stepansnigirev | 1:b88ef7630eb3 | 414 | Keyboard kb(typeCallback, keys); |
stepansnigirev | 1:b88ef7630eb3 | 415 | kb.size(gui.width(), gui.height()/3); |
stepansnigirev | 1:b88ef7630eb3 | 416 | kb.position(0, gui.height()*2/3-150); |
stepansnigirev | 1:b88ef7630eb3 | 417 | |
stepansnigirev | 1:b88ef7630eb3 | 418 | Button btnx(checkMnemonicCallback, "Continue"); |
stepansnigirev | 1:b88ef7630eb3 | 419 | btnx.size(gui.width()-100, 80); |
stepansnigirev | 1:b88ef7630eb3 | 420 | btnx.position(50, gui.height()-100); |
stepansnigirev | 0:f43431023689 | 421 | return LV_RES_OK; |
stepansnigirev | 0:f43431023689 | 422 | } |
stepansnigirev | 0:f43431023689 | 423 | |
stepansnigirev | 0:f43431023689 | 424 | void initKeys(const string mnemonic, const string password){ |
stepansnigirev | 0:f43431023689 | 425 | // TODO: |
stepansnigirev | 0:f43431023689 | 426 | // - derive root key from the mnemonic and empty password |
stepansnigirev | 0:f43431023689 | 427 | // - derive account key using m/84'/1'/0'/ derivation path |
stepansnigirev | 0:f43431023689 | 428 | // - get account master public key |
stepansnigirev | 0:f43431023689 | 429 | root.fromMnemonic(mnemonic, password); |
stepansnigirev | 0:f43431023689 | 430 | account = root.derive("m/84'/1'/0'/"); |
stepansnigirev | 0:f43431023689 | 431 | xpub = account.xpub(); |
stepansnigirev | 0:f43431023689 | 432 | } |