Project template for hardware wallet workshop

Dependencies:   mbed QSPI_DISCO_F469NI BSP_DISCO_F469NI

main.cpp

Committer:
stepansnigirev
Date:
2019-07-31
Revision:
3:f9462bf83c56
Parent:
2:8b42ea8491ae

File content as of revision 3:f9462bf83c56:

#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 **************/

// IF YOU GET BORED:
// - add an option to mix in user input into generated mnemonic
// - add testnet / mainnet switch
// - add address type configuration (native or nested segwit)

// generates a new mnemonic, called from newMnemonicCallback
string generateNewMnemonic(){
    // TODO: 
    // - generate random buffer (16 or 32 bytes) - getRandomBuffer(buf, size)
    // - create a new mnemonic from it - generateMnemonic(buf, size)

    return "";
}

// checks if entered mnemonic is valid
static lv_res_t checkMnemonicCallback(lv_obj_t * btn){
    // TODO:
    // - check mnemonic (stored in global scope string mnemonic)
    // - if ok, init keys and show success message
    // - otherwise erase the whole mnemonic and update dataLbl text
    // OPTIONAL:
    // - display not only the mnemonic but xpub / xprv as well
    // - ask the user what address type to use - nested or native segwit
    // - ask if we want to use testnet or mainnet

    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();
    if(mnemonic.length() > 0){
        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 showInitScreenCallback(lv_obj_t * btn){
    showInitScreen();
    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()/2-45, 80);
    btnx.position(30+gui.width()/2, gui.height()-100);

    Button btny(showInitScreenCallback, "Back");
    btny.size(gui.width()/2-45, 80);
    btny.position(30, gui.height()-100);
    return LV_RES_OK;
}