Project template for hardware wallet workshop

Dependencies:   mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI

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;
+}