Airgapped hardware wallet based on STM32F469-Discovery board using SD card to pass transaction data

Dependencies:   mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI

Files at this revision

API Documentation at this revision

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