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
Parent:
2:120e0ca2f3a7
--- 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();
-}