
Project template for hardware wallet workshop
Dependencies: mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI
main.cpp
- Committer:
- stepansnigirev
- Date:
- 2019-07-31
- Revision:
- 3:f9462bf83c56
- Parent:
- 2:8b42ea8491ae
File content as of revision 3:f9462bf83c56:
#include "mbed.h" #include "helpers.h" // bitcoin lib #include "Bitcoin.h" // Public, Private, HD keys, scripts, raw transactions and stuff #include "PSBT.h" // Partially Signed Bitcoin Transaction format /***************** bitcoin stuff ***************/ string mnemonic; HDPrivateKey root; // root private key HDPrivateKey account; // account master private key HDPublicKey xpub; // account master public key bool change = false; // internal or external address unsigned int child_index = 0; // current child index PSBT psbt; // psbt transaction we will be signing /****************** GUI elements ****************/ // some global scope GUI objects we will be changing in callbacks Label titleLbl; Label dataLbl; QR qr; Button btn; Button printBtn; Label lbl; /*********** forward declarations **************/ // handy function to display information to the user // with a title, a message and OK button that goes to the menu void showMessage(const string title, const string message); // signs transaction after user confirmation static lv_res_t signConfirmCallback(lv_obj_t * btn); // derives keys from mnemonic and password void initKeys(const string mnemonic, const string password = ""); // if mnemonic is not present we can generate or recover it void showInitScreen(); // if mnemonic is there we go directly to the main menu void showMenu(); // some functions that handle button clicks static lv_res_t toMenuCallback(lv_obj_t * btn); static lv_res_t showAddressesCallback(lv_obj_t * btn); static lv_res_t showMnemonicCallback(lv_obj_t * btn); static lv_res_t wipeCallback(lv_obj_t * btn); static lv_res_t enterMnemonicCallback(lv_obj_t * btn); // shows address on the screen void showAddress(unsigned int child_index, bool change); /*********** functions to complete **************/ // IF YOU GET BORED: // - add an option to mix in user input into generated mnemonic // - add testnet / mainnet switch // - add address type configuration (native or nested segwit) // generates a new mnemonic, called from newMnemonicCallback string generateNewMnemonic(){ // TODO: // - generate random buffer (16 or 32 bytes) - getRandomBuffer(buf, size) // - create a new mnemonic from it - generateMnemonic(buf, size) return ""; } // checks if entered mnemonic is valid static lv_res_t checkMnemonicCallback(lv_obj_t * btn){ // TODO: // - check mnemonic (stored in global scope string mnemonic) // - if ok, init keys and show success message // - otherwise erase the whole mnemonic and update dataLbl text // OPTIONAL: // - display not only the mnemonic but xpub / xprv as well // - ask the user what address type to use - nested or native segwit // - ask if we want to use testnet or mainnet return LV_RES_OK; } // generates hd keys from the mnemonic void initKeys(const string mnemonic, const string password){ // TODO: // - derive root key from the mnemonic and empty password // - derive account key using m/84'/1'/0'/ derivation path // - get account master public key } void showAddress(unsigned int child_index, bool change){ // TODO: // - derive an address from xpub according to the derivation // - set dataLbl text to the address // - set qr text to "bitcoin:address" // OPTIONAL: // - display both bech32 and nested segwit addresses stringstream title; title << "Your "; if(change){ title << "change "; }else{ title << "receiving "; } title << "address #" << child_index << ":"; titleLbl.text(title.str()); // generate the address here string address = "to be implemented"; dataLbl.text(address); dataLbl.align(ALIGN_CENTER); qr.text(string("bitcoin:") + address); qr.align(ALIGN_CENTER); } // this will be called when we press "save master key" button static lv_res_t saveXpubCallback(lv_obj_t * btn){ // TODO: // - check if SD card present // - save xpub to the "xpub.txt" // - check if write was sucessful // - show success / fail message // OPTIONAL: // - use [fingerprint/derivation]xpub format // - create bitcoin core descriptor for bitcoin-cli importmulti showMessage("Error","To be implemented"); return LV_RES_OK; } // displays confirmation screen for the transaction signing void showSignRequest(){ // TODO: // display information of the transaction: // - go through all outputs // - detect if the address is the change address // - if not, show information in the form "address: amount" // - if it is change, hide or mark as a change // - display the transaction fee // OPTIONAL: // - verify that pubkey is actually used in the script // - do the same for bip49 and check redeem script // - check if derivation path is not weird (indexes are reasonable) stringstream ss; ss << "Sending:\n\n"; ss << "to be implemented"; gui.clear(); titleLbl = Label("Sign transaction?"); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); dataLbl = Label(ss.str()); dataLbl.size(gui.width()-100, 100); dataLbl.position(50, 300); dataLbl.alignText(ALIGN_TEXT_CENTER); Button btn(toMenuCallback, "Cancel"); btn.size(gui.width()/2-45, 80); btn.position(30, gui.height()-100); Button btn2(signConfirmCallback, "Confirm"); btn2.size(gui.width()/2-45, 80); btn2.position(gui.width()/2+30, gui.height()-100); } // reads unsigned transaction from SD card static lv_res_t signPSBTCallback(lv_obj_t * btn){ // TODO: // - check if SD card is there // - read data from "unsigned.psbt" // - convert it from base64 to raw bytes // - parse psbt transaction // - call showSignRequest() // OPTIONAL // - also show signed transaction as a QR code showSignRequest(); return LV_RES_OK; } // signs transaction static lv_res_t signConfirmCallback(lv_obj_t * btn){ // TODO: // - check if SD card is still there // - serialize the psbt to byte array // - convert to base64 // - save to "signed.psbt" // - show success message showMessage("Error", "To be implemented"); return LV_RES_OK; } /***************** GUI functions ***************/ static lv_res_t newMnemonicCallback(lv_obj_t * btn){ string mnemonic = generateNewMnemonic(); if(mnemonic.length() > 0){ saveMnemonic(mnemonic); showMessage("Write down your recovery phrase:", mnemonic); initKeys(mnemonic); } return LV_RES_OK; } // show next address static lv_res_t nextCallback(lv_obj_t * btn){ child_index++; showAddress(child_index, change); return LV_RES_OK; } // show previous address static lv_res_t prevCallback(lv_obj_t * btn){ if(child_index > 0){ child_index--; showAddress(child_index, change); } return LV_RES_OK; } // switch to change addresses static lv_res_t changeCallback(lv_obj_t * btn){ change = !change; showAddress(child_index, change); return LV_RES_OK; } // show master public key static lv_res_t xpubCallback(lv_obj_t * btn){ titleLbl.text("Your master public key"); qr.text(xpub.toString()); dataLbl.text(xpub.toString()); qr.align(ALIGN_CENTER); dataLbl.align(ALIGN_CENTER); return LV_RES_OK; } /******************* Main part *****************/ int main(){ init(); string mnemonic = loadMnemonic(); if(mnemonic.length() == 0){ showInitScreen(); }else{ initKeys(mnemonic); showMenu(); } while(1){ gui.update(); } } /****************** GUI stuff *****************/ void showInitScreen(){ gui.clear(); titleLbl = Label("Let's set it up!"); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); Button btn(newMnemonicCallback, "Generate new mnemonic"); btn.size(gui.width()-100, 80); btn.position(0, 200); btn.align(ALIGN_CENTER); Button btn2(enterMnemonicCallback, "Enter existing mnemonic"); btn2.size(gui.width()-100, 80); btn2.position(0, 300); btn2.align(ALIGN_CENTER); } void showMenu(){ gui.clear(); titleLbl = Label("What do you want to do?"); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); Button btn(showAddressesCallback, "Show addresses"); btn.size(gui.width()-100, 80); btn.position(0, 100); btn.align(ALIGN_CENTER); Button btn2(saveXpubCallback, "Export xpub"); btn2.size(gui.width()-100, 80); btn2.position(0, 300); btn2.align(ALIGN_CENTER); Button btn3(signPSBTCallback, "Sign PSBT transaction"); btn3.size(gui.width()-100, 80); btn3.position(0, 400); btn3.align(ALIGN_CENTER); Button btn4(wipeCallback, "Wipe device"); btn4.size(gui.width()-100, 80); btn4.position(0, 600); btn4.align(ALIGN_CENTER); Button btn5(showMnemonicCallback, "Show mnemonic"); btn5.size(gui.width()-100, 80); btn5.position(0, 700); btn5.align(ALIGN_CENTER); } void showMessage(const string title, const string message){ gui.clear(); titleLbl = Label(title); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); dataLbl = Label(message); dataLbl.size(gui.width()-100, 100); dataLbl.position(50, 300); dataLbl.alignText(ALIGN_TEXT_CENTER); Button btn(toMenuCallback, "OK"); btn.size(gui.width()-100, 80); btn.position(0, gui.height()-100); btn.align(ALIGN_CENTER); } void showAddressScreen(){ gui.clear(); titleLbl = Label("Your address"); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); dataLbl = Label(" "); dataLbl.size(gui.width()-100, 100); // full width dataLbl.position(50, gui.height()-300); dataLbl.alignText(ALIGN_TEXT_CENTER); qr = QR(" "); qr.size(gui.width()-100); qr.position(0, 100); qr.align(ALIGN_CENTER); Button btn(nextCallback, "Next address"); btn.size(gui.width()/3-20, 80); btn.position(gui.width()*2/3 + 10, gui.height()-100); Button btn2(prevCallback, "Previous address"); btn2.size(gui.width()/3-20, 80); btn2.position(10, gui.height()-100); Button btn3(changeCallback, "Toggle\nchange"); btn3.size(gui.width()/3-20, 80); btn3.position(gui.width()/3 + 10, gui.height()-100); Button btn4(xpubCallback, "Show xpub"); btn4.size(gui.width()/2-20, 80); btn4.position(gui.width()/2+10, gui.height()-200); Button btn5(toMenuCallback, "Menu"); btn5.size(gui.width()/2-20, 80); btn5.position(10, gui.height()-200); } static lv_res_t toMenuCallback(lv_obj_t * btn){ showMenu(); return LV_RES_OK; } static lv_res_t showAddressesCallback(lv_obj_t * btn){ showAddressScreen(); showAddress(child_index, change); return LV_RES_OK; } static lv_res_t wipeCallback(lv_obj_t * btn){ wipe(); return LV_RES_OK; } /*************** mnemonic stuff ***************/ static lv_res_t showMnemonicCallback(lv_obj_t * btn){ string mnemonic = loadMnemonic(); showMessage("Here is your recovery phrase:", mnemonic); return LV_RES_OK; } static const char * keys[] = {"q","w","e","r","t","y","u","i","o","p","\n", "a","s","d","f","g","h","j","k","l","\n", " ","z","x","c","v","b","n","m","<",""}; static lv_res_t typeCallback(lv_obj_t * btn, const char * key){ if(key[0] == '<'){ if(mnemonic.length() > 0){ mnemonic = mnemonic.substr(0,mnemonic.length()-1); } }else{ mnemonic += key; } dataLbl.text(mnemonic); return LV_RES_OK; } static lv_res_t showInitScreenCallback(lv_obj_t * btn){ showInitScreen(); return LV_RES_OK; } static lv_res_t enterMnemonicCallback(lv_obj_t * btn){ gui.clear(); mnemonic = ""; titleLbl = Label("Enter your mnemonic"); titleLbl.size(gui.width(), 20); titleLbl.position(0, 40); titleLbl.alignText(ALIGN_TEXT_CENTER); dataLbl = Label(mnemonic); dataLbl.size(gui.width()-100, 50); dataLbl.position(50, 200); dataLbl.alignText(ALIGN_TEXT_CENTER); Keyboard kb(typeCallback, keys); kb.size(gui.width(), gui.height()/3); kb.position(0, gui.height()*2/3-150); Button btnx(checkMnemonicCallback, "Continue"); btnx.size(gui.width()/2-45, 80); btnx.position(30+gui.width()/2, gui.height()-100); Button btny(showInitScreenCallback, "Back"); btny.size(gui.width()/2-45, 80); btny.position(30, gui.height()-100); return LV_RES_OK; }