
Project template for hardware wallet workshop
Dependencies: mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI
Diff: main.cpp
- Revision:
- 1:6fea6c7dce1c
- Parent:
- 0:176af1483f18
- Child:
- 2:8b42ea8491ae
--- a/main.cpp Mon Jul 29 20:38:08 2019 +0000 +++ b/main.cpp Tue Jul 30 19:29:37 2019 +0000 @@ -0,0 +1,421 @@ +#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 **************/ + +// generates a new mnemonic +string generateNewMnemonic(){ + // TODO: + // - generate random buffer (16 or 32 bytes) - getRandomBuffer(buf, size) + // - create a new mnemonic from it - generateMnemonic(buf, size) + // - save this mnemonic + // - display it to the user + + return ""; +} + +// checks if entered mnemonic is valid +static lv_res_t checkMnemonicCallback(lv_obj_t * btn){ + // TODO: + // - check mnemonic + // - if ok, init keys and show success message + // - otherwise erase the whole mnemonic + + 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(); + 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 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()-100, 80); + btnx.position(50, gui.height()-100); + return LV_RES_OK; +}