
Airgapped hardware wallet based on STM32F469-Discovery board using SD card to pass transaction data
Dependencies: mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI
Revision 4:73e20d662d73, committed 2019-07-30
- Comitter:
- stepansnigirev
- Date:
- Tue Jul 30 19:25:52 2019 +0000
- Parent:
- 3:b16fd275c8fe
- Commit message:
- comments
Changed in this revision
main.cpp | Show annotated file Show diff for this revision Revisions of this file |
uBitcoin.lib | Show annotated file Show diff for this revision Revisions of this file |
--- a/main.cpp Mon Jul 29 09:41:12 2019 +0000 +++ b/main.cpp Tue Jul 30 19:25:52 2019 +0000 @@ -2,47 +2,10 @@ #include "helpers.h" // bitcoin lib -#include "Bitcoin.h" -#include "PSBT.h" - -/*********** forward declarations **************/ - -// generates a new mnemonic -string generateNewMnemonic(); - -// generates hd keys from the mnemonic -void initKeys(const string mnemonic, const string password = ""); - -/****************** GUI classes ****************/ - -Label titleLbl; -Label dataLbl; -QR qr; - -Button btn; -Button printBtn; -Label lbl; +#include "Bitcoin.h" // Public, Private, HD keys, scripts, raw transactions and stuff +#include "PSBT.h" // Partially Signed Bitcoin Transaction format -/***************** GUI functions ***************/ - -// 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(); -// 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); -static lv_res_t toMenuCallback(lv_obj_t * btn); -static lv_res_t showAddressesCallback(lv_obj_t * btn); -static lv_res_t saveXpubCallback(lv_obj_t * btn); -static lv_res_t signPSBTCallback(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 newMnemonicCallback(lv_obj_t * btn); -static lv_res_t enterMnemonicCallback(lv_obj_t * btn); -void showAddress(unsigned int child_index, bool change); - -/***************** bitcoin keys ***************/ +/***************** bitcoin stuff ***************/ string mnemonic; HDPrivateKey root; // root private key @@ -53,6 +16,290 @@ 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 + + uint8_t randomBuffer[16]; + getRandomBuffer(randomBuffer, sizeof(randomBuffer)); + string mnemonic = generateMnemonic(randomBuffer, sizeof(randomBuffer)); + return mnemonic; +} + +// 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 + + if(checkMnemonic(mnemonic.c_str())){ + saveMnemonic(mnemonic); + initKeys(mnemonic); + showMessage("Mnemonic is ok", "Recovered sucessfully"); + }else{ + mnemonic = ""; + dataLbl.text(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 + + root.fromMnemonic(mnemonic, password); + account = root.derive("m/84'/1'/0'/"); + xpub = account.xpub(); +} + +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 = xpub.child(change).child(child_index).address(); + 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 + + if(!SD.detected()){ + showMessage("Error", "SD card is not present"); + return LV_RES_OK; + } + int res = SD.save("xpub.txt", xpub.toString()); + if(res != SD_SUCCESS){ + showMessage("Error", "Something wrong with SD card"); + return LV_RES_OK; + } + showMessage("Success","Master public key is saved to the SD card"); + 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"; + for(unsigned int i=0; i<psbt.tx.outputsNumber; i++){ + bool mine = psbt.isMine(i, account.xpub()); + ss << psbt.tx.txOuts[i].address(&Testnet); + if(mine){ + ss << " (change)"; + } + ss << ": " << (float(psbt.tx.txOuts[i].amount)/1e5) << " mBTC\n\n"; + } + ss << "Fee: " << (float(psbt.fee())/1e5) << " mBTC"; + + 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 + + if(!SD.detected()){ + showMessage("Error", "SD card is not present"); + return LV_RES_OK; + } + string s = SD.read("unsigned.psbt"); + if(s.length() == 0){ + showMessage("Fail", "Can't read unsigned.psbt file"); + return LV_RES_OK; + } + uint8_t * raw = new uint8_t[s.length()]; + unsigned int len = fromBase64(s.c_str(), s.length(), raw, s.length()); + if(len == 0){ + delete [] raw; + showMessage("Fail", "Can't convert from base64"); + return LV_RES_OK; + } + psbt.reset(); + len = psbt.parse(raw, len); + delete [] raw; + if(len == 0){ + showMessage("Fail", "Can't parse PSBT"); + return LV_RES_OK; + } + 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 + + if(!SD.detected()){ + showMessage("Error", "SD card is not present"); + return LV_RES_OK; + } + psbt.sign(account); + + uint8_t * raw = (uint8_t *)calloc(psbt.length(), sizeof(uint8_t)); + size_t len = psbt.serialize(raw, psbt.length()); + psbt = PSBT(); + string b64 = toBase64(raw, len); + + int res = SD.save("signed.psbt", b64.c_str()); + if(res != SD_SUCCESS){ + showMessage("Error", "Something wrong with SD card"); + return LV_RES_OK; + } + showMessage("Success", "Signed transaction saved to the SD card"); + 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(){ @@ -72,6 +319,7 @@ } /****************** GUI stuff *****************/ + void showInitScreen(){ gui.clear(); titleLbl = Label("Let's set it up!"); @@ -141,49 +389,6 @@ btn.align(ALIGN_CENTER); } -static lv_res_t nextCallback(lv_obj_t * btn){ - child_index++; - showAddress(child_index, change); - return LV_RES_OK; -} -static lv_res_t prevCallback(lv_obj_t * btn){ - if(child_index > 0){ - child_index--; - showAddress(child_index, change); - } - return LV_RES_OK; -} -static lv_res_t changeCallback(lv_obj_t * btn){ - change = !change; - showAddress(child_index, change); - return LV_RES_OK; -} -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; -} - -void showAddress(unsigned int child_index, bool change){ - stringstream title; - title << "Your "; - if(change){ - title << "change "; - }else{ - title << "receiving "; - } - title << "address #" << child_index << ":"; - titleLbl.text(title.str()); - string address = xpub.child(change).child(child_index).address(); - dataLbl.text(address); - dataLbl.align(ALIGN_CENTER); - qr.text(string("bitcoin:") + address); - qr.align(ALIGN_CENTER); -} - void showAddressScreen(){ gui.clear(); titleLbl = Label("Your address"); @@ -232,103 +437,7 @@ showAddress(child_index, change); return LV_RES_OK; } -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 - if(!SD.detected()){ - showMessage("Error", "SD card is not present"); - return LV_RES_OK; - } - int res = SD.save("xpub.txt", xpub.toString()); - if(res != SD_SUCCESS){ - showMessage("Error", "Something wrong with SD card"); - return LV_RES_OK; - } - showMessage("Success","Master public key is saved to the SD card"); - return LV_RES_OK; -} -static lv_res_t signConfirmCallback(lv_obj_t * btn){ - if(!SD.detected()){ - showMessage("Error", "SD card is not present"); - return LV_RES_OK; - } - psbt.sign(account); - uint8_t * raw = (uint8_t *)calloc(psbt.length(), sizeof(uint8_t)); - size_t len = psbt.serialize(raw, psbt.length()); - psbt = PSBT(); - string b64 = toBase64(raw, len); - - int res = SD.save("signed.psbt", b64.c_str()); - if(res != SD_SUCCESS){ - showMessage("Error", "Something wrong with SD card"); - return LV_RES_OK; - } - showMessage("Success", "Signed transaction saved to the SD card"); - return LV_RES_OK; -} -void showSignRequest(){ - stringstream ss; - ss << "Sending:\n\n"; - for(unsigned int i=0; i<psbt.tx.outputsNumber; i++){ - bool mine = psbt.isMine(i, account.xpub()); - ss << psbt.tx.txOuts[i].address(&Testnet); - if(mine){ - ss << " (change)"; - } - ss << ": " << (float(psbt.tx.txOuts[i].amount)/1e5) << " mBTC\n\n"; - } - ss << "Fee: " << (float(psbt.fee())/1e5) << " mBTC"; - - 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); -} -static lv_res_t signPSBTCallback(lv_obj_t * btn){ - if(!SD.detected()){ - showMessage("Error", "SD card is not present"); - return LV_RES_OK; - } - string s = SD.read("unsigned.psbt"); - if(s.length() == 0){ - showMessage("Fail", "Can't read unsigned.psbt file"); - return LV_RES_OK; - } - uint8_t * raw = new uint8_t[s.length()]; - unsigned int len = fromBase64(s.c_str(), s.length(), raw, s.length()); - if(len == 0){ - delete [] raw; - showMessage("Fail", "Can't convert from base64"); - return LV_RES_OK; - } - psbt.reset(); - len = psbt.parse(raw, len); - delete [] raw; - if(len == 0){ - showMessage("Fail", "Can't parse PSBT"); - return LV_RES_OK; - } - showSignRequest(); - return LV_RES_OK; -} static lv_res_t wipeCallback(lv_obj_t * btn){ wipe(); return LV_RES_OK; @@ -336,21 +445,6 @@ /*************** mnemonic stuff ***************/ -static lv_res_t newMnemonicCallback(lv_obj_t * btn){ - // TODO: - // - generate random buffer (16 or 32 bytes) - // - create a new mnemonic from it - // - save this mnemonic - // - display it to the user - uint8_t randomBuffer[16]; - getRandomBuffer(randomBuffer, sizeof(randomBuffer)); - string mnemonic = generateMnemonic(randomBuffer, sizeof(randomBuffer)); - saveMnemonic(mnemonic); - showMessage("Write down your recovery phrase:", mnemonic); - initKeys(mnemonic); - return LV_RES_OK; -} - static lv_res_t showMnemonicCallback(lv_obj_t * btn){ string mnemonic = loadMnemonic(); showMessage("Here is your recovery phrase:", mnemonic); @@ -373,18 +467,6 @@ return LV_RES_OK; } -static lv_res_t checkMnemonicCallback(lv_obj_t * btn){ - if(checkMnemonic(mnemonic.c_str())){ - saveMnemonic(mnemonic); - initKeys(mnemonic); - showMessage("Mnemonic is ok", "Recovered sucessfully"); - }else{ - mnemonic = ""; - dataLbl.text(mnemonic); - } - return LV_RES_OK; -} - static lv_res_t enterMnemonicCallback(lv_obj_t * btn){ gui.clear(); mnemonic = ""; @@ -407,13 +489,3 @@ btnx.position(50, gui.height()-100); return LV_RES_OK; } - -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 - root.fromMnemonic(mnemonic, password); - account = root.derive("m/84'/1'/0'/"); - xpub = account.xpub(); -}
--- a/uBitcoin.lib Mon Jul 29 09:41:12 2019 +0000 +++ b/uBitcoin.lib Tue Jul 30 19:25:52 2019 +0000 @@ -1,1 +1,1 @@ -https://github.com/micro-bitcoin/uBitcoin/#51512b4a47d07b95b63d472f6feddfb7016b1088 +https://github.com/micro-bitcoin/uBitcoin/#d95cd475b8a61e887dd8cab39b14f6879b21828c