From ad30c5697c22351e4bd3c6d47af0d4d0e5314178 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Wed, 13 Nov 2019 21:55:20 +0100 Subject: [PATCH 01/11] initial micropython logic --- README.md | 43 +- components/gui/alert/alert.cpp | 37 - components/gui/alert/alert.h | 30 - components/gui/alert/prompt.cpp | 62 -- components/gui/alert/qr_alert.cpp | 41 - components/gui/gui.cpp | 931 -------------------- components/gui/gui.h | 68 -- components/gui/gui_common.cpp | 99 --- components/gui/gui_common.h | 23 - components/gui/hal_lvgl/tft/tft.c | 790 ----------------- components/gui/hal_lvgl/tft/tft.h | 44 - components/gui/hal_lvgl/touchpad/touchpad.c | 114 --- components/gui/hal_lvgl/touchpad/touchpad.h | 42 - components/gui/mnemonic/mnemonic.h | 10 - components/gui/mnemonic/mnemonic_screen.cpp | 118 --- components/gui/tpcal/tpcal.c | 434 --------- components/gui/tpcal/tpcal.h | 24 - components/helpers/helpers.cpp | 32 - components/helpers/helpers.h | 13 - components/host/host.cpp | 84 -- components/host/host.h | 21 - components/keystore/keystore.c | 750 ---------------- components/keystore/keystore.h | 76 -- components/keystore/networks.c | 79 -- components/keystore/networks.h | 45 - components/rng/rng.cpp | 33 - components/rng/rng.h | 17 - components/storage/storage.cpp | 362 -------- components/storage/storage.h | 33 - gui/__init__.py | 3 + gui/common.py | 140 +++ gui/core.py | 32 + gui/decorators.py | 23 + gui/display_unixport.py | 38 + gui/popups.py | 62 ++ gui/screens.py | 250 ++++++ keystore.py | 10 + libraries/BSP_DISCO_F469NI.lib | 1 - libraries/QRScanner/src/QRScanner.cpp | 64 -- libraries/QRScanner/src/QRScanner.h | 67 -- libraries/libwally-core.lib | 1 - libraries/lv_conf.h | 489 ---------- libraries/lvgl.lib | 1 - libraries/qrcode/qrcode.c | 872 ------------------ libraries/qrcode/qrcode.h | 95 -- libraries/secp256k1.lib | 1 - main.cpp | 564 ------------ main.py | 157 ++++ mbed-os.lib | 1 - mbed_app.json | 7 - qrscanner.py | 97 ++ specter_config.h | 11 - 52 files changed, 817 insertions(+), 6624 deletions(-) delete mode 100644 components/gui/alert/alert.cpp delete mode 100644 components/gui/alert/alert.h delete mode 100644 components/gui/alert/prompt.cpp delete mode 100644 components/gui/alert/qr_alert.cpp delete mode 100644 components/gui/gui.cpp delete mode 100644 components/gui/gui.h delete mode 100644 components/gui/gui_common.cpp delete mode 100644 components/gui/gui_common.h delete mode 100644 components/gui/hal_lvgl/tft/tft.c delete mode 100644 components/gui/hal_lvgl/tft/tft.h delete mode 100644 components/gui/hal_lvgl/touchpad/touchpad.c delete mode 100644 components/gui/hal_lvgl/touchpad/touchpad.h delete mode 100644 components/gui/mnemonic/mnemonic.h delete mode 100644 components/gui/mnemonic/mnemonic_screen.cpp delete mode 100644 components/gui/tpcal/tpcal.c delete mode 100644 components/gui/tpcal/tpcal.h delete mode 100644 components/helpers/helpers.cpp delete mode 100644 components/helpers/helpers.h delete mode 100644 components/host/host.cpp delete mode 100644 components/host/host.h delete mode 100644 components/keystore/keystore.c delete mode 100644 components/keystore/keystore.h delete mode 100644 components/keystore/networks.c delete mode 100644 components/keystore/networks.h delete mode 100644 components/rng/rng.cpp delete mode 100644 components/rng/rng.h delete mode 100644 components/storage/storage.cpp delete mode 100644 components/storage/storage.h create mode 100644 gui/__init__.py create mode 100644 gui/common.py create mode 100644 gui/core.py create mode 100644 gui/decorators.py create mode 100644 gui/display_unixport.py create mode 100644 gui/popups.py create mode 100644 gui/screens.py create mode 100644 keystore.py delete mode 100644 libraries/BSP_DISCO_F469NI.lib delete mode 100755 libraries/QRScanner/src/QRScanner.cpp delete mode 100755 libraries/QRScanner/src/QRScanner.h delete mode 100644 libraries/libwally-core.lib delete mode 100644 libraries/lv_conf.h delete mode 100644 libraries/lvgl.lib delete mode 100644 libraries/qrcode/qrcode.c delete mode 100644 libraries/qrcode/qrcode.h delete mode 100644 libraries/secp256k1.lib delete mode 100644 main.cpp create mode 100644 main.py delete mode 100644 mbed-os.lib delete mode 100644 mbed_app.json create mode 100644 qrscanner.py delete mode 100644 specter_config.h diff --git a/README.md b/README.md index 74bb818..fa0f9ef 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ We are also working on the kit that you could buy from us that will include a 3d ## Dev plan -- [x] Single key functionality +- [ ] Single key functionality - [x] Reckless storage -- [x] Multisig +- [ ] Multisig - [ ] SD card support - [ ] Secure element integration - [ ] Secure boot @@ -85,39 +85,6 @@ A few crappy pictures: ## Compiling the code yourself _(This is an optional step for developers. Typical users can just run off the pre-compiled `specter-diy.bin` file referenced above)_ -Create a virtualenv and once it's active install Mbed CLI via pip: -``` -pip install mbed-cli -``` - -Make sure you're in the `specter-diy` root and initialize the project dir: -``` -mbed config root . -``` - -Download `gcc-arm-none-eabi` from: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads - -And then decompress it: -``` -tar xjf gcc-arm-none-eabi-8-2019-q3-update-mac.tar.bz2 -``` - -Configure Mbed to use gcc-arm: -``` -mbed config GCC_ARM_PATH /path/to/gcc-arm/bin -``` - -Fetch the libraries mbed will need: -``` -mbed deploy -``` - -Set the default Mbed toolchain: -``` -mbed toolchain GCC_ARM -``` - -Finally: -``` -mbed compile -``` +TBD. + +Micropython now. Ref: https://github.com/diybitcoinhardware/f469-disco diff --git a/components/gui/alert/alert.cpp b/components/gui/alert/alert.cpp deleted file mode 100644 index 41fdb86..0000000 --- a/components/gui/alert/alert.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "alert.h" -#include "../gui_common.h" -#include - -static lv_obj_t * prev_scr; - -static void cb_back(lv_obj_t * btn, lv_event_t event){ - if(event == LV_EVENT_CLICKED){ - lv_disp_load_scr(prev_scr); - } -} - -lv_obj_t * gui_alert_create(const char * title, - const char * message, - const char * btntext){ - prev_scr = lv_disp_get_scr_act(NULL); - - lv_obj_t * scr = lv_obj_create(NULL, NULL); - - lv_obj_t * obj = gui_title_create(scr, title); - - // create main text - lv_obj_t * txt = lv_label_create(scr, NULL); - lv_label_set_text(txt, message); - lv_label_set_long_mode(txt, LV_LABEL_LONG_BREAK); - lv_obj_set_width(txt, LV_HOR_RES-2*PADDING); - lv_obj_align(txt, NULL, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_align(obj, LV_LABEL_ALIGN_CENTER); - lv_obj_set_y(txt, lv_obj_get_y(obj) + lv_obj_get_height(obj) + PADDING); - - if(btntext != NULL){ - // create button - gui_button_create(scr, btntext, cb_back); - } - lv_disp_load_scr(scr); - return txt; -} \ No newline at end of file diff --git a/components/gui/alert/alert.h b/components/gui/alert/alert.h deleted file mode 100644 index e3e3d6a..0000000 --- a/components/gui/alert/alert.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef GUI_ALERT_H -#define GUI_ALERT_H - -#include "lvgl.h" - -/** - * Create an alert. - * Button returns back to the screen active - * at the moment of the function call. - * If btntext is NULL there will be no button. - * (for example for critical errors) - * - * Returns the pointer to the message label - * (if you want to change it) - */ -lv_obj_t * gui_alert_create(const char * title, - const char * message, - const char * btntext); - -lv_obj_t * gui_qr_alert_create(const char * title, - const char * qr_text, - const char * message, - const char * btntext); -lv_obj_t * gui_prompt_create(const char * title, - const char * message, - const char * ok_text, - void (*ok_callback)(void * ptr), - const char * cancel_text, - void (*cancel_callback)(void * ptr)); -#endif /* GUI_ALERT_H */ diff --git a/components/gui/alert/prompt.cpp b/components/gui/alert/prompt.cpp deleted file mode 100644 index b861758..0000000 --- a/components/gui/alert/prompt.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "alert.h" -#include "../gui_common.h" -#include - -static lv_obj_t * prev_scr; -static void (*ok_cb)(void * ptr); -static void (*cancel_cb)(void * ptr); - -static void cb_ok(lv_obj_t * btn, lv_event_t event){ - if(event == LV_EVENT_CLICKED){ - lv_disp_load_scr(prev_scr); - if(ok_cb != NULL){ - lv_async_call(ok_cb, NULL); - } - } -} - -static void cb_cancel(lv_obj_t * btn, lv_event_t event){ - if(event == LV_EVENT_CLICKED){ - lv_disp_load_scr(prev_scr); - if(cancel_cb != NULL){ - lv_async_call(cancel_cb, NULL); - } - } -} - -lv_obj_t * gui_prompt_create(const char * title, - const char * message, - const char * ok_text, - void (*ok_callback)(void * ptr), - const char * cancel_text, - void (*cancel_callback)(void * ptr)){ - prev_scr = lv_disp_get_scr_act(NULL); - - lv_obj_t * scr = lv_obj_create(NULL, NULL); - - lv_obj_t * obj = gui_title_create(scr, title); - - ok_cb = ok_callback; - cancel_cb = cancel_callback; - - // create main text - lv_obj_t * txt = lv_label_create(scr, NULL); - lv_label_set_text(txt, message); - lv_label_set_long_mode(txt, LV_LABEL_LONG_BREAK); - lv_obj_set_width(txt, LV_HOR_RES-2*PADDING); - lv_obj_align(txt, NULL, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_align(obj, LV_LABEL_ALIGN_CENTER); - lv_obj_set_y(txt, lv_obj_get_y(obj) + lv_obj_get_height(obj) + PADDING); - - // create button - lv_obj_t * btn; - btn = gui_button_create(scr, cancel_text, cb_cancel); - lv_obj_set_width(btn, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(btn, PADDING); - btn = gui_button_create(scr, ok_text, cb_ok); - lv_obj_set_width(btn, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(btn, LV_HOR_RES/2+PADDING/2); - - lv_disp_load_scr(scr); - return txt; -} \ No newline at end of file diff --git a/components/gui/alert/qr_alert.cpp b/components/gui/alert/qr_alert.cpp deleted file mode 100644 index 8207531..0000000 --- a/components/gui/alert/qr_alert.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "alert.h" -#include "../gui_common.h" -#include - -static lv_obj_t * prev_scr; - -static void cb_back(lv_obj_t * btn, lv_event_t event){ - if(event == LV_EVENT_CLICKED){ - lv_disp_load_scr(prev_scr); - } -} - -lv_obj_t * gui_qr_alert_create(const char * title, - const char * qr_text, - const char * message, - const char * btntext){ - prev_scr = lv_disp_get_scr_act(NULL); - - lv_obj_t * scr = lv_obj_create(NULL, NULL); - - lv_obj_t * obj = gui_title_create(scr, title); - - lv_obj_t * qr = gui_qr_create(scr, LV_HOR_RES-100, qr_text); - lv_obj_set_y(qr, lv_obj_get_y(obj) + lv_obj_get_height(obj) + PADDING); - - // create main text - lv_obj_t * txt = lv_label_create(scr, NULL); - lv_label_set_text(txt, message); - lv_label_set_long_mode(txt, LV_LABEL_LONG_BREAK); - lv_obj_set_width(txt, LV_HOR_RES-2*PADDING); - lv_obj_align(txt, NULL, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_label_set_align(txt, LV_LABEL_ALIGN_CENTER); - lv_obj_set_y(txt, lv_obj_get_y(qr) + lv_obj_get_height(qr)+PADDING); - - if(btntext != NULL){ - // create button - gui_button_create(scr, btntext, cb_back); - } - lv_disp_load_scr(scr); - return qr; -} \ No newline at end of file diff --git a/components/gui/gui.cpp b/components/gui/gui.cpp deleted file mode 100644 index fd464f0..0000000 --- a/components/gui/gui.cpp +++ /dev/null @@ -1,931 +0,0 @@ -#include "mbed.h" -#include "helpers.h" -#include "gui.h" -#include "gui_common.h" -#include - -#include "tft.h" -#include "touchpad.h" -#include "lvgl.h" - -#include "tpcal.h" -#include "alert.h" -#include "mnemonic.h" - -#define BASE_UNDEFINED 0 -#define BASE_INIT_SCREEN 1 -#define BASE_RECOVERY_SCREEN 2 -#define BASE_MNEMONIC_SCREEN 3 -#define BASE_PASSWORD_SCREEN 4 -#define BASE_MAIN_SCREEN 5 -#define BASE_NETWORKS_SCREEN 6 -#define BASE_XPUBS_SCREEN 7 -#define BASE_PSBT_CONFIRMATION 8 -#define BASE_RECKLESS 9 -#define BASE_LIST_WALLETS 10 -#define BASE_ADDRESSES_SCREEN 11 -#define BASE_CONFIRM_NEW_WALLET 12 - -#define BACK_TO_MAIN 0xFF -#define GET_NEW_WALLET 0xFE -#define USER_CANCEL 0xFF00 -#define USER_CONFIRM 0xFF01 - -using std::string; - -/* timer to count time in a loop and update the lvgl */ -static volatile int t = 0; -Ticker ms_tick; -static void onMillisecondTicker(void){ - t++; -} - -static int base = BASE_UNDEFINED; -static char input_buffer[501] = ""; - -static int action = GUI_NO_ACTION; -static int value = 0; -static char str[251] = ""; - -static uint8_t network_index; -static const char ** network_names; -static string default_xpubs[2]; - -lv_style_t title_style; - -// main screen that we redraw -// alerts and prompts are on top of it -static lv_obj_t * scr; - -static void gui_styles_create(){ - lv_style_copy(&title_style, &lv_style_plain); - title_style.text.font = &lv_font_roboto_28; -} -static void cb(lv_obj_t * obj, lv_event_t event); -static void back_to_main(void * ptr); -static void back_to_init(void * ptr); -static void process_command(int val); - -int gui_get_action(){ - return action; -} -int gui_get_value(){ - return value; -} -char * gui_get_str(){ - return str; -} - -void gui_clear_action(){ - action = GUI_NO_ACTION; -} - -void gui_set_network(uint8_t index){ - network_index = index; -} - -void gui_set_available_networks(const char * names[]){ - network_names = names; -} - -void gui_start(){ - gui_show_init_screen(); -} - -/***************** calibration stuff ****************/ - -void hang(){ - while(true){ // just stop operating - sleep(); - } -} - -void fs_err(const char * msg){ - static const char * errormsg; - if(msg!=NULL){ // if null - shows last message - errormsg = msg; - } - gui_alert_create("File system error", errormsg, NULL); -} - -// FIXME: move most of this stuff to storage -int gui_calibration_load(){ - int err = 0; - - // check if settings file is in the internal storage - DIR *d = opendir("/internal/"); - if(!d){ - fs_err("Can't open internal storage"); - return -1; - } - closedir(d); - d = opendir("/internal/gui/"); - if(!d){ - err = mkdir("/internal/gui", 0777); - if(err != 0){ - fs_err("Failed to create gui folder"); - return -2; - } - } - closedir(d); - // check if we need to calibrate the screen - FILE *f = fopen("/internal/gui/calibration", "r"); - if(!f){ - return -3; - } - lv_point_t points[4]; - fread(points, sizeof(lv_point_t), 4, f); - touchpad_calibrate(points); - fclose(f); - return err; -} - -void gui_calibration_save(lv_point_t * points){ - - FILE *f = fopen("/internal/gui/calibration", "w"); - if(!f){ - fs_err("Failed to write calibration file"); - return; - } - fwrite(points, sizeof(lv_point_t), 4, f); - fclose(f); - - touchpad_calibrate(points); - - gui_alert_create("Done.\nNow, let's make it clear.", - "This wallet doesn't store your private keys, " - "this means you need to use your recovery phrase " - "every time you want to sign a transaction.\n\n" - "It only stores some metadata like " - "master public keys, cosigners, wallets configuration etc.\n\n" - "You can wipe the device when you want - " - "it will zero all persistent memory.\n\n" - "You should be aware that this is an experimental project, " - "so better use it on testnet or in multisig " - "with some other hardware wallet." - , "Ok, I understand"); - -} - -/*************** init stuff ***************/ - -void gui_init(){ - lv_init(); - tft_init(); - touchpad_init(); - ms_tick.attach_us(onMillisecondTicker, 1000); - - /* define theme */ - lv_theme_t * th = lv_theme_material_init(210, NULL); - lv_theme_set_current(th); - - gui_styles_create(); - - // create screen - scr = lv_cont_create(NULL, NULL); - lv_disp_load_scr(scr); - - /* loading calibration file */ - int err = gui_calibration_load(); - if(err < 0){ - logit("gui", "calibration required"); - // if file is not found - if(err == -3){ - /* calibration screen and a callback when done */ - tpcal_create(gui_calibration_save); - } - } - -} - -void gui_update(){ - HAL_Delay(1); - lv_tick_inc(t); - lv_task_handler(); - t = 0; -} - -/****************** screens & logic **************/ - -static void show_recovery_screen(); - -static void process_init_screen(int val){ - switch(val){ - case 1: - action = GUI_GENERATE_KEY; - break; - case 2: - show_recovery_screen(); - break; - case 3: - action = GUI_LOAD_MNEMONIC; - break; - } -} - -static void process_mnemonic_screen(int val){ - switch(val){ - case 1: // go back - gui_show_init_screen(); - break; - case 7: // continue -> enter recovery phrase to check - show_recovery_screen(); - break; - default: // re-generate 12-word key - value = 12; - if(val > 1 && val < 7){ - value += (val-2)*3; - } - action = GUI_GENERATE_KEY; - break; - } -} - -static void show_networks_screen(){ - base = BASE_NETWORKS_SCREEN; - - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, "Pick the network to use:"); - - uint16_t y = 100; - int i = 0; - const char * net = network_names[i]; - while(strlen(net) > 0){ - obj = gui_button_create(scr, net, cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, i+1); - y+=100; - i++; - net = network_names[i]; - } -} - -static void show_xpubs_screen(){ - base = BASE_XPUBS_SCREEN; - - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, "Pick master key to show:"); - - uint16_t y = 100; - string msg; - msg = string("Single: ") + default_xpubs[0]; - obj = gui_button_create(scr, msg.c_str(), cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 1); - y+=100; - - msg = string("Multisig: ") + default_xpubs[1]; - obj = gui_button_create(scr, msg.c_str(), cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 2); - y+=100; - - // TODO: add "scan custom derivation" button - - obj = gui_button_create(scr, "Back to main menu", cb); - lv_obj_set_user_data(obj, BACK_TO_MAIN); -} - -static void cb_del_wallet(void * ptr){ - process_command(USER_CONFIRM); -} - -static void cb_cancel(void * ptr){ - process_command(USER_CANCEL); -} - -static void cb_del(lv_obj_t * obj, lv_event_t event){ - if(event == LV_EVENT_RELEASED){ - char title[100]; - sprintf(title, "Delete \"%s\"?", str); - gui_prompt_create(title, - "You are about to delete this wallet.\n" - "You won't be able to sign multisig " - "transactions with it until you re-import it.", - "Yes, delete", - cb_del_wallet, - "No, keep it", - cb_cancel); - } -} - -void gui_navigate_wallet(const char * name, uint32_t address, const char * bech32_addr, const char * base58_addr){ - base = BASE_ADDRESSES_SCREEN; - - string qrmsg = "bitcoin:"; - qrmsg += bech32_addr; - string msg = bech32_addr; - msg += "\nor base58:\n"; - msg += base58_addr; - - char title[200]; - sprintf(title, "Wallet \"%s\"\nAddress #%d", name, address+1); - - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, title); - lv_obj_t * qr = gui_qr_create(scr, LV_HOR_RES/2, qrmsg.c_str()); - lv_obj_set_y(qr, lv_obj_get_y(obj) + lv_obj_get_height(obj) + PADDING/2); - - obj = gui_title_create(scr, msg.c_str(), true); - lv_obj_set_y(obj, lv_obj_get_y(qr) + lv_obj_get_height(qr) + PADDING/2); - - obj = gui_button_create(scr, "Previous", cb); - uint16_t y = lv_obj_get_y(obj) - 170; - lv_obj_set_user_data(obj, address-1); - lv_obj_set_y(obj, y); - lv_obj_set_width(obj, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(obj, PADDING); - if(address == 0){ - lv_btn_set_state(obj, LV_BTN_STATE_INA); - } - - obj = gui_button_create(scr, "Next", cb); - lv_obj_set_user_data(obj, address+1); - lv_obj_set_y(obj, y); - lv_obj_set_width(obj, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(obj, LV_HOR_RES/2+PADDING/2); - - y += 85; - strcpy(str, name); // to access it from the callback - obj = gui_button_create(scr, "Delete wallet", cb_del); - lv_obj_set_y(obj, y); - - obj = gui_button_create(scr, "Back to main menu", cb); - lv_obj_set_user_data(obj, BACK_TO_MAIN); -} - - -static void show_reckless_screen(){ - base = BASE_RECKLESS; - - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, "Careful with that!"); - - uint16_t y = 100; - obj = gui_button_create(scr, "Save recovery phrase", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 1); - y+=100; - - obj = gui_button_create(scr, "Delete recovery phrase", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 2); - y+=100; - - obj = gui_button_create(scr, "Show recovery phrase", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 3); - y+=100; - - obj = gui_button_create(scr, "Back to main screen", cb); - lv_obj_set_user_data(obj, BACK_TO_MAIN); -} - -static void process_main_screen(int val){ - switch(val){ - case 1: // multisig - action = GUI_LIST_WALLETS; - logit("gui", "action set to list wallets"); - break; - case 2: // master keys - show_xpubs_screen(); - break; - case 3: - action = GUI_SIGN_PSBT; - logit("gui", "action set to Sign PSBT"); - break; - case 4: - action = GUI_VERIFY_ADDRESS; - logit("gui", "action set to Verify address"); - break; - case 5: // use another passwd - gui_get_password(); - break; - case 6: // pick network - show_networks_screen(); - break; - case 7: - show_reckless_screen(); - break; - } -} - -static int copy_string(){ - if(strlen(input_buffer) > sizeof(str)-1){ - show_err("Input is too large, try again."); - return 0; - } - strcpy(str, input_buffer); - return 1; -} - -static void process_command(int val){ - if(val == BACK_TO_MAIN){ - lv_async_call(back_to_main, NULL); - memset(input_buffer, 0, sizeof(input_buffer)); - return; - } - switch(base){ - case BASE_RECKLESS: - switch(val){ - case 1: - action = GUI_SAVE_MNEMONIC; - break; - case 2: - action = GUI_DELETE_MNEMONIC; - break; - case 3: - action = GUI_SHOW_MNEMONIC; - break; - } - break; - case BASE_INIT_SCREEN: - process_init_screen(val); - break; - case BASE_MNEMONIC_SCREEN: - process_mnemonic_screen(val); - break; - case BASE_RECOVERY_SCREEN: - if(copy_string()){ - action = GUI_PROCESS_MNEMONIC; - } - break; - case BASE_PASSWORD_SCREEN: - if(val == 1){ - action = GUI_BACK; - }else{ - if(copy_string()){ - action = GUI_PROCESS_PASSWORD; - } - } - break; - case BASE_MAIN_SCREEN: - process_main_screen(val); - break; - case BASE_NETWORKS_SCREEN: - value = val-1; - action = GUI_PROCESS_NETWORK; - break; - case BASE_XPUBS_SCREEN: - if(val >= 1 && val < 3){ - value = val-1; - strcpy(str, default_xpubs[value].c_str()); - action = GUI_SHOW_XPUB; - } - if(val == 3){ - gui_show_main_screen(); - } - break; - case BASE_PSBT_CONFIRMATION: - if(val == 1){ - action = GUI_PSBT_CONFIRMED; - printf("\r\nOk, signing transaction\r\n"); - }else{ - gui_show_main_screen(); - } - break; - case BASE_LIST_WALLETS: - if(val == GET_NEW_WALLET){ - action = GUI_NEW_WALLET; - }else{ - value = val; - action = GUI_SELECT_WALLET; - } - break; - case BASE_CONFIRM_NEW_WALLET: - if(val == 1){ - action = GUI_CONFIRM_NEW_WALLET; - }else{ - action = GUI_CANCEL_NEW_WALLET; - } - break; - case BASE_ADDRESSES_SCREEN: - switch(val){ - case USER_CONFIRM: - action = GUI_DELETE_WALLET; - break; - case USER_CANCEL: - break; - default: - value = val; - action = GUI_GET_WALLET_ADDRESS; - } - break; - default: - show_err("Undefined GUI behaviour"); - } - memset(input_buffer, 0, sizeof(input_buffer)); -} - -static void cb(lv_obj_t * obj, lv_event_t event){ - if(event == LV_EVENT_RELEASED){ - int v = lv_obj_get_user_data(obj); - process_command(v); - } -} - -void gui_confirm_new_wallet(const char * wallet_info){ - base = BASE_CONFIRM_NEW_WALLET; - lv_obj_clean(scr); - lv_obj_t * obj; - obj = gui_title_create(scr, "Add new wallet?"); - - obj = gui_title_create(scr, wallet_info, 1); - lv_obj_set_y(obj, 100); - - obj = gui_button_create(scr, "Confirm", cb); - lv_obj_set_user_data(obj, 1); - lv_obj_set_width(obj, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(obj, LV_HOR_RES/2+PADDING/2); - - obj = gui_button_create(scr, "Cancel", cb); - lv_obj_set_user_data(obj, 0); - lv_obj_set_width(obj, LV_HOR_RES/2-3*PADDING/2); - lv_obj_set_x(obj, PADDING); -} - -void gui_show_wallets(char ** wallets){ - base = BASE_LIST_WALLETS; - if(wallets==NULL){ - show_err("Weird... You don't have any wallets"); - return; - } - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, "Your wallets:"); - - uint16_t y = 100; - int i=0; - while(wallets[i]!=NULL && strlen(wallets[i]) > 0){ - obj = gui_button_create(scr, wallets[i], cb); - lv_obj_set_user_data(obj, i); - lv_obj_set_y(obj, y); - i++; - y += 100; - } - - obj = gui_button_create(scr, "Add new wallet (scan)", cb); - lv_obj_set_user_data(obj, GET_NEW_WALLET); - lv_obj_set_y(obj, lv_obj_get_y(obj)-100); - - obj = gui_button_create(scr, "Back to main menu", cb); - lv_obj_set_user_data(obj, BACK_TO_MAIN); -} - -void gui_show_signed_psbt(const char * output){ - gui_show_main_screen(); - gui_qr_alert_create("Transaction is signed!", output, "Scan it with your wallet", "Back to main screen"); -} - -void gui_show_psbt(const char * wallet_name, uint64_t out_amount, uint64_t change_amount, uint64_t fee, uint8_t num_outputs, txout_t * outputs){ - base = BASE_PSBT_CONFIRMATION; - - lv_obj_clean(scr); - - lv_obj_t * obj; - char msg[200]; - sprintf(msg, "Spending %llu satoshi\nfrom %s", out_amount+fee, wallet_name); - obj = gui_title_create(scr, msg); - - uint16_t y = 100; - sprintf(msg, "Number of outputs: %u\n" - "Fee: %llu satoshi\n" - "Outputs:\n" - , num_outputs - , fee - ); - obj = gui_title_create(scr, msg, true); - lv_obj_set_y(obj, y); - y+=100; - for(int i=0; i 0){ - input_buffer[strlen(input_buffer)-1] = 0; - } - }else if(strcmp(txt, "Back")==0){ - memset(input_buffer, 0, sizeof(input_buffer)); - lv_async_call(back_to_init, NULL); - }else if(strcmp(txt, "Done")==0){ - process_command(0); - }else{ - input_buffer[strlen(input_buffer)] = tolower(txt[0]); - } - gui_show_mnemonic(tbl, input_buffer, true); - gui_check_mnemonic(input_buffer, obj); - } -} - -static void show_recovery_screen(){ - base = BASE_RECOVERY_SCREEN; - - lv_obj_clean(scr); - - lv_obj_t * obj; - obj = gui_title_create(scr, "Enter your recovery phrase:"); - - tbl = gui_mnemonic_table_create(scr, input_buffer); - - // keyboard - lv_obj_t * kb = lv_kb_create(scr, NULL); - lv_obj_set_y(kb, LV_VER_RES*2/3); - lv_obj_set_height(kb, LV_VER_RES/3); - lv_kb_set_map(kb, keymap); - - static lv_style_t kb_dis_style; - lv_style_copy(&kb_dis_style, &lv_style_btn_ina); - kb_dis_style.body.main_color = LV_COLOR_MAKE(0xe0,0xe0,0xe0); - kb_dis_style.body.grad_color = LV_COLOR_MAKE(0xe0,0xe0,0xe0); - kb_dis_style.body.radius = 0; - kb_dis_style.body.border.opa = 30; - lv_kb_set_style(kb, LV_KB_STYLE_BTN_INA, &kb_dis_style); - - lv_obj_set_event_cb(kb, cb_keyboard); - lv_btnm_set_btn_ctrl(kb, 29, LV_BTNM_CTRL_INACTIVE); - lv_btnm_set_btn_ctrl(kb, 28, LV_BTNM_CTRL_INACTIVE); -} - -/********************** password screen ********************/ - -// keyboard config -static const char * pkeymapCap[] = { - "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","<-","\n", - "Clear"," ","Done",""}; -static const char * pkeymapLow[] = { - "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","<-","\n", - "Clear"," ","Done",""}; -static const char * pkeymapNum[] = { - "1","2","3","4","5","6","7","8","9","0","\n", - "aA","@","#","$","_","&","-","+","(",")","/","\n", - "[","]","*","\"","'",":",";","!","?","\\","<-","\n", - "Clear"," ","Done",""}; - -// key press callback -static void cb_pkeyboard(lv_obj_t * obj, lv_event_t event){ - if(event == LV_EVENT_CLICKED){ - const char * txt = lv_btnm_get_active_btn_text(obj); - if(strcmp(txt, "<-")==0){ - if(strlen(input_buffer) > 0){ - input_buffer[strlen(input_buffer)-1] = 0; - } - }else if(strcmp(txt, "Clear")==0){ - memset(input_buffer, 0, sizeof(input_buffer)); - }else if(strcmp(txt, "Done")==0){ - process_command(0); - }else if(strcmp(txt,"^ ")==0){ - lv_kb_set_map(obj, pkeymapCap); - }else if(strcmp(txt,"^ ")==0){ - lv_kb_set_map(obj, pkeymapLow); - }else if(strcmp(txt,"#@")==0){ - lv_kb_set_map(obj, pkeymapNum); - }else if(strcmp(txt,"aA")==0){ - lv_kb_set_map(obj, pkeymapLow); - }else{ - input_buffer[strlen(input_buffer)] = txt[0]; - } - lv_obj_t * ta = lv_kb_get_ta(obj); - lv_ta_set_text(ta, input_buffer); - } -} - -void gui_get_password(){ - base = BASE_PASSWORD_SCREEN; - - lv_obj_clean(scr); - - gui_title_create(scr, "Enter your password (optional)"); - - // keyboard - lv_obj_t * kb = lv_kb_create(scr, NULL); - lv_obj_set_y(kb, LV_VER_RES*2/3); - lv_obj_set_height(kb, LV_VER_RES*1/3); - lv_kb_set_map(kb, pkeymapLow); - lv_obj_set_event_cb(kb, cb_pkeyboard); - - /* Create a text area. The keyboard will write here */ - lv_obj_t * ta = lv_ta_create(scr, NULL); - lv_obj_set_size(ta, LV_HOR_RES-2*PADDING, 150); - lv_ta_set_text(ta, ""); - // lv_ta_set_pwd_mode(ta, true); // password mode... tricky - lv_obj_align(ta, NULL, LV_ALIGN_IN_TOP_MID, 0, 200); - lv_obj_set_style(ta, &lv_style_transp); - - /* Assign the text area to the keyboard */ - lv_kb_set_ta(kb, ta); -} - -/********************** main screen ********************/ - -void gui_show_main_screen(){ - base = BASE_MAIN_SCREEN; - - lv_obj_clean(scr); - lv_obj_t * obj; - - obj = gui_title_create(scr, "Select an option below"); - - uint16_t y = 100; - - obj = gui_button_create(scr, "Wallets", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 1); - y+=100; - obj = gui_button_create(scr, "Master keys", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 2); - y+=100; - obj = gui_button_create(scr, "Sign transaction", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 3); - y+=100; - obj = gui_button_create(scr, "Verify address", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 4); - y+=100; - obj = gui_button_create(scr, "Use another password", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 5); - y+=100; - obj = gui_button_create(scr, "Switch network", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 6); - y+=100; - obj = gui_button_create(scr, "# Reckless", cb); - lv_obj_set_y(obj, y); - lv_obj_set_user_data(obj, 7); - y+=100; - - // TODO: add GUI_SECURE_SHUTDOWN - // TODO: add Advanced menu: - // - Reckless save mnemonic - // - SD card support -} - -static void back_to_main(void * ptr){ - gui_show_main_screen(); -} - -static void back_to_init(void * ptr){ - gui_show_init_screen(); -} - -void gui_set_default_xpubs(const char * single, const char * multisig){ - default_xpubs[0] = single; - default_xpubs[1] = multisig; -} - -void gui_show_addresses(const char * derivation, const char * bech32_addr, const char * base58_addr){ - string qrmsg = "bitcoin:"; - qrmsg += bech32_addr; - string msg = "bech32: "; - msg += bech32_addr; - msg += "\nbase58: "; - msg += base58_addr; - - gui_qr_alert_create("Your bitcoin address", qrmsg.c_str(), msg.c_str(), "Ok"); -} - -void gui_calibrate(){ - lv_point_t points[4]; - points[0].x = 0; - points[0].y = 0; - points[1].x = TFT_HOR_RES; - points[1].y = 0; - points[2].x = TFT_HOR_RES; - points[2].y = TFT_VER_RES; - points[3].x = 0; - points[3].y = TFT_VER_RES; - touchpad_calibrate(points); - tpcal_create(gui_calibration_save); -} diff --git a/components/gui/gui.h b/components/gui/gui.h deleted file mode 100644 index d263d84..0000000 --- a/components/gui/gui.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef __GUI_H__ -#define __GUI_H__ - -#include -#include "./alert/alert.h" - -/** possible GUI actions (main needs to handle them) */ -#define GUI_NO_ACTION 0 -#define GUI_BACK 1 -#define GUI_GENERATE_KEY 2 -#define GUI_SECURE_SHUTDOWN 3 -#define GUI_PROCESS_MNEMONIC 4 -#define GUI_PROCESS_PASSWORD 5 -#define GUI_PROCESS_NETWORK 6 -#define GUI_SHOW_XPUB 7 -#define GUI_VERIFY_ADDRESS 8 -#define GUI_SIGN_PSBT 9 -#define GUI_PSBT_CONFIRMED 10 -// reckless -#define GUI_SHOW_MNEMONIC 11 -#define GUI_SAVE_MNEMONIC 12 -#define GUI_DELETE_MNEMONIC 13 -#define GUI_LOAD_MNEMONIC 14 - -#define GUI_LIST_WALLETS 15 -#define GUI_SELECT_WALLET 16 -#define GUI_GET_WALLET_ADDRESS 17 -#define GUI_NEW_WALLET 18 -#define GUI_CONFIRM_NEW_WALLET 19 -#define GUI_CANCEL_NEW_WALLET 20 -#define GUI_DELETE_WALLET 21 - -/** structure to display output */ -typedef struct _txout_t { - char * address; - uint64_t amount; - uint8_t is_change; - char * warning; -} txout_t; - -void gui_init(); -void gui_start(); -void gui_update(); -int gui_get_action(); -void gui_clear_action(); -int gui_get_value(); -char * gui_get_str(); // not const because we will wipe it from main - -void gui_set_available_networks(const char * names[]); -void gui_set_network(uint8_t index); -void gui_set_default_xpubs(const char * single, const char * multisig); - -void gui_show_init_screen(); -void gui_show_mnemonic(const char * mnemonic); -void gui_get_password(); -void gui_show_main_screen(); -void gui_show_xpub(const char * fingerprint, const char * derivation, const char * xpub); -void gui_show_addresses(const char * derivation, const char * segwit_addr, const char * base58_addr); -void gui_show_psbt(const char * wallet_name, uint64_t out_amount, uint64_t change_amount, uint64_t fee, uint8_t num_outputs, txout_t * outputs); -void gui_show_signed_psbt(const char * output); -void gui_show_reckless_mnemonic(const char * mnemonic); -void gui_show_wallets(char ** wallets); // TODO: should be const -void gui_navigate_wallet(const char * name, uint32_t address, const char * bech32_addr, const char * base58_addr); -void gui_confirm_new_wallet(const char * wallet_info); - -void gui_calibrate(); - -#endif \ No newline at end of file diff --git a/components/gui/gui_common.cpp b/components/gui/gui_common.cpp deleted file mode 100644 index d1c989c..0000000 --- a/components/gui/gui_common.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "gui_common.h" -#include "qrcode.h" -#include - -lv_obj_t * gui_title_create(lv_obj_t * scr, const char * title, bool no_style){ - if(scr == NULL){ - scr = lv_scr_act(); - } - lv_obj_t * obj = lv_label_create(scr, NULL); - if(!no_style){ - lv_obj_set_style(obj, &title_style); - } - lv_label_set_text(obj, title); - lv_label_set_long_mode(obj, LV_LABEL_LONG_BREAK); - lv_obj_set_width(obj, LV_HOR_RES-2*PADDING); - lv_obj_set_x(obj, PADDING); - lv_label_set_align(obj, LV_LABEL_ALIGN_CENTER); - lv_obj_set_y(obj, PADDING); - return obj; -} - -lv_obj_t * gui_button_create(lv_obj_t * scr, const char * text, void (*callback)(lv_obj_t * btn, lv_event_t event)){ - if(scr == NULL){ - scr = lv_scr_act(); - } - // button - lv_obj_t * obj = lv_btn_create(scr, NULL); - lv_obj_set_event_cb(obj, callback); - lv_obj_set_width(obj, LV_HOR_RES-2*PADDING); - lv_obj_set_height(obj, BTN_HEIGHT); - // button label - lv_obj_t * lbl = lv_label_create(obj, NULL); - lv_label_set_text(lbl, text); - lv_label_set_align(lbl, LV_LABEL_ALIGN_CENTER); - // alignment - lv_obj_align(obj, NULL, LV_ALIGN_IN_TOP_MID, 0, 0); - lv_obj_set_y(obj, 700); - return obj; -} - -lv_obj_t * gui_qr_create(lv_obj_t * scr, uint16_t width, const char * text){ - static lv_color_t * cbuf; - if(cbuf != NULL){ - free(cbuf); - cbuf = NULL; - } - if(scr == NULL){ - scr = lv_scr_act(); - } - lv_obj_t * obj = lv_canvas_create(scr, NULL); - cbuf = (lv_color_t*)calloc(LV_CANVAS_BUF_SIZE_INDEXED_1BIT(width, width), sizeof(lv_color_t)); - lv_obj_set_size(obj, width, width); - - lv_canvas_set_buffer(obj, cbuf, width, width, LV_IMG_CF_INDEXED_1BIT); - lv_canvas_set_palette(obj, 0, LV_COLOR_TRANSP); - lv_canvas_set_palette(obj, 1, LV_COLOR_BLACK); - - lv_color_t c0; - lv_color_t c1; - c0.full = 0; - c1.full = 1; - - /*Transparent background*/ - lv_canvas_fill_bg(obj, c0); - - int qrSize = 10; - int sizes[] = { 14, 26, 42, 62, 84, 106, 122, 152, 180, 213, 251, 287, 331, 362, 412, 480, 504, 560, 624, 666, 711, 779, 857, 911, 997, 1059, 1125, 1190 }; - int len = strlen(text); - for(int i=0; i len){ - qrSize = i+1; - break; - } - } - - QRCode qrcode; - uint8_t * qrcodeData = (uint8_t *)calloc(qrcode_getBufferSize(qrSize), sizeof(uint8_t)); - qrcode_initText(&qrcode, qrcodeData, qrSize, 1, text); - - if(qrcode.size <= width){ - uint16_t scale = width/qrcode.size; - uint16_t padding = (width % qrcode.size)/2; - for (uint16_t y = 0; y < qrcode.size; y++) { - for (uint16_t x = 0; x < qrcode.size; x++) { - if(qrcode_getModule(&qrcode, x, y)){ - for(uint16_t xx = 0; xx - -#include "stm32f4xx.h" -#include "stm32469i_discovery.h" -#include "stm32469i_discovery_lcd.h" -#include "stm32469i_discovery_sdram.h" -/********************* - * DEFINES - *********************/ - -#if TFT_EXT_FB != 0 - -#define SDRAM_BANK_ADDR SDRAM_DEVICE_ADDR /* Set in stm32469i_discovery_sdram.h (0xC0000000) */ -#define SDRAM_TIMEOUT ((uint32_t)0xFFFF) - -#endif - -/* DMA Stream parameters definitions. You can modify these parameters to select - a different DMA Stream and/or channel. - But note that only DMA2 Streams are capable of Memory to Memory transfers. */ -#define DMA_STREAM DMA2_Stream0 -#define DMA_CHANNEL DMA_CHANNEL_0 -#define DMA_STREAM_IRQ DMA2_Stream0_IRQn -#define DMA_STREAM_IRQHANDLER DMA2_Stream0_IRQHandler - -/********************** - * TYPEDEFS - **********************/ -static DSI_VidCfgTypeDef hdsivideo_handle; - -/********************** - * STATIC PROTOTYPES - **********************/ - -/*These 3 functions are needed by LittlevGL*/ -static void tft_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p); -#if TFT_USE_GPU != 0 -static void gpu_mem_blend(lv_disp_drv_t * drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa); -static void gpu_mem_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width, - const lv_area_t * fill_area, lv_color_t color); -#endif - -/*LCD*/ -static void LCD_Config(void); -void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc); -void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc); -#if TFT_USE_GPU != 0 -static void DMA2D_Config(void); -#endif - -/*SD RAM*/ -#if TFT_EXT_FB != 0 -static void SDRAM_Init(void); -static void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command); -#endif - -/*DMA to flush to frame buffer*/ -static void DMA_Config(void); -static void DMA_TransferComplete(DMA_HandleTypeDef *han); -static void DMA_TransferError(DMA_HandleTypeDef *han); - -static void Error_Handler(void); -/********************** - * STATIC VARIABLES - **********************/ - - -// DSI_HandleTypeDef hdsi_eval; -// LTDC_HandleTypeDef hltdc_eval; - -#if TFT_USE_GPU != 0 -static DMA2D_HandleTypeDef Dma2dHandle; -#endif - -#if TFT_EXT_FB != 0 -SDRAM_HandleTypeDef hsdram; -FMC_SDRAM_TimingTypeDef SDRAM_Timing; -FMC_SDRAM_CommandTypeDef command; -static __IO uint16_t * my_fb = (__IO uint16_t*) (SDRAM_BANK_ADDR); -#else -static uint16_t my_fb[TFT_HOR_RES * TFT_VER_RES]; -#endif - - -DMA_HandleTypeDef DmaHandle; -static lv_disp_drv_t disp_drv; -static int32_t x1_flush; -static int32_t y1_flush; -static int32_t x2_flush; -static int32_t y2_fill; -static int32_t y_fill_act; -static const lv_color_t * buf_to_flush; - -/********************** - * MACROS - **********************/ - -/********************** - * GLOBAL FUNCTIONS - **********************/ - -/** - * Initialize your display here - */ -void tft_init(void) -{ - static lv_color_t disp_buf1[LV_VER_RES_MAX * 30]; - static lv_disp_buf_t buf; - lv_disp_buf_init(&buf, disp_buf1, NULL, LV_HOR_RES_MAX * 30); - - lv_disp_drv_init(&disp_drv); - -#if TFT_EXT_FB != 0 - SDRAM_Init(); -#endif - LCD_Config(); - DMA_Config(); - - disp_drv.buffer = &buf; - disp_drv.flush_cb = tft_flush; -#if TFT_USE_GPU != 0 - DMA2D_Config(); - disp_drv.gpu_blend_cb = gpu_mem_blend; - disp_drv.gpu_fill_cb = gpu_mem_fill; -#endif - lv_disp_drv_register(&disp_drv); -} - -/** - * Put a color map to a rectangular area - * @param x1 left coordinate of the rectangle - * @param x2 right coordinate of the rectangle - * @param y1 top coordinate of the rectangle - * @param y2 bottom coordinate of the rectangle - * @param color_p pointer to an array of colors - */ -static void tft_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p) -{ - /*Return if the area is out the screen*/ - if(area->x2 < 0) return; - if(area->y2 < 0) return; - if(area->x1 > TFT_HOR_RES - 1) return; - if(area->y1 > TFT_VER_RES - 1) return; - - /*Truncate the area to the screen*/ - int32_t act_x1 = area->x1 < 0 ? 0 : area->x1; - int32_t act_y1 = area->y1 < 0 ? 0 : area->y1; - int32_t act_x2 = area->x2 > TFT_HOR_RES - 1 ? TFT_HOR_RES - 1 : area->x2; - int32_t act_y2 = area->y2 > TFT_VER_RES - 1 ? TFT_VER_RES - 1 : area->y2; - - x1_flush = act_x1; - y1_flush = act_y1; - x2_flush = act_x2; - y2_fill = act_y2; - y_fill_act = act_y1; - buf_to_flush = color_p; - - - /*##-7- Start the DMA transfer using the interrupt mode #*/ - /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */ - /* Enable All the DMA interrupts */ - HAL_StatusTypeDef err; - err = HAL_DMA_Start_IT(&DmaHandle,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * TFT_HOR_RES + x1_flush], - (x2_flush - x1_flush + 1)); - if(err != HAL_OK) - { - while(1); /*Halt on error*/ - } -} - -#if TFT_USE_GPU != 0 - -/** - * Copy pixels to destination memory using opacity - * @param dest a memory address. Copy 'src' here. - * @param src pointer to pixel map. Copy it to 'dest'. - * @param length number of pixels in 'src' - * @param opa opacity (0, OPA_TRANSP: transparent ... 255, OPA_COVER, fully cover) - */ -static void gpu_mem_blend(lv_disp_drv_t * drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa) -{ - /*Wait for the previous operation*/ - HAL_DMA2D_PollForTransfer(&Dma2dHandle, 100); - Dma2dHandle.Init.Mode = DMA2D_M2M_BLEND; - /* DMA2D Initialization */ - if(HAL_DMA2D_Init(&Dma2dHandle) != HAL_OK) - { - /* Initialization Error */ - while(1); - } - - Dma2dHandle.LayerCfg[1].InputAlpha = opa; - HAL_DMA2D_ConfigLayer(&Dma2dHandle, 1); - HAL_DMA2D_BlendingStart(&Dma2dHandle, (uint32_t) src, (uint32_t) dest, (uint32_t)dest, length, 1); -} - -static void gpu_mem_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width, - const lv_area_t * fill_area, lv_color_t color) -{ - /*Wait for the previous operation*/ - HAL_DMA2D_PollForTransfer(&Dma2dHandle, 100); - - Dma2dHandle.Init.Mode = DMA2D_R2M; - /* DMA2D Initialization */ - if(HAL_DMA2D_Init(&Dma2dHandle) != HAL_OK) - { - /* Initialization Error */ - while(1); - } - - Dma2dHandle.LayerCfg[1].InputAlpha = 0xff; - HAL_DMA2D_ConfigLayer(&Dma2dHandle, 1); - - lv_color_t * dest_buf_ofs = dest_buf; - - dest_buf_ofs += dest_width * fill_area->y1; - dest_buf_ofs += fill_area->x1; - lv_coord_t area_w = lv_area_get_width(fill_area); - - uint32_t i; - for(i = fill_area->y1; i <= fill_area->y2; i++) { - /*Wait for the previous operation*/ - HAL_DMA2D_PollForTransfer(&Dma2dHandle, 100); - HAL_DMA2D_BlendingStart(&Dma2dHandle, (uint32_t) lv_color_to32(color), (uint32_t) dest_buf_ofs, (uint32_t)dest_buf_ofs, area_w, 1); - dest_buf_ofs += dest_width; - } -} - -#endif - -static void LCD_Config(void) -{ - LTDC_LayerCfgTypeDef pLayerCfg; - BSP_LCD_InitEx(LCD_ORIENTATION_PORTRAIT); - BSP_LCD_LayerDefaultInit(0, (uint32_t)my_fb); -} - -#if TFT_USE_GPU != 0 -/** - * @brief DMA2D Transfer completed callback - * @param hdma2d: DMA2D handle. - * @note This example shows a simple way to report end of DMA2D transfer, and - * you can add your own implementation. - * @retval None - */ -static void DMA2D_TransferComplete(DMA2D_HandleTypeDef *hdma2d) -{ - -} - -/** - * @brief DMA2D error callbacks - * @param hdma2d: DMA2D handle - * @note This example shows a simple way to report DMA2D transfer error, and you can - * add your own implementation. - * @retval None - */ -static void DMA2D_TransferError(DMA2D_HandleTypeDef *hdma2d) -{ - -} - -/** - * @brief DMA2D configuration. - * @note This function Configure the DMA2D peripheral : - * 1) Configure the Transfer mode as memory to memory with blending. - * 2) Configure the output color mode as RGB565 pixel format. - * 3) Configure the foreground - * - first image loaded from FLASH memory - * - constant alpha value (decreased to see the background) - * - color mode as RGB565 pixel format - * 4) Configure the background - * - second image loaded from FLASH memory - * - color mode as RGB565 pixel format - * @retval None - */ -static void DMA2D_Config(void) -{ - /* Configure the DMA2D Mode, Color Mode and output offset */ - Dma2dHandle.Init.Mode = DMA2D_M2M_BLEND; - Dma2dHandle.Init.ColorMode = DMA2D_RGB565 ; - Dma2dHandle.Init.OutputOffset = 0x0; - - /* DMA2D Callbacks Configuration */ - Dma2dHandle.XferCpltCallback = DMA2D_TransferComplete; - Dma2dHandle.XferErrorCallback = DMA2D_TransferError; - - /* Foreground Configuration */ - Dma2dHandle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA; - Dma2dHandle.LayerCfg[1].InputAlpha = 0xFF; - Dma2dHandle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; - Dma2dHandle.LayerCfg[1].InputOffset = 0x0; - - /* Background Configuration */ - Dma2dHandle.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; - Dma2dHandle.LayerCfg[0].InputAlpha = 0xFF; - Dma2dHandle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565; - Dma2dHandle.LayerCfg[0].InputOffset = 0x0; - - Dma2dHandle.Instance = DMA2D; - - /* DMA2D Initialization */ - if(HAL_DMA2D_Init(&Dma2dHandle) != HAL_OK) - { - /* Initialization Error */ - Error_Handler(); - } - - HAL_DMA2D_ConfigLayer(&Dma2dHandle, 0); - HAL_DMA2D_ConfigLayer(&Dma2dHandle, 1); -} -#endif - - -/** - * @brief This function handles LTDC global interrupt request. - * @param None - * @retval None - */ -// void LTDC_IRQHandler(void) -// { -// // HAL_LTDC_IRQHandler(&hltdc_eval); -// } - -/** - * @brief LTDC MSP Initialization - * This function configures the hardware resources used in this example: - * - Peripheral's clock enable - * - Peripheral's GPIO Configuration - * @param hltdc: LTDC handle pointer - * @retval None - */ -void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc) -{ - GPIO_InitTypeDef GPIO_Init_Structure; - - /*##-1- Enable peripherals and GPIO Clocks #################################*/ - /* Enable the LTDC Clock */ - __HAL_RCC_LTDC_CLK_ENABLE(); - -#if 0 - /* Enable GPIOs clock */ - __HAL_RCC_GPIOA_CLK_ENABLE(); - __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOC_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - - /*##-2- Configure peripheral GPIO ##########################################*/ - - /* LTDC pins configuraiton: PA3 -- 12 */ - GPIO_Init_Structure.Pin = GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_6 | - GPIO_PIN_11 | GPIO_PIN_12; - GPIO_Init_Structure.Mode = GPIO_MODE_AF_PP; - GPIO_Init_Structure.Pull = GPIO_NOPULL; - GPIO_Init_Structure.Speed = GPIO_SPEED_FAST; - GPIO_Init_Structure.Alternate= GPIO_AF14_LTDC; - HAL_GPIO_Init(GPIOA, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PB8 -- 11 */ - GPIO_Init_Structure.Pin = GPIO_PIN_8 | \ - GPIO_PIN_9 ;//| GPIO_PIN_10 | GPIO_PIN_11; - HAL_GPIO_Init(GPIOB, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PC6 -- 10 */ - GPIO_Init_Structure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10; - HAL_GPIO_Init(GPIOC, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PD3 -- 6 */ - GPIO_Init_Structure.Pin = GPIO_PIN_3 | GPIO_PIN_6; - HAL_GPIO_Init(GPIOD, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PF10 */ - GPIO_Init_Structure.Pin = GPIO_PIN_10; - HAL_GPIO_Init(GPIOF, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PG6 -- 11 */ - GPIO_Init_Structure.Pin = GPIO_PIN_7 | \ - GPIO_PIN_11; - HAL_GPIO_Init(GPIOG, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PB0 -- 1 */ - GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1; - GPIO_Init_Structure.Alternate = GPIO_AF9_LTDC; - HAL_GPIO_Init(GPIOB, &GPIO_Init_Structure); - - /* LTDC pins configuraiton: PG10 -- 12 */ - GPIO_Init_Structure.Pin = GPIO_PIN_10 | GPIO_PIN_12; - HAL_GPIO_Init(GPIOG, &GPIO_Init_Structure); -#endif - /* Set LTDC Interrupt to the lowest priority */ - HAL_NVIC_SetPriority(LTDC_IRQn, 0x5, 0); - - /* Enable LTDC Interrupt */ - HAL_NVIC_EnableIRQ(LTDC_IRQn); -} - -/** - * @brief LTDC MSP De-Initialization - * This function frees the hardware resources used in this example: - * - Disable the Peripheral's clock - * @param hltdc: LTDC handle pointer - * @retval None - */ -void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc) -{ - - /*##-1- Reset peripherals ##################################################*/ - /* Enable LTDC reset state */ - __HAL_RCC_LTDC_FORCE_RESET(); - - /* Release LTDC from reset state */ - __HAL_RCC_LTDC_RELEASE_RESET(); -} -/** - * @brief DMA2D MSP Initialization - * This function configures the hardware resources used in this example: - * - Peripheral's clock enable - * - Peripheral's GPIO Configuration - * @param hdma2d: DMA2D handle pointer - * @retval None - */ -void HAL_DMA2D_MspInit(DMA2D_HandleTypeDef *hdma2d) -{ - /*##-1- Enable peripherals and GPIO Clocks #################################*/ - __HAL_RCC_DMA2D_CLK_ENABLE(); - - /*##-2- NVIC configuration ################################################*/ - /* NVIC configuration for DMA2D transfer complete interrupt */ - HAL_NVIC_SetPriority(DMA2D_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA2D_IRQn); -} - - -#if TFT_EXT_FB != 0 - -static void SDRAM_Init(void) -{ - /* SDRAM device configuration */ - hsdram.Instance = FMC_SDRAM_DEVICE; - - /* Timing configuration for 90 MHz of SDRAM clock frequency (180MHz/2) */ - /* TMRD: 2 Clock cycles */ - SDRAM_Timing.LoadToActiveDelay = 2; - /* TXSR: min=70ns (6x11.90ns) */ - SDRAM_Timing.ExitSelfRefreshDelay = 7; - /* TRAS: min=42ns (4x11.90ns) max=120k (ns) */ - SDRAM_Timing.SelfRefreshTime = 4; - /* TRC: min=63 (6x11.90ns) */ - SDRAM_Timing.RowCycleDelay = 7; - /* TWR: 2 Clock cycles */ - SDRAM_Timing.WriteRecoveryTime = 2; - /* TRP: 15ns => 2x11.90ns */ - SDRAM_Timing.RPDelay = 2; - /* TRCD: 15ns => 2x11.90ns */ - SDRAM_Timing.RCDDelay = 2; - - hsdram.Init.SDBank = FMC_SDRAM_BANK1; - hsdram.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; - hsdram.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; - hsdram.Init.MemoryDataWidth = SDRAM_MEMORY_WIDTH; - hsdram.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; - hsdram.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3; - hsdram.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; - hsdram.Init.SDClockPeriod = SDCLOCK_PERIOD; - hsdram.Init.ReadBurst = FMC_SDRAM_RBURST_DISABLE; - hsdram.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; - - /* Initialize the SDRAM controller */ - if(HAL_SDRAM_Init(&hsdram, &SDRAM_Timing) != HAL_OK) - { - /* Initialization Error */ - Error_Handler(); - } - - /* Program the SDRAM external device */ - SDRAM_Initialization_Sequence(&hsdram, &command); -} - -/** - * @brief Perform the SDRAM external memory initialization sequence - * @param hsdram: SDRAM handle - * @param Command: Pointer to SDRAM command structure - * @retval None - */ -static void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command) -{ - __IO uint32_t tmpmrd =0; - /* Step 3: Configure a clock configuration enable command */ - Command->CommandMode = FMC_SDRAM_CMD_CLK_ENABLE; - Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; - Command->AutoRefreshNumber = 1; - Command->ModeRegisterDefinition = 0; - - /* Send the command */ - HAL_SDRAM_SendCommand(hsdram, Command, 0x1000); - - /* Step 4: Insert 100 ms delay */ - HAL_Delay(100); - - /* Step 5: Configure a PALL (precharge all) command */ - Command->CommandMode = FMC_SDRAM_CMD_PALL; - Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; - Command->AutoRefreshNumber = 1; - Command->ModeRegisterDefinition = 0; - - /* Send the command */ - HAL_SDRAM_SendCommand(hsdram, Command, 0x1000); - - /* Step 6 : Configure a Auto-Refresh command */ - Command->CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE; - Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; - Command->AutoRefreshNumber = 8; - Command->ModeRegisterDefinition = 0; - - /* Send the command */ - HAL_SDRAM_SendCommand(hsdram, Command, 0x1000); - - /* Step 7: Program the external memory mode register */ - tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | - SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | - SDRAM_MODEREG_CAS_LATENCY_3 | - SDRAM_MODEREG_OPERATING_MODE_STANDARD | - SDRAM_MODEREG_WRITEBURST_MODE_SINGLE; - - Command->CommandMode = FMC_SDRAM_CMD_LOAD_MODE; - Command->CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1; - Command->AutoRefreshNumber = 1; - Command->ModeRegisterDefinition = tmpmrd; - - /* Send the command */ - HAL_SDRAM_SendCommand(hsdram, Command, 0x1000); - - /* Step 8: Set the refresh rate counter */ - /* (15.62 us x Freq) - 20 */ - /* Set the device refresh counter */ - HAL_SDRAM_ProgramRefreshRate(hsdram, REFRESH_COUNT); -} - - -/** - * @brief SDRAM MSP Initialization - * This function configures the hardware resources used in this example: - * - Peripheral's clock enable - * - Peripheral's GPIO Configuration - * @param hsdram: SDRAM handle pointer - * @retval None - */ -void HAL_SDRAM_MspInit(SDRAM_HandleTypeDef *hsdram) -{ -#if 0 - GPIO_InitTypeDef gpio_init_structure; - - /*##-1- Enable peripherals and GPIO Clocks #################################*/ - /* Enable GPIO clocks */ - __HAL_RCC_GPIOC_CLK_ENABLE(); - __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOE_CLK_ENABLE(); - __HAL_RCC_GPIOF_CLK_ENABLE(); - __HAL_RCC_GPIOG_CLK_ENABLE(); - __HAL_RCC_GPIOH_CLK_ENABLE(); - __HAL_RCC_GPIOI_CLK_ENABLE(); - /* Enable FMC clock */ - __HAL_RCC_FMC_CLK_ENABLE(); - - /*##-2- Configure peripheral GPIO ##########################################*/ - /*-- GPIOs Configuration -----------------------------------------------------*/ - - /* Common GPIO configuration */ - gpio_init_structure.Mode = GPIO_MODE_AF_PP; - gpio_init_structure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; - gpio_init_structure.Pull = GPIO_PULLUP; - gpio_init_structure.Alternate = GPIO_AF12_FMC; - - /* GPIOC configuration : PC0 is SDNWE */ - gpio_init_structure.Pin = GPIO_PIN_0; - HAL_GPIO_Init(GPIOC, &gpio_init_structure); - - /* GPIOD configuration */ - gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8| GPIO_PIN_9 | GPIO_PIN_10 |\ - GPIO_PIN_14 | GPIO_PIN_15; - - - HAL_GPIO_Init(GPIOD, &gpio_init_structure); - - /* GPIOE configuration */ - gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7| GPIO_PIN_8 | GPIO_PIN_9 |\ - GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |\ - GPIO_PIN_15; - - HAL_GPIO_Init(GPIOE, &gpio_init_structure); - - /* GPIOF configuration */ - gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3 | GPIO_PIN_4 |\ - GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |\ - GPIO_PIN_15; - - HAL_GPIO_Init(GPIOF, &gpio_init_structure); - - /* GPIOG configuration */ - gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4| GPIO_PIN_5 | GPIO_PIN_8 |\ - GPIO_PIN_15; - HAL_GPIO_Init(GPIOG, &gpio_init_structure); - - /* GPIOH configuration */ - gpio_init_structure.Pin = GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8 | GPIO_PIN_9 |\ - GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |\ - GPIO_PIN_15; - HAL_GPIO_Init(GPIOH, &gpio_init_structure); - - /* GPIOI configuration */ - gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 |\ - GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 | GPIO_PIN_10; - HAL_GPIO_Init(GPIOI, &gpio_init_structure); -#endif -} - -/** - * @brief SDRAM MSP De-Initialization - * This function frees the hardware resources used in this example: - * - Disable the Peripheral's clock - * - Revert GPIO configuration to their default state - * @param hsdram: SDRAM handle pointer - * @retval None - */ -void HAL_SDRAM_MspDeInit(SDRAM_HandleTypeDef *hsdram) -{ -#if 0 - - HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0); - - HAL_GPIO_DeInit(GPIOD, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 |\ - GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 |\ - GPIO_PIN_15); - - HAL_GPIO_DeInit(GPIOE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7 |\ - GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |\ - GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 |\ - GPIO_PIN_14 | GPIO_PIN_15); - - HAL_GPIO_DeInit(GPIOF, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |\ - GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 |\ - GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 |\ - GPIO_PIN_14 | GPIO_PIN_15); - - HAL_GPIO_DeInit(GPIOG, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 |\ - GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15); - - HAL_GPIO_DeInit(GPIOH, GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8 |\ - GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 |\ - GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 |\ - GPIO_PIN_15); - - HAL_GPIO_DeInit(GPIOI, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |\ - GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5 |\ - GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_9 |\ - GPIO_PIN_10); -#endif -} - -#endif - - -/** - * @brief Configure the DMA controller according to the Stream parameters - * defined in main.h file - * @note This function is used to : - * -1- Enable DMA2 clock - * -2- Select the DMA functional Parameters - * -3- Select the DMA instance to be used for the transfer - * -4- Select Callbacks functions called after Transfer complete and - Transfer error interrupt detection - * -5- Initialize the DMA stream - * -6- Configure NVIC for DMA transfer complete/error interrupts - * @param None - * @retval None - */ -static void DMA_Config(void) -{ - /*## -1- Enable DMA2 clock #################################################*/ - __HAL_RCC_DMA2_CLK_ENABLE(); - - /*##-2- Select the DMA functional Parameters ###############################*/ - DmaHandle.Init.Channel = DMA_CHANNEL; /* DMA_CHANNEL_0 */ - DmaHandle.Init.Direction = DMA_MEMORY_TO_MEMORY; /* M2M transfer mode */ - DmaHandle.Init.PeriphInc = DMA_PINC_ENABLE; /* Peripheral increment mode Enable */ - DmaHandle.Init.MemInc = DMA_MINC_ENABLE; /* Memory increment mode Enable */ - DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* Peripheral data alignment : 16bit */ - DmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* memory data alignment : 16bit */ - DmaHandle.Init.Mode = DMA_NORMAL; /* Normal DMA mode */ - DmaHandle.Init.Priority = DMA_PRIORITY_HIGH; /* priority level : high */ - DmaHandle.Init.FIFOMode = DMA_FIFOMODE_ENABLE; /* FIFO mode enabled */ - DmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL; /* FIFO threshold: 1/4 full */ - DmaHandle.Init.MemBurst = DMA_MBURST_SINGLE; /* Memory burst */ - DmaHandle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* Peripheral burst */ - - /*##-3- Select the DMA instance to be used for the transfer : DMA2_Stream0 #*/ - DmaHandle.Instance = DMA_STREAM; - - /*##-4- Initialize the DMA stream ##########################################*/ - if(HAL_DMA_Init(&DmaHandle) != HAL_OK) - { - /* Turn LED4 on: in case of Initialization Error */ - // BSP_LED_On(LED4); - while(1) - { - } -} - - /*##-5- Select Callbacks functions called after Transfer complete and Transfer error */ - HAL_DMA_RegisterCallback(&DmaHandle, HAL_DMA_XFER_CPLT_CB_ID, DMA_TransferComplete); - HAL_DMA_RegisterCallback(&DmaHandle, HAL_DMA_XFER_ERROR_CB_ID, DMA_TransferError); - - /*##-6- Configure NVIC for DMA transfer complete/error interrupts ##########*/ - HAL_NVIC_SetPriority(DMA_STREAM_IRQ, 0, 0); - HAL_NVIC_EnableIRQ(DMA_STREAM_IRQ); -} - -/** - * @brief DMA conversion complete callback - * @note This function is executed when the transfer complete interrupt - * is generated - * @retval None - */ -static void DMA_TransferComplete(DMA_HandleTypeDef *han) -{ - y_fill_act ++; - - if(y_fill_act > y2_fill) { - lv_disp_flush_ready(&disp_drv); - } else { - buf_to_flush += x2_flush - x1_flush + 1; - /*##-7- Start the DMA transfer using the interrupt mode ####################*/ - /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */ - /* Enable All the DMA interrupts */ - if(HAL_DMA_Start_IT(han,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * TFT_HOR_RES + x1_flush], - (x2_flush - x1_flush + 1)) != HAL_OK) - { - while(1); /*Halt on error*/ - } - } -} - -/** - * @brief DMA conversion error callback - * @note This function is executed when the transfer error interrupt - * is generated during DMA transfer - * @retval None - */ -static void DMA_TransferError(DMA_HandleTypeDef *han) -{ - -} - - - -/** - * @brief This function handles DMA Stream interrupt request. - * @param None - * @retval None - */ -void DMA_STREAM_IRQHANDLER(void) -{ - /* Check the interrupt and clear flag */ - HAL_DMA_IRQHandler(&DmaHandle); -} - - -/** - * @brief This function is executed in case of error occurrence. - * @param None - * @retval None - */ -static void Error_Handler(void) -{ - while(1) - { - } -} diff --git a/components/gui/hal_lvgl/tft/tft.h b/components/gui/hal_lvgl/tft/tft.h deleted file mode 100644 index ad3ee9b..0000000 --- a/components/gui/hal_lvgl/tft/tft.h +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file disp.h - * - */ - -#ifndef DISP_H -#define DISP_H - -/********************* - * INCLUDES - *********************/ -#include -#include "lvgl.h" - -/********************* - * DEFINES - *********************/ -#define TFT_HOR_RES LV_HOR_RES -#define TFT_VER_RES LV_VER_RES - -#define TFT_EXT_FB 1 /*Frame buffer is located into an external SDRAM*/ -#define TFT_USE_GPU 1 /*Enable hardware accelerator*/ - -/********************** - * TYPEDEFS - **********************/ - - #ifdef __cplusplus - extern "C" { - #endif - -/********************** - * GLOBAL PROTOTYPES - **********************/ -void tft_init(void); - -/********************** - * MACROS - **********************/ - #ifdef __cplusplus - } /* extern "C" */ - #endif - -#endif diff --git a/components/gui/hal_lvgl/touchpad/touchpad.c b/components/gui/hal_lvgl/touchpad/touchpad.c deleted file mode 100644 index e4fe160..0000000 --- a/components/gui/hal_lvgl/touchpad/touchpad.c +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @file indev.c - * - */ - -/********************* - * INCLUDES - *********************/ -#include "stm32f4xx.h" - -#include "stm32469i_discovery.h" -#include "stm32469i_discovery_ts.h" -#include "lvgl.h" -#include "tft.h" - -/********************* - * DEFINES - *********************/ - -/********************** - * TYPEDEFS - **********************/ - -/********************** - * STATIC PROTOTYPES - **********************/ -static bool touchpad_read(lv_indev_drv_t * drv, lv_indev_data_t *data); -static void touchpad_correct(lv_point_t * point); - -/********************** - * STATIC VARIABLES - **********************/ -static TS_StateTypeDef TS_State; -static lv_point_t pcal[4]; // calibration points - -/********************** - * MACROS - **********************/ - -/********************** - * GLOBAL FUNCTIONS - **********************/ - -/** - * Initialize your input devices here - */ -void touchpad_init(void) -{ - BSP_TS_Init(TFT_HOR_RES, TFT_VER_RES); - - lv_indev_drv_t indev_drv; - lv_indev_drv_init(&indev_drv); - indev_drv.read_cb = touchpad_read; - indev_drv.type = LV_INDEV_TYPE_POINTER; - lv_indev_drv_register(&indev_drv); - - pcal[0].x = 0; - pcal[0].y = 0; - pcal[1].x = TFT_HOR_RES; - pcal[1].y = 0; - pcal[2].x = TFT_HOR_RES; - pcal[2].y = TFT_VER_RES; - pcal[3].x = 0; - pcal[3].y = TFT_VER_RES; -} - -void touchpad_calibrate(const lv_point_t * points){ - memcpy(pcal, points, sizeof(pcal)); -} - -/********************** - * STATIC FUNCTIONS - **********************/ - -static void touchpad_correct(lv_point_t * point){ - int16_t x = point->x; - int16_t y = point->y; - int16_t x1 = (pcal[0].x*(TFT_VER_RES-y)+pcal[3].x*y)/TFT_VER_RES; - int16_t x2 = (pcal[1].x*(TFT_VER_RES-y)+pcal[2].x*y)/TFT_VER_RES; - int16_t y1 = (pcal[0].y*(TFT_HOR_RES-x)+pcal[1].y*x)/TFT_HOR_RES; - int16_t y2 = (pcal[3].y*(TFT_HOR_RES-x)+pcal[2].y*x)/TFT_HOR_RES; - point->x = TFT_HOR_RES * (x-x1)/(x2-x1); - point->y = TFT_VER_RES * (y-y1)/(y2-y1); -} -/** - * Read an input device - * @param indev_id id of the input device to read - * @param x put the x coordinate here - * @param y put the y coordinate here - * @return true: the device is pressed, false: released - */ -static bool touchpad_read(lv_indev_drv_t * drv, lv_indev_data_t *data) -{ - static int16_t last_x = 0; - static int16_t last_y = 0; - - BSP_TS_GetState(&TS_State); - if(TS_State.touchDetected != 0) { - lv_point_t point; - point.x = TS_State.touchX[0]; - point.y = TS_State.touchY[0]; - touchpad_correct(&point); - data->point = point; - last_x = data->point.x; - last_y = data->point.y; - data->state = LV_INDEV_STATE_PR; - } else { - data->point.x = last_x; - data->point.y = last_y; - data->state = LV_INDEV_STATE_REL; - } - - return false; -} diff --git a/components/gui/hal_lvgl/touchpad/touchpad.h b/components/gui/hal_lvgl/touchpad/touchpad.h deleted file mode 100644 index c6adae8..0000000 --- a/components/gui/hal_lvgl/touchpad/touchpad.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @file indev.h - * - */ - -#ifndef INDEV_H -#define INDEV_H - -/********************* - * INCLUDES - *********************/ -#include -#include -#include - -/********************* - * DEFINES - *********************/ - -/********************** - * TYPEDEFS - **********************/ - - #ifdef __cplusplus - extern "C" { - #endif - -/********************** - * GLOBAL PROTOTYPES - **********************/ -void touchpad_init(void); -void touchpad_calibrate(const lv_point_t * points); - -/********************** - * MACROS - **********************/ - - #ifdef __cplusplus - } /* extern "C" */ - #endif - -#endif diff --git a/components/gui/mnemonic/mnemonic.h b/components/gui/mnemonic/mnemonic.h deleted file mode 100644 index 2cc57fc..0000000 --- a/components/gui/mnemonic/mnemonic.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef GUI_MNEMONIC_H -#define GUI_MNEMONIC_H - -#include "lvgl.h" - -lv_obj_t * gui_mnemonic_table_create(lv_obj_t * scr, const char * mnemonic); -void gui_show_mnemonic(lv_obj_t * tbl, const char * mnemonic, bool highlight = false); -void gui_check_mnemonic(const char * mnemonic, lv_obj_t * kb); - -#endif /* GUI_MNEMONIC_H */ diff --git a/components/gui/mnemonic/mnemonic_screen.cpp b/components/gui/mnemonic/mnemonic_screen.cpp deleted file mode 100644 index 6c6c37d..0000000 --- a/components/gui/mnemonic/mnemonic_screen.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "mnemonic.h" -#include -#include "lvgl.h" -#include "wally_bip39.h" -extern "C" { - #include "utility/wordlist.h" -} - -#include "../gui_common.h" -#include "../gui.h" - -lv_obj_t * gui_mnemonic_table_create(lv_obj_t * scr, const char * mnemonic){ - static lv_style_t num_style; - lv_style_copy(&num_style, &lv_style_transp); - num_style.text.opa = LV_OPA_40; - - lv_obj_t * table = lv_table_create(scr, NULL); - lv_table_set_col_cnt(table, 4); - lv_table_set_row_cnt(table, 12); - lv_table_set_col_width(table, 0, 50); - lv_table_set_col_width(table, 2, 50); - lv_table_set_col_width(table, 1, 150); - lv_table_set_col_width(table, 3, 150); - - lv_table_set_style(table, LV_PAGE_STYLE_BG, &lv_style_transp); - lv_table_set_style(table, LV_TABLE_STYLE_CELL1, &lv_style_transp); - lv_table_set_style(table, LV_TABLE_STYLE_CELL2, &num_style); - - char msg[5] = ""; - for(int i=0; i<12; i++){ - sprintf(msg, "%d.", i+1); - lv_table_set_cell_value(table, i, 0, msg); - sprintf(msg, "%d.", i+13); - lv_table_set_cell_value(table, i, 2, msg); - lv_table_set_cell_type(table, i, 0, LV_TABLE_STYLE_CELL2); - lv_table_set_cell_type(table, i, 2, LV_TABLE_STYLE_CELL2); - } - lv_obj_align(table, NULL, LV_ALIGN_IN_TOP_MID, 0, 100); - - gui_show_mnemonic(table, mnemonic, NULL); - - return table; -} - -void gui_show_mnemonic(lv_obj_t * tbl, const char * mnemonic, bool highlight){ - int cellnum = 0; - int word_start = 0; - char word[10] = ""; - for(unsigned i=0; i0){ - lv_table_set_cell_type(tbl, (cellnum-1)%12, 2*((cellnum-1)/12), LV_TABLE_STYLE_CELL2); - } - if(cellnum<24){ - lv_table_set_cell_type(tbl, (cellnum+1)%12, 2*((cellnum+1)/12), LV_TABLE_STYLE_CELL2); - } - // last word - if((word_start < strlen(mnemonic))||(word_start==0)){ - lv_table_set_cell_value(tbl, cellnum % 12, 1+2*(cellnum/12), mnemonic + word_start); - } - int wordcount = cellnum+1; - if(wordcount < 24){ - lv_table_set_cell_value(tbl, wordcount % 12, 1+2*(wordcount/12), ""); - } -} - -static struct words * wordlist = NULL; - -void gui_check_mnemonic(const char * mnemonic, lv_obj_t * kb){ - if(wordlist == NULL){ - bip39_get_wordlist(NULL, &wordlist); - } - const char * lastWord = mnemonic; - for(int i=strlen(mnemonic)-1; i>=0; i--){ - if(mnemonic[i]==' '){ - lastWord = mnemonic+i+1; - break; - } - } - if(strlen(lastWord) > 0){ - bool correct = (wordlist_lookup_word(wordlist, lastWord) > 0); - if(correct){ - lv_btnm_clear_btn_ctrl(kb, 28, LV_BTNM_CTRL_INACTIVE); - }else{ - lv_btnm_set_btn_ctrl(kb, 28, LV_BTNM_CTRL_INACTIVE); - } - }else{ - lv_btnm_set_btn_ctrl(kb, 29, LV_BTNM_CTRL_INACTIVE); - lv_btnm_set_btn_ctrl(kb, 28, LV_BTNM_CTRL_INACTIVE); - } - - int wordcount = 1; - for(int i=0; i=12 && (wordcount % 3 == 0)){ - if(bip39_mnemonic_validate(NULL, mnemonic) == 0){ - lv_btnm_clear_btn_ctrl(kb, 29, LV_BTNM_CTRL_INACTIVE); - }else{ - lv_btnm_set_btn_ctrl(kb, 29, LV_BTNM_CTRL_INACTIVE); - } - } -} \ No newline at end of file diff --git a/components/gui/tpcal/tpcal.c b/components/gui/tpcal/tpcal.c deleted file mode 100644 index bebb166..0000000 --- a/components/gui/tpcal/tpcal.c +++ /dev/null @@ -1,434 +0,0 @@ -/** - * @file tpcal.c - * - * TOUCHPAD CALIBRATION - * --------------------- - * - * This application creates a GUI and instruct the user - * to click the four corners to get data for touchpad calibration. - * - * - You display driver should have two functions: `xxx_read` and `xxx_set_cal_data`. - * - At first run run the touchpad is not calibrated therefore your `xxx_read` function should provide raw data. - * - When the user touched all four corners you should call the `xxx_set_cal_data` function in - * ` TP_CAL_STATE_WAIT_LEAVE` state. As arguments you should pass `point[0]`, `point[1]`, `point[2]` and `point[3]` - * which are the coordinates read on corner pressing. - * - `xxx_set_cal_data` should mark the display as calibrated, save the raw coordinates - * and use them in the upcoming calls of `xxx_read` to adjust the coordinates. - * - A simple equation to adjust the coordinates: x_cal = ((x_act - x1_saved) * lcd_hor_res) / (x2_saved - x1_saved); - * - x_cal: the calibrated X coordinate - * - x_act: the currently measured X coordinate - * - x1_saved, x2_saved: The raw X coordinates saved as calibration data - */ - -/********************* - * INCLUDES - *********************/ -#include "tpcal.h" -#include -#include "touchpad.h" - -/********************* - * DEFINES - *********************/ -#define CIRCLE_SIZE 20 -#define CIRCLE_OFFSET 20 -#define TP_MAX_VALUE 5000 -#define TOUCH_NUMBER 3 - -/********************** - * TYPEDEFS - **********************/ -typedef enum { - TP_CAL_STATE_INIT, - TP_CAL_STATE_WAIT_TOP_LEFT, - TP_CAL_STATE_WAIT_TOP_RIGHT, - TP_CAL_STATE_WAIT_BOTTOM_RIGHT, - TP_CAL_STATE_WAIT_BOTTOM_LEFT, - TP_CAL_STATE_WAIT_LEAVE, - TP_CAL_STATE_READY, -} tp_cal_state_t; - -/********************** - * STATIC PROTOTYPES - **********************/ -static void get_avr_value(lv_point_t * p); -static void btn_event_cb(lv_obj_t * scr, lv_event_t event); - -static void draw_rect(); -static void update_rect(); - -/********************** - * STATIC VARIABLES - **********************/ -static lv_point_t point[4]; /*Calibration points: [0]: top-left; [1]: top-right, [2]: bottom-right, [3]: bottom-left */ -static lv_point_t avr[TOUCH_NUMBER]; /*Storage point to calculate average*/ - -static tp_cal_state_t state; -static lv_obj_t * prev_scr; -static lv_obj_t * big_btn; -static lv_obj_t * label_main; -static lv_obj_t * circ_area; - -static lv_obj_t * line; -static lv_point_t line_points[5]; - -static void (*callback)(lv_point_t * points); - -/********************** - * GLOBAL FUNCTIONS - **********************/ - -/** - * Create a touch pad calibration screen - */ -void tpcal_create(void (*cb)(lv_point_t * points)){ - callback = cb; - - state = TP_CAL_STATE_INIT; - - prev_scr = lv_disp_get_scr_act(NULL); - - lv_obj_t * scr = lv_obj_create(NULL, NULL); - lv_obj_set_size(scr, TP_MAX_VALUE, TP_MAX_VALUE); - lv_disp_load_scr(scr); - - /*Create a big transparent button screen to receive clicks*/ - big_btn = lv_btn_create(lv_disp_get_scr_act(NULL), NULL); - lv_obj_set_size(big_btn, TP_MAX_VALUE, TP_MAX_VALUE); - lv_btn_set_style(big_btn, LV_BTN_STYLE_REL, &lv_style_transp); - lv_btn_set_style(big_btn, LV_BTN_STYLE_PR, &lv_style_transp); - lv_obj_set_event_cb(big_btn, btn_event_cb); - lv_btn_set_layout(big_btn, LV_LAYOUT_OFF); - - lv_coord_t hres = lv_disp_get_hor_res(NULL); - lv_coord_t vres = lv_disp_get_ver_res(NULL); - - lv_obj_t * title = lv_label_create(lv_disp_get_scr_act(NULL), NULL); - lv_label_set_text(title, "First we need to calibrate the screen\n" - "If you failed and can't interact with GUI\n" - "just press the user button\n(blue on the back)" - ); - lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); - lv_obj_set_pos(title, (hres - lv_obj_get_width(title)) / 2, 200); - - label_main = lv_label_create(lv_disp_get_scr_act(NULL), NULL); - char buf[100]; - sprintf(buf, "Click the circle in the\n" - "upper left-hand corner\n" - "%u times left", TOUCH_NUMBER); - lv_label_set_text(label_main, buf); - lv_label_set_align(label_main, LV_LABEL_ALIGN_CENTER); - - lv_obj_set_pos(label_main, (hres - lv_obj_get_width(label_main)) / 2, - (vres - lv_obj_get_height(label_main)) / 2); - - - static lv_style_t style_circ; - lv_style_copy(&style_circ, &lv_style_pretty_color); - style_circ.body.radius = LV_RADIUS_CIRCLE; - - circ_area = lv_obj_create(lv_disp_get_scr_act(NULL), NULL); - lv_obj_set_size(circ_area, CIRCLE_SIZE, CIRCLE_SIZE); - lv_obj_set_style(circ_area, &style_circ); - lv_obj_set_click(circ_area, false); - -#if LV_USE_ANIMATION - lv_anim_t a; - a.var = circ_area; - a.start = hres / 2; - a.end = CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_x; - a.path_cb = lv_anim_path_linear; - a.ready_cb = NULL; - a.act_time = -500; - a.time = 200; - a.playback = 0; - a.playback_pause = 0; - a.repeat = 0; - a.repeat_pause = 0; - lv_anim_create(&a); - - a.start = vres / 2; - a.end = CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_y; - a.ready_cb = NULL; - a.time = 200; - lv_anim_create(&a); -#else - lv_obj_set_pos(circ_area, CIRCLE_OFFSET, CIRCLE_OFFSET); -#endif - - draw_rect(); - - state = TP_CAL_STATE_WAIT_TOP_LEFT; -} - -/********************** - * STATIC FUNCTIONS - **********************/ - -static void get_avr_value(lv_point_t * p) -{ - int32_t x_sum = 0; - int32_t y_sum = 0; - uint8_t i = 0; - for(; i < TOUCH_NUMBER ; i++) { - x_sum += avr[i].x; - y_sum += avr[i].y; - } - p->x = x_sum / TOUCH_NUMBER; - p->y = y_sum / TOUCH_NUMBER; -} - -static void btn_event_cb(lv_obj_t * scr, lv_event_t event) -{ - (void) scr; /*Unused*/ - - if(event != LV_EVENT_CLICKED) return; - - lv_disp_t * disp = lv_obj_get_disp(prev_scr); - lv_coord_t hres = lv_disp_get_hor_res(disp); - lv_coord_t vres = lv_disp_get_ver_res(disp); - - static uint8_t touch_nb = TOUCH_NUMBER; - - if(state == TP_CAL_STATE_WAIT_TOP_LEFT) { - char buf[64]; - touch_nb--; - lv_indev_t * indev = lv_indev_get_act(); - lv_indev_get_point(indev, &avr[touch_nb]); - - if(!touch_nb) { - touch_nb = TOUCH_NUMBER; - get_avr_value(&point[0]); - sprintf(buf, "x: %d\ny: %d", point[0].x, point[0].y); - lv_obj_t * label_coord = lv_label_create(lv_disp_get_scr_act(disp), NULL); - lv_label_set_text(label_coord, buf); - sprintf(buf, "Click the circle in\n" - "upper right-hand corner\n" - " %u Left", TOUCH_NUMBER); -#if LV_USE_ANIMATION - lv_anim_t a; - a.var = circ_area; - a.start = CIRCLE_OFFSET; - a.end = hres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_x; - a.path_cb = lv_anim_path_linear; - a.ready_cb = NULL; - a.act_time = 0; - a.time = 200; - a.playback = 0; - a.playback_pause = 0; - a.repeat = 0; - a.repeat_pause = 0; - lv_anim_create(&a); - - a.start = CIRCLE_OFFSET; - a.end = CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_y; - a.ready_cb = NULL; - a.time = 200; - lv_anim_create(&a); -#else - lv_obj_set_pos(circ_area, LV_HOR_RES - CIRCLE_SIZE - CIRCLE_OFFSET, CIRCLE_OFFSET); -#endif - state = TP_CAL_STATE_WAIT_TOP_RIGHT; - } else { - sprintf(buf, "Click the circle in\n" - "upper left-hand corner\n" - " %u Left", touch_nb); - } - lv_label_set_text(label_main, buf); - lv_obj_set_pos(label_main, (hres - lv_obj_get_width(label_main)) / 2, - (vres - lv_obj_get_height(label_main)) / 2); - - - } else if(state == TP_CAL_STATE_WAIT_TOP_RIGHT) { - char buf[64]; - touch_nb--; - lv_indev_t * indev = lv_indev_get_act(); - lv_indev_get_point(indev, &avr[touch_nb]); - - if(!touch_nb) { - touch_nb = TOUCH_NUMBER; - get_avr_value(&point[1]); - sprintf(buf, "x: %d\ny: %d", point[1].x, point[1].y); - lv_obj_t * label_coord = lv_label_create(lv_disp_get_scr_act(disp), NULL); - lv_label_set_text(label_coord, buf); - lv_obj_set_pos(label_coord, hres - lv_obj_get_width(label_coord), 0); - sprintf(buf, "Click the circle in\n" - "lower right-hand corner\n" - " %u Left", TOUCH_NUMBER); -#if LV_USE_ANIMATION - lv_anim_t a; - a.var = circ_area; - a.start = hres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.end = hres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_x; - a.path_cb = lv_anim_path_linear; - a.ready_cb = NULL; - a.act_time = 0; - a.time = 200; - a.playback = 0; - a.playback_pause = 0; - a.repeat = 0; - a.repeat_pause = 0; - lv_anim_create(&a); - - a.start = CIRCLE_OFFSET; - a.end = vres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_y; - a.ready_cb = NULL; - a.time = 200; - lv_anim_create(&a); -#else - lv_obj_set_pos(circ_area, hres - CIRCLE_SIZE - CIRCLE_OFFSET, vres - CIRCLE_SIZE - CIRCLE_OFFSET); -#endif - state = TP_CAL_STATE_WAIT_BOTTOM_RIGHT; - } else { - sprintf(buf, "Click the circle in\n" - "upper right-hand corner\n" - " %u Left", touch_nb); - } - lv_label_set_text(label_main, buf); - lv_obj_set_pos(label_main, (hres - lv_obj_get_width(label_main)) / 2, - (vres - lv_obj_get_height(label_main)) / 2); - - } else if(state == TP_CAL_STATE_WAIT_BOTTOM_RIGHT) { - char buf[64]; - touch_nb--; - lv_indev_t * indev = lv_indev_get_act(); - lv_indev_get_point(indev, &avr[touch_nb]); - - if(!touch_nb) { - touch_nb = TOUCH_NUMBER; - get_avr_value(&point[2]); - sprintf(buf, "x: %d\ny: %d", point[2].x, point[2].y); - lv_obj_t * label_coord = lv_label_create(scr, NULL); - lv_label_set_text(label_coord, buf); - sprintf(buf, "Click the circle in\n" - "lower left-hand corner\n" - " %u Left", TOUCH_NUMBER); - lv_obj_set_pos(label_coord, hres - lv_obj_get_width(label_coord), - vres - lv_obj_get_height(label_coord)); -#if LV_USE_ANIMATION - lv_anim_t a; - a.var = circ_area; - a.start = hres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.end = CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_x; - a.path_cb = lv_anim_path_linear; - a.ready_cb = NULL; - a.act_time = 0; - a.time = 200; - a.playback = 0; - a.playback_pause = 0; - a.repeat = 0; - a.repeat_pause = 0; - lv_anim_create(&a); - - a.start = vres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.end = vres - CIRCLE_SIZE - CIRCLE_OFFSET; - a.exec_cb = (lv_anim_exec_xcb_t)lv_obj_set_y; - a.ready_cb = NULL; - a.time = 200; - lv_anim_create(&a); -#else - lv_obj_set_pos(circ_area, CIRCLE_OFFSET, LV_VER_RES - CIRCLE_SIZE - CIRCLE_OFFSET); -#endif - state = TP_CAL_STATE_WAIT_BOTTOM_LEFT; - } else { - sprintf(buf, "Click the circle in\n" - "lower right-hand corner\n" - " %u Left", touch_nb); - } - lv_label_set_text(label_main, buf); - lv_obj_set_pos(label_main, (hres - lv_obj_get_width(label_main)) / 2, - (vres - lv_obj_get_height(label_main)) / 2); - } else if(state == TP_CAL_STATE_WAIT_BOTTOM_LEFT) { - char buf[300]; - touch_nb--; - lv_indev_t * indev = lv_indev_get_act(); - lv_indev_get_point(indev, &avr[touch_nb]); - - if(!touch_nb) { - touch_nb = TOUCH_NUMBER; - get_avr_value(&point[3]); - sprintf(buf, "x: %d\ny: %d", point[3].x, point[3].y); - lv_obj_t * label_coord = lv_label_create(scr, NULL); - lv_label_set_text(label_coord, buf); - lv_obj_set_pos(label_coord, 0, vres - lv_obj_get_height(label_coord)); - sprintf(buf, "Click the screen\n" - "to leave calibration"); - for(int i=0;i<4;i++){ - update_rect(); - sprintf(buf+strlen(buf),"\npoint%d: (%d,%d)", i, line_points[i].x, line_points[i].y); - } - lv_obj_del(circ_area); - state = TP_CAL_STATE_WAIT_LEAVE; - } else { - sprintf(buf, "Click the circle in\n" - "lower left-hand corner\n" - " %u Left", touch_nb); - } - lv_label_set_text(label_main, buf); - lv_obj_set_pos(label_main, (hres - lv_obj_get_width(label_main)) / 2, - (vres - lv_obj_get_height(label_main)) / 2); - } else if(state == TP_CAL_STATE_WAIT_LEAVE) { - lv_disp_load_scr(prev_scr); - /* - * TODO Process 'p' points here to calibrate the touch pad - * Offset will be: CIRCLE_SIZE/2 + CIRCLE_OFFSET - */ - callback(line_points); - /* - * TODO: you can change the calibrate input callback here e.g: - * lv_indev_t *indev = lv_indev_get_act(); - * indev->driver.read = xxxx_input_get_calib; - */ - - state = TP_CAL_STATE_READY; - - } else if(state == TP_CAL_STATE_READY) { - } -} - -static void update_rect(){ - memcpy(line_points, point, sizeof(line_points)); - line_points[0].x -= CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[0].y -= CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[1].x += CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[1].y -= CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[2].x += CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[2].y += CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[3].x -= CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[3].y += CIRCLE_SIZE/2 + CIRCLE_OFFSET; - line_points[4] = line_points[0]; - lv_line_set_points(line, line_points, 5); /*Set the points*/ -} - -static void draw_rect(){ - static lv_style_t style_line; - lv_style_copy(&style_line, &lv_style_plain); - style_line.line.color = LV_COLOR_MAKE(0xaa, 0xaa, 0xaa); - style_line.line.width = 1; - style_line.line.rounded = 1; - - // get rid of previous calibration - line_points[0].x = 0; - line_points[0].y = 0; - line_points[1].x = LV_HOR_RES; - line_points[1].y = 0; - line_points[2].x = LV_HOR_RES; - line_points[2].y = LV_VER_RES; - line_points[3].x = 0; - line_points[3].y = LV_VER_RES; - - touchpad_calibrate(line_points); - - /*Copy the previous line and apply the new style*/ - line = lv_line_create(lv_scr_act(), NULL); - lv_line_set_style(line, LV_LINE_STYLE_MAIN, &style_line); - // lv_obj_align(line, NULL, LV_ALIGN_CENTER, 0, 0); - // update_rect(); -} diff --git a/components/gui/tpcal/tpcal.h b/components/gui/tpcal/tpcal.h deleted file mode 100644 index f218878..0000000 --- a/components/gui/tpcal/tpcal.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file tpcal.h - * - */ - -#ifndef TPCAL_H -#define TPCAL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "lvgl.h" - -/** - * Create a touch pad calibration screen - */ -void tpcal_create(void (*cb)(lv_point_t * points)); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /*TP_CAL_H*/ diff --git a/components/helpers/helpers.cpp b/components/helpers/helpers.cpp deleted file mode 100644 index 8e6aad3..0000000 --- a/components/helpers/helpers.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "helpers.h" -#include -#include "alert.h" - -/** small helper functions that prints - * data in hex to the serial port */ -void print_hex(const uint8_t * data, size_t data_len){ - for(int i=0; i -#include - -void print_hex(const uint8_t * data, size_t data_len); -void println_hex(const uint8_t * data, size_t data_len); -void show_err(const char * message); - -void logit(const char * module, const char * message); - -#endif \ No newline at end of file diff --git a/components/host/host.cpp b/components/host/host.cpp deleted file mode 100644 index f44257c..0000000 --- a/components/host/host.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "host.h" -#include "specter_config.h" -#include "mbed.h" -#include "QRScanner.h" -#include "helpers.h" - -static unsigned int host_flags = HOST_DEFAULT; - -Timer t; -static float tmax; - -QRScanner qrscanner(D5, D1, D0); -// buffer to store external scans -static char * qrbuf = NULL; - -void host_update(){ - if(qrscanner.getStatus() == QR_EXTERNAL){ - t.stop(); - t.reset(); - qrscanner.trigger = 1; - host_flags = host_flags & 0xE; // meh - }else{ - if(host_flags & HOST_LISTEN_ONCE){ - float dt = t.read(); - if(dt > tmax){ - t.stop(); - t.reset(); - show_err("QR scanner timed out, try again."); - } - } - } -} - -void host_init(int flags, float timeout){ - qrbuf = (char *)calloc(SPECTER_HOST_INPUT_SIZE, 1); - host_flags = flags; - memset(qrbuf, 0, SPECTER_HOST_INPUT_SIZE); - qrscanner.setBuffer(qrbuf, SPECTER_HOST_INPUT_SIZE-1); - tmax = timeout; -} - -void host_request_data(){ - host_flush(); - wait(0.3); - host_flags |= HOST_LISTEN_ONCE; - qrscanner.trigger = 0; - t.reset(); - t.start(); -} - -int host_data_available(){ - if(qrscanner.getStatus() == QR_EXTERNAL){ - return strlen(qrbuf); - } - return 0; -} - -// this communication channel doesn't support sending data -int host_send(uint8_t * data, size_t len){ - return 0; -} - -void host_flush(){ - qrscanner.trigger = 1; - host_flags = host_flags & 0xE; - memset(qrbuf, 0, SPECTER_HOST_INPUT_SIZE); - qrscanner.setBuffer(qrbuf, SPECTER_HOST_INPUT_SIZE); -} - -size_t host_read(uint8_t * buf, size_t len){ - size_t cur = strlen(qrbuf); - if(len > cur){ - len = cur; - } - memcpy(buf, qrbuf, len); - cur -= len; - memcpy(qrbuf, qrbuf+len, cur); - memset(qrbuf+cur, 0, len); - return len; -} - -uint8_t * host_get_data(){ - return (uint8_t *)qrbuf; -} diff --git a/components/host/host.h b/components/host/host.h deleted file mode 100644 index 482861a..0000000 --- a/components/host/host.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef __HOST_H__ -#define __HOST_H__ - -#include -#include - -#define HOST_DEFAULT 0 // drops data if not set to listen -#define HOST_LISTEN_ONCE 1 // set to receive data once, will be cleared after one packet -#define HOST_ALWAYS_LISTEN 2 // set to receive data even if it's not triggered by the main logic -#define HOST_ALLOW_SEND 4 // set if data should be sent to host - -void host_init(int flags, float timeout); -void host_request_data(); -void host_update(); -int host_data_available(); -int host_send(uint8_t * data, size_t len); -void host_flush(); -size_t host_read(uint8_t * buf, size_t len); -uint8_t * host_get_data(); - -#endif \ No newline at end of file diff --git a/components/keystore/keystore.c b/components/keystore/keystore.c deleted file mode 100644 index ca90633..0000000 --- a/components/keystore/keystore.c +++ /dev/null @@ -1,750 +0,0 @@ -#include "keystore.h" -#include -#include "wally_crypto.h" -#include "wally_bip32.h" -#include "wally_bip39.h" -#include "wally_address.h" -#include "wally_script.h" -#include "networks.h" -#include "utility/ccan/ccan/endian/endian.h" - -static uint32_t * parse_derivation(const char * path, size_t * derlen){ - static const char VALID_CHARS[] = "0123456789/'h"; - size_t len = strlen(path); - const char * cur = path; - if(path[0] == 'm'){ // remove leading "m/" - cur+=2; - len-=2; - } - if(cur[len-1] == '/'){ // remove trailing "/" - len--; - } - size_t derivationLen = 1; - // checking if all chars are valid and counting derivation length - for(size_t i=0; i= BIP32_INITIAL_HARDENED_CHILD){ // can't have anything after hardened - free(derivation); - return NULL; - } - if(val < 10){ - derivation[current] = derivation[current]*10 + val; - }else{ // h or ' -> hardened - derivation[current] += BIP32_INITIAL_HARDENED_CHILD; - } - } - *derlen = derivationLen; - return derivation; -} - -int keystore_init(const char * mnemonic, const char * password, keystore_t * key){ - if(key == NULL){ - return -1; - } - if(mnemonic == NULL){ - key->root = NULL; - return 0; - } - if(key->root != NULL){ - bip32_key_free(key->root); - key->root = NULL; - } - int res; - size_t len; - uint8_t seed[BIP39_SEED_LEN_512]; - // FIXME: process results - something might go wrong - res = bip39_mnemonic_to_seed(mnemonic, password, seed, sizeof(seed), &len); - res = bip32_key_from_seed_alloc(seed, sizeof(seed), BIP32_VER_TEST_PRIVATE, 0, &key->root); - wally_bzero(seed, sizeof(seed)); - uint8_t h160[20]; - wally_hash160(key->root->pub_key, sizeof(key->root->pub_key), - h160, sizeof(h160)); - for(int i=0; i<4; i++){ - sprintf(key->fingerprint+2*i, "%02x", h160[i]); - } - return 0; -} - -int keystore_get_xpub(const keystore_t * key, const char * path, const network_t * network, int use_slip132, char ** xpub){ - struct ext_key * child = NULL; - int res; - size_t len = 0; - uint32_t * derivation = parse_derivation(path, &len); - if(derivation == NULL){ - return -1; - } - res = bip32_key_from_parent_path_alloc(key->root, derivation, len, BIP32_FLAG_KEY_PRIVATE, &child); - child->version = network->xprv; - uint8_t xpub_raw[BIP32_SERIALIZED_LEN]; - res = bip32_key_serialize(child, BIP32_FLAG_KEY_PUBLIC, xpub_raw, sizeof(xpub_raw)); - uint32_t ver = cpu_to_be32(network->xpub); - if((len > 0) & use_slip132){ - switch(derivation[0]){ - case BIP32_INITIAL_HARDENED_CHILD+84: - { - ver = cpu_to_be32(network->zpub); - break; - } - case BIP32_INITIAL_HARDENED_CHILD+49: - { - ver = cpu_to_be32(network->ypub); - break; - } - case BIP32_INITIAL_HARDENED_CHILD+48: - { - if(len >= 4){ - switch(derivation[3]){ - case BIP32_INITIAL_HARDENED_CHILD+1: - { - ver = cpu_to_be32(network->Ypub); - break; - } - case BIP32_INITIAL_HARDENED_CHILD+2: - { - ver = cpu_to_be32(network->Zpub); - break; - } - } - } - break; - } - } - } - memcpy(xpub_raw, &ver, 4); - res = wally_base58_from_bytes(xpub_raw, sizeof(xpub_raw), BASE58_FLAG_CHECKSUM, xpub); - bip32_key_free(child); - free(derivation); - return 0; -} - -int keystore_get_addr_path(const keystore_t * key, const uint32_t * derivation, size_t len, const network_t * network, char ** addr, int flag){ - struct ext_key * child = NULL; - int res; - res = bip32_key_from_parent_path_alloc(key->root, derivation, len, BIP32_FLAG_KEY_PRIVATE, &child); - child->version = network->xprv; - - if(flag == KEYSTORE_BECH32_ADDRESS){ - res |= wally_bip32_key_to_addr_segwit(child, network->bech32, 0, addr); - }else{ - res |= wally_bip32_key_to_address(child, WALLY_ADDRESS_TYPE_P2SH_P2WPKH, network->p2sh, addr); - } - bip32_key_free(child); - if(res!=WALLY_OK){ - wally_free_string(*addr); - } - return res; -} - -int keystore_get_addr(const keystore_t * key, const char * path, const network_t * network, char ** addr, int flag){ - size_t len = 0; - uint32_t * derivation = parse_derivation(path, &len); - if(derivation == NULL){ - return -1; - } - int res = keystore_get_addr_path(key, derivation, len, network, addr, flag); - free(derivation); - return res; -} - -static int pubkeys_compare(const void * pub1, const void * pub2) { - return memcmp(pub1, pub2, 33); -} - -int wallet_get_scriptpubkey(const wallet_t * wallet, const uint32_t * derivation, size_t derlen, uint8_t * scriptpubkey, size_t scriptpubkey_len){ - if(wallet->val == 0){ // TODO: implement for p2wpkh! - return -1; - } - if(scriptpubkey_len < 34){ - return -1; - } - char path[100]; - sprintf(path, "/internal/%s/%s/%d.wallet", wallet->keystore->fingerprint, wallet->network->name, wallet->val-1); - FILE *f = fopen(path, "r"); - if(!f){ - printf("missing file\r\n"); - return -1; - } - int m; int n; - fscanf(f, "name=%*[^\n]\ntype=%*s\nm=%d\nn=%d\n", &m, &n); - char xpub[150]; - uint8_t * pubs = (uint8_t *)malloc(33*n); - for(int i=0; i 0){ - bip32_key_from_parent_alloc(k, derivation[0], BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH, &k2); - temp = k; k = k2; k2 = temp; // swap - } - for(int j=1; jpub_key, 33); - temp = NULL; - bip32_key_free(k); - if(derlen > 0){ - bip32_key_free(k2); - } - } - qsort(pubs, n, 33, pubkeys_compare); - size_t len = 34*n+3; - uint8_t * script = (uint8_t *)malloc(len); - size_t lenout = 0; - wally_scriptpubkey_multisig_from_bytes(pubs, 33*n, m, 0, script, len, &lenout); - free(pubs); - - scriptpubkey[0] = 0; - scriptpubkey[1] = 32; - wally_sha256(script, lenout, scriptpubkey+2, 32); - free(script); - - return 0; -} - -int keystore_check_psbt(const keystore_t * key, const network_t * network, const struct wally_psbt * psbt, wallet_t * wallet){ - // check inputs: at least one to sign - uint8_t err = KEYSTORE_PSBTERR_CANNOT_SIGN; - uint8_t h160[20]; - // binary fingerprint of the root - wally_hash160(key->root->pub_key, sizeof(key->root->pub_key), - h160, sizeof(h160)); - // all inputs have to correpond to the same wallet - int wallet_id = -1; // undetermined yet - for(int i=0; inum_inputs; i++){ - // check fingerprints in derivations - if(psbt->inputs[i].keypaths == NULL){ - return KEYSTORE_PSBTERR_CANNOT_SIGN; - } - uint8_t can_sign = 0; - for(int j=0; jinputs[i].keypaths->num_items; j++){ - if(memcmp(psbt->inputs[i].keypaths->items[j].origin.fingerprint, h160, 4)==0){ - can_sign = 1; - break; - } - // if watch wallet doesn't know the fingerprint - try to sign (i.e. blue wallet) - uint8_t zero[4] = { 0 }; - if(memcmp(psbt->inputs[i].keypaths->items[j].origin.fingerprint, zero, 4)==0){ - can_sign = 1; - break; - } - } - if(can_sign){ - err = 0; - }else{ - if(err == 0){ // if can't sign but could sign previous - return KEYSTORE_PSBTERR_MIXED_INPUTS; - } - } - if(psbt->inputs[i].witness_utxo == NULL){ // we don't support legacy - return KEYSTORE_PSBTERR_UNSUPPORTED_POLICY; - } - // now let's check which wallet it corresponds to - size_t script_type; - uint8_t * script = psbt->inputs[i].witness_utxo->script; - size_t script_len = psbt->inputs[i].witness_utxo->script_len; - - int res = wally_scriptpubkey_get_type( - script, - script_len, - &script_type); - if(res != WALLY_OK){ - return KEYSTORE_PSBTERR_UNSUPPORTED_POLICY; - } - if(script_type == WALLY_SCRIPT_TYPE_P2SH){ - if(psbt->inputs[i].redeem_script == NULL){ - return KEYSTORE_PSBTERR_WRONG_FIELDS; - } - script = psbt->inputs[i].redeem_script; - script_len = psbt->inputs[i].redeem_script_len; - res = wally_scriptpubkey_get_type( - script, - script_len, - &script_type); - if(res != WALLY_OK){ - return KEYSTORE_PSBTERR_WRONG_FIELDS; - } - } - switch(script_type){ - case WALLY_SCRIPT_TYPE_P2WPKH: - { - // check that key is indeed the right one - struct ext_key * pk; - bip32_key_from_parent_path_alloc(key->root, - psbt->inputs[i].keypaths->items[0].origin.path, - psbt->inputs[i].keypaths->items[0].origin.path_len, - BIP32_FLAG_KEY_PRIVATE, &pk); - uint8_t h160[20]; - wally_hash160(pk->pub_key, 33, h160, 20); - bip32_key_free(pk); - if(memcmp(h160, script+2, 20) != 0){ - return KEYSTORE_PSBTERR_WRONG_FIELDS; - } - if(wallet_id > 0){ // multisig and single are mixed - return KEYSTORE_PSBTERR_MIXED_INPUTS; - } - if(wallet_id < 0){ - wallet_id = 0; - } - break; - } - case WALLY_SCRIPT_TYPE_P2WSH: // multisig - { - // find non-hardened derivation - uint32_t * derivation = psbt->inputs[i].keypaths->items[0].origin.path; - size_t derlen = psbt->inputs[i].keypaths->items[0].origin.path_len; - while(derivation[0] >= BIP32_INITIAL_HARDENED_CHILD){ - derivation = derivation+1; - derlen -= 1; - } - if(wallet_id == 0){ - return KEYSTORE_PSBTERR_MIXED_INPUTS; - } - wallet_t w; - uint8_t script2[34]; - if(wallet_id > 0){ // if wallet is already determined - keystore_get_wallet(key, network, wallet_id, &w); - wallet_get_scriptpubkey(&w, derivation, derlen, script2, sizeof(script2)); - if(memcmp(script, script2, script_len) != 0){ - return KEYSTORE_PSBTERR_MIXED_INPUTS; - } - }else{ // search for matching wallet - int count = keystore_get_wallets_number(key, network); - for(int i=0; ikeystore; - if(i >= psbt->num_outputs){ - return 0; - } - if(psbt->outputs[i].keypaths == NULL){ - return 0; - } - if(wallet->val > 0){ // multisig - uint32_t * derivation = psbt->outputs[i].keypaths->items[0].origin.path; - size_t derlen = psbt->outputs[i].keypaths->items[0].origin.path_len; - while(derivation[0] >= BIP32_INITIAL_HARDENED_CHILD){ - derivation = derivation+1; - derlen -= 1; - } - uint8_t scriptpubkey[34]; - wallet_get_scriptpubkey(wallet, derivation, derlen, scriptpubkey, sizeof(scriptpubkey)); - if(memcmp(scriptpubkey, psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len) == 0){ - return 1; - } - return 0; - } - struct ext_key * pk = NULL; - // TODO: fix for multiple keypaths - bip32_key_from_parent_path_alloc(key->root, - psbt->outputs[i].keypaths->items[0].origin.path, - psbt->outputs[i].keypaths->items[0].origin.path_len, - BIP32_FLAG_KEY_PRIVATE, &pk); - size_t script_type; - wally_scriptpubkey_get_type(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, &script_type); - // should deal with all script types, only P2WPKH for now - - // doesn't matter, we just compare strings... - // TODO: refactor with scriptpubkey instead of addresses - const network_t * network = &Mainnet; - char * addr = NULL; - char * addr2 = NULL; - uint8_t bytes[21]; - switch(script_type){ - case WALLY_SCRIPT_TYPE_P2WPKH: - wally_addr_segwit_from_bytes(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, network->bech32, 0, &addr); - wally_bip32_key_to_addr_segwit(pk, network->bech32, 0, &addr2); - break; - case WALLY_SCRIPT_TYPE_P2SH: - bytes[0] = network->p2sh; - memcpy(bytes+1, psbt->tx->outputs[i].script+2, 20); - wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr); - wally_bip32_key_to_address(pk, WALLY_ADDRESS_TYPE_P2SH_P2WPKH, network->p2sh, &addr2); - break; - case WALLY_SCRIPT_TYPE_P2PKH: - bytes[0] = network->p2pkh; - memcpy(bytes+1, psbt->tx->outputs[i].script+3, 20); - wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr); - wally_bip32_key_to_address(pk, WALLY_ADDRESS_TYPE_P2PKH, network->p2pkh, &addr2); - break; - default: - return 0; - } - int res = 0; - if(strcmp(addr, addr2) == 0){ - res = 1; - } - bip32_key_free(pk); - wally_free_string(addr2); - wally_free_string(addr); - return res; -} - -int wallet_sign_psbt(const wallet_t * wallet, struct wally_psbt * psbt, char ** output){ - const keystore_t * key = wallet->keystore; - size_t len; - struct wally_psbt * signed_psbt; - wally_psbt_init_alloc( - psbt->num_inputs, - psbt->num_outputs, - 0, - &signed_psbt); - wally_psbt_set_global_tx(psbt->tx, signed_psbt); - for(int i = 0; i < psbt->num_inputs; i++){ - if(!psbt->inputs[i].witness_utxo){ - return -1; - } - uint8_t hash[32]; - uint8_t script[25]; - - struct ext_key * pk = NULL; - int k = -1; - uint8_t h160[20]; - wally_hash160(key->root->pub_key, sizeof(key->root->pub_key), - h160, sizeof(h160)); - for(int j = 0; jinputs[i].keypaths->num_items; j++){ - if(memcmp(h160,psbt->inputs[i].keypaths->items[j].origin.fingerprint, 4) == 0){ - k = j; - break; - } - // if fingerprint is 00000000 force-use this derivation - uint8_t zero[4] = { 0 }; - if(memcmp(psbt->inputs[i].keypaths->items[j].origin.fingerprint, zero, 4)==0){ - k = j; - break; - } - } - if(k < 0){ - return -1; - } - bip32_key_from_parent_path_alloc(key->root, - psbt->inputs[i].keypaths->items[k].origin.path, - psbt->inputs[i].keypaths->items[k].origin.path_len, - BIP32_FLAG_KEY_PRIVATE, &pk); - - if(wallet->val == 0){ // single key - wally_scriptpubkey_p2pkh_from_bytes(pk->pub_key, EC_PUBLIC_KEY_LEN, - WALLY_SCRIPT_HASH160, - script, 25, - &len); - - wally_tx_get_btc_signature_hash(psbt->tx, i, - script, len, - psbt->inputs[i].witness_utxo->satoshi, - WALLY_SIGHASH_ALL, - WALLY_TX_FLAG_USE_WITNESS, - hash, 32 - ); - }else{ - if(psbt->inputs[i].witness_script == NULL){ - return -1; - } - wally_tx_get_btc_signature_hash(psbt->tx, i, - psbt->inputs[i].witness_script, - psbt->inputs[i].witness_script_len, - psbt->inputs[i].witness_utxo->satoshi, - WALLY_SIGHASH_ALL, - WALLY_TX_FLAG_USE_WITNESS, - hash, 32 - ); - } - - uint8_t sig[EC_SIGNATURE_LEN]; - wally_ec_sig_from_bytes( - pk->priv_key+1, 32, // first byte of ext_key.priv_key is 0x00 - hash, 32, - EC_FLAG_ECDSA, - sig, EC_SIGNATURE_LEN - ); - uint8_t der[EC_SIGNATURE_DER_MAX_LEN+1]; - wally_ec_sig_to_der( - sig, EC_SIGNATURE_LEN, - der, EC_SIGNATURE_DER_MAX_LEN, - &len - ); - der[len] = WALLY_SIGHASH_ALL; - if(!signed_psbt->inputs[i].partial_sigs){ - partial_sigs_map_init_alloc(1, &signed_psbt->inputs[i].partial_sigs); - } - add_new_partial_sig(signed_psbt->inputs[i].partial_sigs, - pk->pub_key, - der, len+1 - ); - bip32_key_free(pk); - } - - wally_psbt_to_base64(signed_psbt, output); - wally_psbt_free(signed_psbt); - return 0; -} - - -// maybe expose it to public interface -int keystore_get_wallets_number(const keystore_t * key, const network_t * network){ - char path[100]; - sprintf(path, "/internal/%s", key->fingerprint); - storage_maybe_mkdir(path); - sprintf(path, "/internal/%s/%s", key->fingerprint, network->name); - storage_maybe_mkdir(path); - // folder, extension - return storage_get_file_count(path, ".wallet"); -} - -static int keystore_get_wallet_name(const keystore_t * key, const network_t * network, int i, char ** wname){ - char path[100]; - sprintf(path, "/internal/%s/%s/%d.wallet", key->fingerprint, network->name, i); - FILE *f = fopen(path, "r"); - if(!f){ - return -1; - } - char w[100] = "Undefined"; - int m; int n; - fscanf(f, "name=%[^\n]\ntype=%*s\nm=%d\nn=%d", w, &m, &n); - sprintf(w+strlen(w), " (%d of %d)", m, n); - *wname = (char *)malloc(strlen(w)+1); - strcpy(*wname, w); - return strlen(wname); -} - -int keystore_get_wallets(const keystore_t * key, const network_t * network, char *** wallets){ - int num_wallets = keystore_get_wallets_number(key, network); - if(num_wallets < 0){ // error - return num_wallets; - } - char ** w = (char **)calloc(num_wallets+2, sizeof(char *)); - // first - default single key wallet - w[0] = (char *)calloc(30, sizeof(char)); - sprintf(w[0], "Default (single key)"); - // multisig wallets: - for(int i=1; i 0){ // free everything that is not empty - free(wallets[i]); - i++; - } - free(wallets[i]); // last one - free(wallets); // free the list - wallets = NULL; - return 0; -} - -int keystore_get_wallet(const keystore_t * key, const network_t * network, int val, wallet_t * wallet){ - wallet->val = val; - wallet->keystore = key; - wallet->network = network; - wallet->address = 0; - if(val == 0){ - sprintf(wallet->name, "Default (single key)"); - }else{ - char * wname; - keystore_get_wallet_name(key, network, val-1, &wname); - strcpy(wallet->name, wname); - free(wname); - } - return 0; -} - -int wallet_get_addresses(const wallet_t * wallet, char ** base58_addr, char ** bech32_addr){ - if(wallet->val == 0){ // single key - char path[50]; - sprintf(path, "m/84h/%dh/0h/0/%d", wallet->network->bip32, wallet->address); - keystore_get_addr(wallet->keystore, path, wallet->network, bech32_addr, KEYSTORE_BECH32_ADDRESS); - keystore_get_addr(wallet->keystore, path, wallet->network, base58_addr, KEYSTORE_BASE58_ADDRESS); - }else{ - uint8_t scriptpubkey[34]; - uint32_t derivation[2] = {0, wallet->address}; - wallet_get_scriptpubkey(wallet, derivation, 2, scriptpubkey, sizeof(scriptpubkey)); - wally_addr_segwit_from_bytes(scriptpubkey, sizeof(scriptpubkey), wallet->network->bech32, 0, bech32_addr); - uint8_t bytes[21]; - bytes[0] = wallet->network->p2sh; - wally_hash160(scriptpubkey, sizeof(scriptpubkey), bytes+1, 20); - wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, base58_addr); - } - return 0; -} - -int keystore_check_wallet(const keystore_t * keystore, const network_t * network, const char * buf){ - // FIXME: hardcoded lengths are bad... - char name[100]; - char type[10]; - int m; - int n; - char * rest; - rest = (char *)calloc(strlen(buf)+1, 1); - int res = sscanf(buf, "name=%[^\n]\ntype=%[^\n]\nm=%d\nn=%d\n%[^\0]", name, type, &m, &n, rest); - if(res < 5){ - return KEYSTORE_WALLET_ERR_PARSING; - } - char derivation[150]; - char xpub[150]; - char * line = rest; - int err = KEYSTORE_WALLET_ERR_NOT_INCLUDED; - for(int i=0; ifingerprint, 8) == 0){ - char * mypub; - char * myslippub; - res = keystore_get_xpub(keystore, derivation+9, network, 0, &mypub); - res = keystore_get_xpub(keystore, derivation+9, network, 1, &myslippub); - if(res < 0){ - free(rest); - return KEYSTORE_WALLET_ERR_WRONG_XPUB; - } - if((strcmp(mypub, xpub) == 0) || (strcmp(myslippub, xpub)==0)){ - wally_free_string(mypub); - wally_free_string(myslippub); - err = 0; - }else{ - wally_free_string(mypub); - wally_free_string(myslippub); - free(rest); - return KEYSTORE_WALLET_ERR_WRONG_XPUB; - } - } - line += strlen(derivation)+strlen(xpub)+3; - } - free(rest); - return err; -} - -int keystore_add_wallet(const keystore_t * keystore, const network_t * network, const char * buf, wallet_t * wallet){ - char path[100]; - sprintf(path, "/internal/%s", keystore->fingerprint); - storage_maybe_mkdir(path); - sprintf(path, "/internal/%s/%s", keystore->fingerprint, network->name); - storage_maybe_mkdir(path); - // folder, data, extension - return storage_push(path, buf, ".wallet"); -} - -int keystore_del_wallet(const keystore_t * keystore, const network_t * network, wallet_t * wallet){ - char path[100]; - sprintf(path, "/internal/%s/%s", keystore->fingerprint, network->name); - return storage_del(path, wallet->val-1, ".wallet"); -} - -int keystore_verify_address(const keystore_t * keystore, - const network_t * network, - const char * addr, - const uint32_t * path, - size_t path_len, - char ** wallet_name){ - // first we need to determine if address belongs to the network - uint8_t * buf = (uint8_t *)malloc(strlen(addr)); - size_t len; - int res = wally_base58_to_bytes(addr, BASE58_FLAG_CHECKSUM, buf, strlen(addr), &len); - uint8_t prefix = buf[0]; - free(buf); - int flag = KEYSTORE_BASE58_ADDRESS; - if(res!=WALLY_OK){ // invalid base58 - probably bech32 address - if(memcmp(addr, network->bech32, strlen(network->bech32)) != 0){ // wrong network - return KEYSTORE_ERR_WRONG_NETWORK; - } - flag = KEYSTORE_BECH32_ADDRESS; - }else{ - if(prefix != network->p2sh){ - return KEYSTORE_ERR_WRONG_NETWORK; - } - } - // now let's check default wallet - char * myaddr; - uint32_t * derivation = (uint32_t *)calloc(path_len+3, sizeof(uint32_t)); - for(int i=0; ibip32; - derivation[2] = BIP32_INITIAL_HARDENED_CHILD; - res = keystore_get_addr_path(keystore, derivation, path_len+3, network, &myaddr, flag); - free(derivation); - if(strcmp(myaddr, addr) == 0){ - *wallet_name = (char *)calloc(30, sizeof(char)); - sprintf(*wallet_name, "Default (single key)"); - return 0; - } - // go through all multisig wallets - wallet_t w; - int count = keystore_get_wallets_number(keystore, network); - for(int i=0; i -#include -#include "wally_bip32.h" -#include "wally_psbt.h" -#include "networks.h" - -#define KEYSTORE_BECH32_ADDRESS 1 -#define KEYSTORE_BASE58_ADDRESS 2 - -#define KEYSTORE_PSBTERR_CANNOT_SIGN 1 -#define KEYSTORE_PSBTERR_MIXED_INPUTS 2 -#define KEYSTORE_PSBTERR_WRONG_FIELDS 4 -#define KEYSTORE_PSBTERR_UNSUPPORTED_POLICY 8 - -#define KEYSTORE_WALLET_ERR_NOT_INCLUDED 1 -#define KEYSTORE_WALLET_ERR_PARSING 2 -#define KEYSTORE_WALLET_ERR_WRONG_XPUB 3 - -#define KEYSTORE_ERR_WRONG_NETWORK -1 -#define KEYSTORE_ERR_NOT_MINE -2 - -typedef struct { - struct ext_key * root; - char fingerprint[9]; -} keystore_t; - -typedef struct{ - int val; // file kinda - char name[100]; // name of the wallet - uint32_t address; // address index - const keystore_t * keystore; - const network_t * network; -} wallet_t; - -int keystore_init(const char * mnemonic, const char * password, keystore_t * key); -int keystore_get_xpub(const keystore_t * key, const char * derivation, const network_t * network, int use_slip132, char ** xpub); - -int keystore_get_addr_path(const keystore_t * key, const uint32_t * derivation, size_t len, const network_t * network, char ** addr, int flag); -int keystore_get_addr(const keystore_t * keystore, const char * derivation, const network_t * network, char ** addr, int flag); - -int keystore_check_psbt(const keystore_t * key, const network_t * network, const struct wally_psbt * psbt, wallet_t * wallet); -int wallet_sign_psbt(const wallet_t * wallet, struct wally_psbt * psbt, char ** output); - -/** allocates memory for wallet names - a list of char arrays ending with empty string "" - network is required to distinguish between wallets for mainnet, testnet and others - */ -// pointer to the pointer to the list of pointers... there should be a better way... -int keystore_get_wallets(const keystore_t * key, const network_t * network, char *** wallets); -/** frees memory allocated by the function above */ -int keystore_free_wallets(char ** wallets); - -int keystore_get_wallet(const keystore_t * key, const network_t * network, int val, wallet_t * wallet); -int wallet_get_addresses(const wallet_t * wallet, char ** base58_addr, char ** bech32_addr); -int wallet_output_is_change(const wallet_t * wallet, const struct wally_psbt * psbt, uint8_t i, char ** warning); - -int keystore_get_wallets_number(const keystore_t * key, const network_t * network); -/** adds wallet, returns wallet id, populates wallet with corresponding data */ -int keystore_check_wallet(const keystore_t * keystore, const network_t * network, const char * buf); -int keystore_add_wallet(const keystore_t * keystore, const network_t * network, const char * buf, wallet_t * wallet); -int keystore_del_wallet(const keystore_t * keystore, const network_t * network, wallet_t * wallet); -int keystore_verify_address(const keystore_t * keystore, const network_t * network, const char * addr, const uint32_t * path, size_t path_len, char ** wallet_name); -// int keystore_clear(keystore_t * key); -// int keystore_wipe() - -#ifdef __cplusplus -} -#endif - -#endif // __KEYSTORE_H__ \ No newline at end of file diff --git a/components/keystore/networks.c b/components/keystore/networks.c deleted file mode 100644 index 085f6bb..0000000 --- a/components/keystore/networks.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "networks.h" - -const network_t Mainnet = { - "Mainnet", - 0x80, // wif - 0x00, // p2pkh - 0x05, // p2sh - "bc", // bech32 - 0x0488ade4, // xprv - 0x0488b21e, // xpub - 0x049d7878, // yprv - 0x04b2430c, // zprv - 0x0295b005, // Yprv - 0x02aa7a99, // Zprv - 0x049d7cb2, // ypub - 0x04b24746, // zpub - 0x0295b43f, // Ypub - 0x02aa7ed3, // Zpub - 0 // bip32 coin type -}; - -const network_t Testnet = { - "Testnet", - 0xEF, // wif - 0x6F, // p2pkh - 0xC4, // p2sh - "tb", // bech32 - 0x04358394, // tprv - 0x043587cf, // tpub - 0x044a4e28, // uprv - 0x045f18bc, // vprv - 0x024285b5, // Uprv - 0x02575048, // Vprv - 0x044a5262, // upub - 0x045f1cf6, // vpub - 0x024289ef, // Upub - 0x02575483, // Vpub - 1 // bip32 coin type -}; - -const network_t Regtest = { - "Regtest", - 0xEF, // wif - 0x6F, // p2pkh - 0xC4, // p2sh - "bcrt", // bech32 - 0x04358394, // tprv - 0x043587cf, // tpub - 0x044a4e28, // uprv - 0x045f18bc, // vprv - 0x024285b5, // Uprv - 0x02575048, // Vprv - 0x044a5262, // upub - 0x045f1cf6, // vpub - 0x024289ef, // Upub - 0x02575483, // Vpub - 1 // bip32 coin type -}; - -const network_t Signet = { - "Signet", - 0xD9, // wif - 0x7D, // p2pkh - 0x57, // p2sh - "sb", // bech32 - 0x04358394, // tprv - 0x043587cf, // tpub - 0x044a4e28, // uprv - 0x045f18bc, // vprv - 0x024285b5, // Uprv - 0x02575048, // Vprv - 0x044a5262, // upub - 0x045f1cf6, // vpub - 0x024289ef, // Upub - 0x02575483, // Vpub - 1 // bip32 coin type -}; - -const network_t * networks[4] = { &Mainnet, &Testnet, &Regtest, &Signet }; diff --git a/components/keystore/networks.h b/components/keystore/networks.h deleted file mode 100644 index c94e3ba..0000000 --- a/components/keystore/networks.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef __NETWORKS_H__ -#define __NETWORKS_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -typedef struct { - char * name; // Testnet / Mainnet / Regtest / Signet - uint8_t wif; - // address prefixes - uint8_t p2pkh; - uint8_t p2sh; - char bech32[5]; - // bip32 prefixes - uint32_t xprv; - uint32_t xpub; - // slip132 prefixes - uint32_t yprv; - uint32_t zprv; - uint32_t Yprv; - uint32_t Zprv; - uint32_t ypub; - uint32_t zpub; - uint32_t Ypub; - uint32_t Zpub; - // coin type - uint32_t bip32; -} network_t; - -extern const network_t Mainnet; -extern const network_t Testnet; -extern const network_t Regtest; -extern const network_t Signet; - -#define NETWORKS_NUM 4 -extern const network_t * networks[NETWORKS_NUM]; - -#ifdef __cplusplus -} -#endif - -#endif //__NETWORKS_H__ \ No newline at end of file diff --git a/components/rng/rng.cpp b/components/rng/rng.cpp deleted file mode 100644 index 1cc34f2..0000000 --- a/components/rng/rng.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "rng.h" -#include "mbed.h" - -static RNG_HandleTypeDef rng; - -int rng_init(){ - __HAL_RCC_RNG_CLK_ENABLE(); - rng.Instance = RNG; - if(HAL_RNG_Init(&rng) != HAL_OK){ - return 0; - } - return 1; -} - -uint32_t rng_get_random_number(void){ - uint32_t rnd = 0xff; - if(HAL_RNG_GenerateRandomNumber(&rng, &rnd) != HAL_OK){ - return 0; - } - return rnd; -} - -size_t rng_get_random_buffer(uint8_t * arr, size_t len){ - for(unsigned i=0; 4*i -#include - -/** initializes TRNG */ -int rng_init(); - -/** generates a single random number */ -uint32_t rng_get_random_number(void); - -/** fills the buffer with random data */ -size_t rng_get_random_buffer(uint8_t * arr, size_t len); - - -#endif \ No newline at end of file diff --git a/components/storage/storage.cpp b/components/storage/storage.cpp deleted file mode 100644 index 827fde3..0000000 --- a/components/storage/storage.cpp +++ /dev/null @@ -1,362 +0,0 @@ -#include "storage.h" -#include "mbed.h" -#include "QSPIFBlockDevice.h" -#include "BlockDevice.h" -#include "LittleFileSystem.h" - -QSPIFBlockDevice bd(MBED_CONF_QSPIF_QSPI_IO0,MBED_CONF_QSPIF_QSPI_IO1,MBED_CONF_QSPIF_QSPI_IO2,MBED_CONF_QSPIF_QSPI_IO3, - MBED_CONF_QSPIF_QSPI_SCK,MBED_CONF_QSPIF_QSPI_CSN,MBED_CONF_QSPIF_QSPI_POLARITY_MODE,MBED_CONF_QSPIF_QSPI_FREQ); -LittleFileSystem fs("internal", &bd); - -int storage_erase(){ - // FIXME: should erase everything - // FIXME: should also zero the whole memory - int err = fs.reformat(NULL); - return err; - // return fs.remove("/internal/gui/calibration"); -} - -int storage_init(){ - int err = fs.mount(&bd); - printf("%s\r\n", (err ? "Fail :(" : "OK")); - if (err) { - printf("No filesystem found, formatting...\r\n"); - err = fs.reformat(&bd); - printf("%s\r\n", (err ? "Fail :(" : "OK")); - if (err) { - printf("error: %s (%d)\r\n", strerror(-err), err); - return err; - } - } - return STORAGE_OK; -} - -int storage_save_mnemonic(const char * mnemonic){ - FILE *f = fopen("/internal/mnemonic", "w"); - if(!f){ - return -1; - } - fprintf(f, mnemonic); - fclose(f); - return 0; -} - -int storage_load_mnemonic(char ** mnemonic){ - FILE *f = fopen("/internal/mnemonic", "r"); - if(!f){ - return -1; - } - // TODO: check length - char content[300]; - fscanf(f,"%[^\n]", content); - *mnemonic = (char *)malloc(strlen(content)+1); - strcpy(*mnemonic, content); - memset(content, 0, sizeof(content)); - fclose(f); - return 0; -} - -int storage_delete_mnemonic(){ - return remove("/internal/mnemonic"); -} - -int storage_maybe_mkdir(const char * path){ - DIR *d = opendir(path); - if(!d){ // doesnt exist - int err = mkdir(path, 0777); - return err; - } - return 0; -} - -int storage_get_file_count(const char * path, const char * extension){ - DIR *d = opendir(path); - if(!d){ - return -1; - } - int count = 0; - while(1){ - struct dirent *e = readdir(d); - if(!e){ - break; - } - if(strlen(e->d_name) >= strlen(extension)){ - if(strcmp(e->d_name + strlen(e->d_name) - strlen(extension), extension) == 0){ - count++; - } - } - } - closedir(d); - return count; -} - -static int get_available_file_id(const char * path, const char * extension){ - DIR *d = opendir(path); - if(!d){ - return -1; - } - int num = 0; - while(1){ - struct dirent *e = readdir(d); - if(!e){ - break; - } - if(strlen(e->d_name) >= strlen(extension)){ - if(strcmp(e->d_name + strlen(e->d_name) - strlen(extension), extension) == 0){ - char ext[20]; - int n; - sscanf(e->d_name, "%d.", &n); - if(num < n+1){ - num = n+1; - } - } - } - } - closedir(d); - return num; -} - -int storage_push(const char * path, const char * buf, const char * extension){ - int num = get_available_file_id(path, extension); - if(num < 0){ - return num; - } - char fname[100]; - sprintf(fname, "%s/%d%s", path, num, extension); - FILE *f = fopen(fname, "w"); - fprintf(f, buf); - fclose(f); - return num; -} - -int storage_del(const char * path, int num, const char * extension){ - int max = get_available_file_id(path, extension); - char fname[100]; - sprintf(fname, "%s/%d%s", path, num, extension); - printf("removing %s\r\n", fname); - int err = remove(fname); - if(err){ // failed to remove file - return err; - } - char newname[100]; - for(int i=num+1; id_name); - } - - printf("Closing the root directory... "); - int err = closedir(d); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } -} - -int save(const char * fname, const char * content){ - printf("Opening \"%s\"... ", fname); - char * fullname = (char *)calloc(strlen(fname)+5, sizeof(char)); - sprintf(fullname, "/internal/%s", fname); - FILE *f = fopen(fullname, "w+"); - free(fullname); - printf("%s\r\n", (!f ? "Fail :(" : "OK")); - if(!f){ - error("error: %s (%d)\r\n", strerror(errno), -errno); - return errno; - } - - int res = fprintf(f, "%s", content); - if (res < 0) { - printf("Fail :(\r\n"); - error("error: %s (%d)\r\n", strerror(errno), -errno); - return errno; - } - return res; -} - -bool dirExists(const char * dirname){ - char * fullname = (char *)calloc(strlen(dirname)+5, sizeof(char)); - sprintf(fullname, "/internal/%s", dirname); - DIR *d = opendir(fullname); - free(fullname); - return !!d; -} - -int makeDir(const char * dirname){ - char * fullname = (char *)calloc(strlen(dirname)+5, sizeof(char)); - sprintf(fullname, "/internal/%s", dirname); - int err = mkdir(fullname, 0777); - free(fullname); - return err; -} - -static int qspi_init(){ - int err = fs.mount(&bd); - printf("%s\r\n", (err ? "Fail :(" : "OK")); - if (err) { - // Reformat if we can't mount the filesystem - // this should only happen on the first boot - printf("No filesystem found, formatting...\r\n"); - err = fs.reformat(&bd); - printf("%s\r\n", (err ? "Fail :(" : "OK")); - if (err) { - error("error: %s (%d)\r\n", strerror(-err), err); - } - } - printf("Opening \"/internal/numbers.txt\"... "); - FILE *f = fopen("/internal/numbers.txt", "r+"); - printf("%s\r\n", (!f ? "Fail :(" : "OK")); - if (!f) { - // Create the numbers file if it doesn't exist - printf("No file found, creating a new file... "); - f = fopen("/internal/numbers.txt", "w+"); - printf("%s\r\n", (!f ? "Fail :(" : "OK")); - if (!f) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - for (int i = 0; i < 10; i++) { - printf("\rWriting numbers (%d/%d)... ", i, 10); - err = fprintf(f, " %d\r\n", i); - if (err < 0) { - printf("Fail :(\r\n"); - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - } - printf("\rWriting numbers (%d/%d)... OK\r\n", 10, 10); - - printf("Seeking file... "); - err = fseek(f, 0, SEEK_SET); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - } - - // Go through and increment the numbers - for (int i = 0; i < 10; i++) { - printf("\rIncrementing numbers (%d/%d)... ", i, 10); - - // Get current stream position - long pos = ftell(f); - - // Parse out the number and increment - int32_t number; - fscanf(f, "%d", &number); - number += 1; - - // Seek to beginning of number - fseek(f, pos, SEEK_SET); - - // Store number - fprintf(f, " %d\r\n", number); - - // Flush between write and read on same file - fflush(f); - } - printf("\rIncrementing numbers (%d/%d)... OK\r\n", 10, 10); - - // Close the file which also flushes any cached writes - printf("Closing \"/internal/numbers.txt\"... "); - err = fclose(f); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - // Display the root directory - printf("Opening the root directory... "); - DIR *d = opendir("/internal/"); - printf("%s\r\n", (!d ? "Fail :(" : "OK")); - if (!d) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - printf("root directory:\r\n"); - while (true) { - struct dirent *e = readdir(d); - if (!e) { - break; - } - - printf(" %s\r\n", e->d_name); - } - - printf("Closing the root directory... "); - err = closedir(d); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - // Display the numbers file - printf("Opening \"/internal/numbers.txt\"... "); - f = fopen("/internal/numbers.txt", "r"); - printf("%s\r\n", (!f ? "Fail :(" : "OK")); - if (!f) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - printf("numbers:\r\n"); - while (!feof(f)) { - int c = fgetc(f); - printf("%c", c); - } - - printf("\rClosing \"/internal/numbers.txt\"... "); - err = fclose(f); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(errno), -errno); - } - - // Tidy up - printf("Unmounting... "); - err = fs.unmount(); - printf("%s\r\n", (err < 0 ? "Fail :(" : "OK")); - if (err < 0) { - error("error: %s (%d)\r\n", strerror(-err), err); - } - - printf("Mbed OS filesystem example done!\r\n"); -} -#endif \ No newline at end of file diff --git a/components/storage/storage.h b/components/storage/storage.h deleted file mode 100644 index 50ff013..0000000 --- a/components/storage/storage.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef STORAGE_H -#define STORAGE_H - -#define STORAGE_OK 0 - -int storage_init(); -int storage_erase(); - -// reckless storage -int storage_save_mnemonic(const char * mnemonic); -int storage_load_mnemonic(char ** mnemonic); -int storage_delete_mnemonic(); - -#ifdef __cplusplus -extern "C" { -#endif - -int storage_maybe_mkdir(const char * path); -int storage_get_file_count(const char * path, const char * extension); -int storage_push(const char * path, const char * buf, const char * extension); -int storage_del(const char * path, int num, const char * extension); - -#ifdef __cplusplus -} -#endif - -// void listRoot(); -// int save(const char * fname, const char * content); -// bool dirExists(const char * dirname); -// int makeDir(const char * dirname); -// int erase(); - -#endif \ No newline at end of file diff --git a/gui/__init__.py b/gui/__init__.py new file mode 100644 index 0000000..d0a9523 --- /dev/null +++ b/gui/__init__.py @@ -0,0 +1,3 @@ +from .core import init, update, ioloop +from .screens import ask_pin, create_menu, show_progress +from .popups import alert, prompt, error, qr_alert diff --git a/gui/common.py b/gui/common.py new file mode 100644 index 0000000..29c14df --- /dev/null +++ b/gui/common.py @@ -0,0 +1,140 @@ +import lvgl as lv +import qrcode +import math +from micropython import const +import gc + +PADDING = const(30) +BTN_HEIGHT = const(80) +HOR_RES = const(480) +VER_RES = const(800) +QR_PADDING = const(40) + +styles = {} + +def init_styles(): + # Title style - just a default style with larger font + styles["title"] = lv.style_t() + lv.style_copy(styles["title"], lv.style_plain) + styles["title"].text.font = lv.font_roboto_28 + +def add_label(text, y=PADDING, scr=None, style=None): + """Helper functions that creates a title-styled label""" + if scr is None: + scr = lv.scr_act() + lbl = lv.label(scr) + lbl.set_text(text) + if style in styles: + lbl.set_style(0, styles[style]) + lbl.set_long_mode(lv.label.LONG.BREAK) + lbl.set_width(HOR_RES-2*PADDING) + lbl.set_x(PADDING) + lbl.set_align(lv.label.ALIGN.CENTER) + lbl.set_y(y) + return lbl + +def add_button(text, callback=None, scr=None, y=700): + """Helper function that creates a button with a text label""" + if scr is None: + scr = lv.scr_act() + btn = lv.btn(scr) + btn.set_width(HOR_RES-2*PADDING); + btn.set_height(BTN_HEIGHT); + + lbl = lv.label(btn) + lbl.set_text(text) + lbl.set_align(lv.label.ALIGN.CENTER) + + btn.align(scr, lv.ALIGN.IN_TOP_MID, 0, 0) + btn.set_y(y) + + if callback is not None: + btn.set_event_cb(callback) + + return btn + +def add_mnemonic(mnemonic, scr=None, y=200): + return add_label(mnemonic, y=y, scr=scr) + +def add_button_pair(text1, callback1, text2, callback2, scr=None, y=700): + """Helper function that creates a button with a text label""" + w = (HOR_RES-3*PADDING)//2 + btn = add_button(text1, callback1, scr=scr, y=y) + btn.set_width(w) + btn = add_button(text2, callback2, scr=scr, y=y) + btn.set_width(w) + btn.set_x(HOR_RES//2+PADDING//2) + return btn + +def add_qrcode(text, y=QR_PADDING, scr=None, style=None): + """Helper functions that creates a title-styled label""" + if scr is None: + scr = lv.scr_act() + + qr = qrcode.encode_to_string(text) + size = int(math.sqrt(len(qr))) + + scr = lv.scr_act() + # canvas = lv.canvas(scr) + + width = HOR_RES + scale = width//(size+4) + sizes = [1,2,3,5,7,10] + fontsize = [s for s in sizes if s < scale][-1] + font = getattr(lv, "square%d" % fontsize) + + lbl = add_label("Text", y=y, scr=scr) + style = lv.style_t() + lv.style_copy(style, lv.style_plain) + style.body.main_color = lv.color_make(0xFF,0xFF,0xFF) + style.body.grad_color = lv.color_make(0xFF,0xFF,0xFF) + style.body.opa = 255 + style.text.font = font + style.text.line_space = 0; + style.text.letter_space = 0; + lbl.set_style(0, style) + # lbl.set_body_draw(True) + lbl.set_text(qr) + del qr + gc.collect() + return lbl + +def table_set_mnemonic(table, mnemonic): + words = mnemonic.split() + for i in range(24): + row = i%12 + col = 1+2*(i//12) + if i < len(words): + table.set_cell_value(row, col, words[i]) + else: + table.set_cell_value(row, col, "") + +def add_mnemonic_table(mnemonic, y=PADDING, scr=None): + if scr is None: + scr = lv.scr_act() + num_style = lv.style_t() + lv.style_copy(num_style, lv.style_transp) + num_style.text.opa = lv.OPA._40 + + table = lv.table(scr) + table.set_col_cnt(4) + table.set_row_cnt(12) + table.set_col_width(0, 50) + table.set_col_width(2, 50) + table.set_col_width(1, 150) + table.set_col_width(3, 150) + + table.set_style(lv.page.STYLE.BG, lv.style_transp) + table.set_style(lv.table.STYLE.CELL1, lv.style_transp) + table.set_style(lv.table.STYLE.CELL2, num_style) + + for i in range(12): + table.set_cell_value(i, 0, "%d" % (i+1)) + table.set_cell_value(i, 2, "%d" % (i+13)) + table.set_cell_type(i, 0, lv.table.STYLE.CELL2) + table.set_cell_type(i, 2, lv.table.STYLE.CELL2) + table.align(scr, lv.ALIGN.IN_TOP_MID, 0, y) + + table_set_mnemonic(table, mnemonic) + + return table diff --git a/gui/core.py b/gui/core.py new file mode 100644 index 0000000..fdd2c1e --- /dev/null +++ b/gui/core.py @@ -0,0 +1,32 @@ +import lvgl as lv +import utime as time + +try: + import display +except: + from . import display_unixport as display + +from .common import init_styles +from .decorators import handle_queue + +def init(): + display.init() + + # Set theme + th = lv.theme_material_init(210, lv.font_roboto_22) + lv.theme_set_current(th) + + # Initialize the styles + init_styles() + + scr = lv.obj() + lv.scr_load(scr) + +def update(dt:int=30): + time.sleep_ms(dt) + display.update(dt) + handle_queue() + +def ioloop(dt:int=30): + while True: + update(dt) diff --git a/gui/decorators.py b/gui/decorators.py new file mode 100644 index 0000000..c4272c7 --- /dev/null +++ b/gui/decorators.py @@ -0,0 +1,23 @@ +import lvgl as lv + +queue = [] + +# decorators +def queued(func): + """A decorator to put a function in a queue + after lvgl update instead of calling it right away + """ + def wrapper(*args, **kwargs): + queue.append((func, args, kwargs)) + return wrapper + +def on_release(func): + def wrapper(o, e): + if e == lv.EVENT.RELEASED: + func() + return wrapper + +def handle_queue(): + while len(queue) > 0: + cb, args, kwargs = queue.pop() + cb(*args, **kwargs) \ No newline at end of file diff --git a/gui/display_unixport.py b/gui/display_unixport.py new file mode 100644 index 0000000..8d72f67 --- /dev/null +++ b/gui/display_unixport.py @@ -0,0 +1,38 @@ +import lvgl as lv +import SDL + +HOR_RES = 480 +VER_RES = 800 + +def init(*args, **kwargs): + """ + GUI initialization function. + Should be called once in the very beginning. + """ + + # init the gui library + lv.init() + # init the hardware library + SDL.init() + + # Register SDL display driver + disp_buf1 = lv.disp_buf_t() + buf1_1 = bytearray(HOR_RES*10) + lv.disp_buf_init(disp_buf1,buf1_1, None, len(buf1_1)//4) + disp_drv = lv.disp_drv_t() + lv.disp_drv_init(disp_drv) + disp_drv.buffer = disp_buf1 + disp_drv.flush_cb = SDL.monitor_flush + disp_drv.hor_res = HOR_RES + disp_drv.ver_res = VER_RES + lv.disp_drv_register(disp_drv) + + # Regsiter SDL mouse driver + indev_drv = lv.indev_drv_t() + lv.indev_drv_init(indev_drv) + indev_drv.type = lv.INDEV_TYPE.POINTER; + indev_drv.read_cb = SDL.mouse_read; + lv.indev_drv_register(indev_drv); + +def update(*args, **kwargs): + pass \ No newline at end of file diff --git a/gui/popups.py b/gui/popups.py new file mode 100644 index 0000000..bf27908 --- /dev/null +++ b/gui/popups.py @@ -0,0 +1,62 @@ +import lvgl as lv +from .common import * +from .decorators import * + +# pop-up screens +def alert(title, message, callback=None): + old_scr = lv.scr_act() + scr = lv.obj() + lv.scr_load(scr) + add_label(title, style="title") + add_label(message, y=PADDING+100) + def cb(obj, event): + if event == lv.EVENT.RELEASED: + lv.scr_load(old_scr) + if callback is not None: + callback() + add_button("OK", cb) + +def prompt(title, message, ok=None, cancel=None, **kwargs): + old_scr = lv.scr_act() + scr = lv.obj() + lv.scr_load(scr) + add_label(title, style="title") + add_label(message, y=PADDING+100) + + def cb_ok(obj, event): + if event == lv.EVENT.RELEASED: + lv.scr_load(old_scr) + if ok is not None: + ok(**kwargs) + + def cb_cancel(obj, event): + if event == lv.EVENT.RELEASED: + lv.scr_load(old_scr) + if cancel is not None: + cancel(**kwargs) + + add_button_pair( + "Cancel", cb_cancel, + "Confirm", cb_ok, + ) + +def error(message): + alert("Error!", message) + +def qr_alert(title, message, message_text=None, callback=None): + old_scr = lv.scr_act() + scr = lv.obj() + lv.scr_load(scr) + add_label(title, style="title") + obj = add_qrcode(message, scr=scr, y=PADDING+100) + if message_text is not None: + y = obj.get_y()+obj.get_height()+20 + add_label(message_text, y=y) + def cb(obj, event): + if event == lv.EVENT.RELEASED: + lv.scr_load(old_scr) + obj.delete() + print(gc.collect()) + if callback is not None: + callback() + add_button("OK", cb) diff --git a/gui/screens.py b/gui/screens.py new file mode 100644 index 0000000..002e2e4 --- /dev/null +++ b/gui/screens.py @@ -0,0 +1,250 @@ +import lvgl as lv +from .common import * +from .decorators import * + +# queued screens +@queued +def ask_pin(name, callback): + scr = lv.scr_act() + scr.clean() + add_label("Hello, %s!" % name) + add_label("Enter your PIN code", y=PADDING+30, style="title") + btnm = lv.btnm(scr) + btnm.set_map([ + "1","2","3","\n", + "4","5","6","\n", + "7","8","9","\n", + lv.SYMBOL.CLOSE,"0",lv.SYMBOL.OK,"" + ]) + btnm.set_width(HOR_RES) + btnm.set_height(HOR_RES) + btnm.align(scr, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + + pin_lbl = lv.ta(scr) + pin_lbl.set_text("") + pin_lbl.set_pwd_mode(True) + # pin_lbl.set_style(0, styles["title"]) + pin_lbl.set_width(HOR_RES-2*PADDING) + pin_lbl.set_x(PADDING) + pin_lbl.set_text_align(lv.label.ALIGN.CENTER) + pin_lbl.set_y(PADDING+150) + pin_lbl.set_cursor_type(lv.CURSOR.HIDDEN) + pin_lbl.set_one_line(True) + pin_lbl.set_pwd_show_time(0) + def cb(obj, event): + if event == lv.EVENT.RELEASED: + c = obj.get_active_btn_text() + if c == lv.SYMBOL.CLOSE: + pin_lbl.set_text("") + elif c == lv.SYMBOL.OK: + callback(pin_lbl.get_text()) + pin_lbl.set_text("") + else: + pin_lbl.add_text(c) + btnm.set_event_cb(cb); + +@queued +def create_menu(buttons=[], title="What do you want to do?", y0=100): + scr = lv.scr_act() + scr.clean() + add_label(title) + y = y0 + for text, callback in buttons: + add_button(text, on_release(callback), y=y) + y+=100 + +@queued +def show_progress(title, text, callback=None): + scr = lv.scr_act() + scr.clean() + add_label(title) + add_label(text, y=200) + if callback is not None: + add_button("Cancel", on_release(callback)) + +@queued +def new_mnemonic(mnemonic, + cb_continue, cb_back, cb_update=None, + title="Your new recovery phrase:"): + """Makes the new mnemonic screen with a slider to change number of words""" + scr = lv.scr_act() + scr.clean() + add_label(title) + table = add_mnemonic_table(mnemonic, y=100) + + if cb_update is not None: + wordcount = len(mnemonic.split()) + slider = lv.slider(scr) + slider.set_width(HOR_RES-2*PADDING) + slider.set_range(0, 4) + slider.set_pos(PADDING, 600) + slider.set_value((wordcount-12)//3, lv.ANIM.OFF) + lbl = add_label("Number of words: %d" % wordcount, y=550) + def cb_upd(obj, event): + if event == lv.EVENT.VALUE_CHANGED: + wordcount = slider.get_value()*3+12 + lbl.set_text("Number of words: %d" % wordcount) + mnemonic = cb_update(wordcount) + table_set_mnemonic(table, mnemonic) + slider.set_event_cb(cb_upd) + def cb_prev(obj, event): + if event == lv.EVENT.RELEASED: + cb_back() + def cb_next(obj, event): + if event == lv.EVENT.RELEASED: + cb_continue() + add_button_pair("Back", cb_prev, "Continue", cb_next) + +CHARSET = [ + "q","w","e","r","t","y","u","i","o","p","\n", + "#@","a","s","d","f","g","h","j","k","l","\n", + lv.SYMBOL.UP,"z","x","c","v","b","n","m",lv.SYMBOL.LEFT,"\n", + lv.SYMBOL.CLOSE+" Clear"," ",lv.SYMBOL.OK+" Done","" +] +CHARSET_EXTRA = [ + "1","2","3","4","5","6","7","8","9","0","\n", + "aA","@","#","$","_","&","-","+","(",")","/","\n", + "[","]","*","\"","'",":",";","!","?","\\",lv.SYMBOL.LEFT,"\n", + lv.SYMBOL.CLOSE+" Clear"," ",lv.SYMBOL.OK+" Done","" +] + +@queued +def ask_for_password(cb_continue, title="Enter your password (optional)"): + scr = lv.scr_act() + scr.clean() + add_label(title) + + btnm = lv.btnm(scr) + btnm.set_map(CHARSET) + btnm.set_width(HOR_RES) + btnm.set_height(VER_RES//3) + btnm.align(scr, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + + ta = lv.ta(scr) + ta.set_text("") + # ta.set_pwd_mode(True) + ta.set_width(HOR_RES-2*PADDING) + ta.set_x(PADDING) + ta.set_text_align(lv.label.ALIGN.CENTER) + ta.set_y(PADDING+150) + ta.set_cursor_type(lv.CURSOR.HIDDEN) + ta.set_one_line(True) + # ta.set_pwd_show_time(0) + def cb(obj, event): + if event == lv.EVENT.RELEASED: + c = obj.get_active_btn_text() + if c[0] == lv.SYMBOL.LEFT: + ta.del_char() + elif c == lv.SYMBOL.UP or c == lv.SYMBOL.DOWN: + for i,ch in enumerate(CHARSET): + if ch.isalpha(): + if c == lv.SYMBOL.UP: + CHARSET[i] = CHARSET[i].upper() + else: + CHARSET[i] = CHARSET[i].lower() + elif ch == lv.SYMBOL.UP: + CHARSET[i] = lv.SYMBOL.DOWN + elif ch == lv.SYMBOL.DOWN: + CHARSET[i] = lv.SYMBOL.UP + btnm.set_map(CHARSET) + elif c == "#@": + btnm.set_map(CHARSET_EXTRA) + elif c == "aA": + btnm.set_map(CHARSET) + elif c[0] == lv.SYMBOL.CLOSE: + ta.set_text("") + elif c[0] == lv.SYMBOL.OK: + cb_continue(ta.get_text()) + ta.set_text("") + else: + ta.add_text(c) + btnm.set_event_cb(cb) + +# global +words = [] + +@queued +def ask_for_mnemonic(cb_continue, cb_back, + check_mnemonic=None, words_lookup=None, + title="Enter your recovery phrase"): + scr = lv.scr_act() + scr.clean() + add_label(title) + table = add_mnemonic_table("", y=70) + + btnm = lv.btnm(scr) + btnm.set_map([ + "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",lv.SYMBOL.LEFT,"\n", + lv.SYMBOL.LEFT+" Back","Next word",lv.SYMBOL.OK+" Done","" + ]) + kb_dis_style = lv.style_t() + lv.style_copy(kb_dis_style, lv.style_btn_ina); + kb_dis_style.body.main_color = lv.color_make(0xe0,0xe0,0xe0) + kb_dis_style.body.grad_color = lv.color_make(0xe0,0xe0,0xe0) + kb_dis_style.body.radius = 0 + kb_dis_style.body.border.opa = 30 + btnm.set_style(lv.btnm.STYLE.BTN_INA, kb_dis_style) + + if words_lookup is not None: + # Next word button inactive + btnm.set_btn_ctrl(28, lv.btnm.CTRL.INACTIVE) + if check_mnemonic is not None: + # Done inactive + btnm.set_btn_ctrl(29, lv.btnm.CTRL.INACTIVE) + btnm.set_width(HOR_RES) + btnm.set_height(VER_RES//3) + btnm.align(scr, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + + def cb(obj, event): + global words + if event == lv.EVENT.RELEASED: + c = obj.get_active_btn_text() + if c == lv.SYMBOL.LEFT+" Back": + cb_back() + elif c == lv.SYMBOL.LEFT: + if len(words[-1]) > 0: + words[-1] = words[-1][:-1] + elif len(words) > 0: + words = words[:-1] + table_set_mnemonic(table, " ".join(words)) + elif c == "Next word": + if words_lookup is not None and len(words[-1])>=2: + candidates = words_lookup(words[-1]) + if len(candidates) == 1: + words[-1] = candidates[0] + words.append("") + table_set_mnemonic(table, " ".join(words)) + elif c == lv.SYMBOL.OK+" Done": + pass + else: + if len(words) == 0: + words.append("") + words[-1] = words[-1]+c + table_set_mnemonic(table, " ".join(words)) + + mnemonic = None + if words_lookup is not None: + btnm.set_btn_ctrl(28, lv.btnm.CTRL.INACTIVE) + if len(words) > 0 and len(words[-1])>=2: + candidates = words_lookup(words[-1]) + if len(candidates) == 1 or words[-1] in candidates: + btnm.clear_btn_ctrl(28, lv.btnm.CTRL.INACTIVE) + mnemonic = " ".join(words[:-1]) + if len(candidates) == 1: + mnemonic += " "+candidates[0] + else: + mnemonic += " "+words[-1] + else: + mnemonic = " ".join(words) + if check_mnemonic is not None and mnemonic is not None: + if check_mnemonic(mnemonic): + btnm.clear_btn_ctrl(29, lv.btnm.CTRL.INACTIVE) + else: + btnm.set_btn_ctrl(29, lv.btnm.CTRL.INACTIVE) + # if user was able to click this button then mnemonic is correct + if c == lv.SYMBOL.OK+" Done": + cb_continue(mnemonic) + + btnm.set_event_cb(cb); \ No newline at end of file diff --git a/keystore.py b/keystore.py new file mode 100644 index 0000000..251d3ed --- /dev/null +++ b/keystore.py @@ -0,0 +1,10 @@ +from bitcoin import bip32 + +class KeyStore: + def __init__(self, seed=None): + self.root = None + self.load_seed(seed) + + def load_seed(self, seed): + if seed is not None: + self.root = bip32.HDKey.from_seed(seed) \ No newline at end of file diff --git a/libraries/BSP_DISCO_F469NI.lib b/libraries/BSP_DISCO_F469NI.lib deleted file mode 100644 index 247522a..0000000 --- a/libraries/BSP_DISCO_F469NI.lib +++ /dev/null @@ -1 +0,0 @@ -https://os.mbed.com/users/stepansnigirev/code/BSP_DISCO_F469NI/#d8e068ff1ba1 diff --git a/libraries/QRScanner/src/QRScanner.cpp b/libraries/QRScanner/src/QRScanner.cpp deleted file mode 100755 index 89b38eb..0000000 --- a/libraries/QRScanner/src/QRScanner.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "QRScanner.h" - -QRScanner::QRScanner(PinName triggerPin, PinName txPin, PinName rxPin, int baudrate): - serial(txPin, rxPin, baudrate), trigger(triggerPin){ - trigger = 1; - serial.attach(this, &QRScanner::rx_interrupt, RawSerial::RxIrq); -}; - -int QRScanner::scan(char * buffer, size_t len, float timeout){ - rx_buffer = buffer; - buf_len = len; - qr_status = QR_SCANNING; - rx_cur = 0; - memset(rx_buffer, 0, sizeof(rx_buffer)); - Timer t; - t.start(); - trigger = 0; - float dt = 0; - while(qr_status == 0 && dt < timeout){ - dt = t.read(); - } - t.stop(); - trigger = 1; - if(dt >= timeout){ - qr_status = QR_TIMEOUT; - } - rx_buffer = NULL; - buf_len = 0; - return qr_status; -} -void QRScanner::setBuffer(char * buffer, size_t len){ - rx_buffer = buffer; - buf_len = len; - qr_status = QR_OK; - rx_cur = 0; - memset(rx_buffer, 0, sizeof(rx_buffer)); -} -int QRScanner::getStatus() const{ - int status = qr_status; - return status; -} -void QRScanner::rx_interrupt(){ - while(serial.readable() && rx_cur < buf_len-1){ - if(rx_buffer != NULL){ - rx_buffer[rx_cur] = serial.getc(); - rx_cur ++; - } - } - rx_buffer[rx_cur] = 0; // eol - if(rx_buffer[rx_cur-1] == 0x0d){ - rx_cur --; - rx_buffer[rx_cur] = 0; - if(qr_status == QR_SCANNING){ - qr_status = QR_OK; - }else{ - qr_status = QR_EXTERNAL; - rx_buffer = NULL; - buf_len = 0; - } - } - if(rx_cur >= buf_len-1){ - qr_status = QR_OVERFLOW; - } -} diff --git a/libraries/QRScanner/src/QRScanner.h b/libraries/QRScanner/src/QRScanner.h deleted file mode 100755 index 871dbd9..0000000 --- a/libraries/QRScanner/src/QRScanner.h +++ /dev/null @@ -1,67 +0,0 @@ -/** @file readline.h - * QRScanner class working with QR Scanner module - * for example MIKROE Barcode Click https://www.mikroe.com/barcode-click - * or Waveshare Barcode Scanner https://www.waveshare.com/barcode-scanner-module.htm - * - * Example: - * @code - * #include - * #include - * - * Serial pc(SERIAL_TX, SERIAL_RX, 115200); - * QRScanner qrscanner(D5, D1, D0); - * - * int main() { - * - * pc.printf("Ready to scan QR code\n"); - * - * while(1){ - * char c = pc.getc(); - * if(c=='s'){ - * char buffer[1000] = ""; - * int qr_status = qrscanner.scan(buffer, sizeof(buffer)); // By default 3s timeout - * - * if(qr_status == QR_OK){ - * pc.printf("Success! %s\n", buffer); - * } - * if(qr_status == QR_OVERFLOW){ - * pc.printf("Fail! Overflow! %s\n", buffer); - * } - * if(qr_status == QR_TIMEOUT){ - * pc.printf("Fail! Timeout! %s\n", buffer); - * } - * } - * } - * @endcode - */ -#ifndef __QRSCANNER_H__ -#define __QRSCANNER_H__ - -#include - -#define QR_EXTERNAL 2 // when qr scanner was triggered by the button -#define QR_OK 1 -#define QR_SCANNING 0 -#define QR_OVERFLOW -1 -#define QR_TIMEOUT -2 - -class QRScanner{ -private: - void rx_interrupt(); - char * rx_buffer; - size_t buf_len = 0; - volatile size_t rx_cur = 0; - volatile int qr_status = 0; - RawSerial serial; -public: - DigitalOut trigger; - // constructor. Trigger pin to start scanning, Tx pin, Rx pin and baudrate for serial port. - QRScanner(PinName triggerPin, PinName txPin, PinName rxPin, int baudrate=9600); - // tries to scan QR code and puts the result into buffer. - int scan(char * buffer, size_t len, float timeout=3); - // defines the buffer to use for qr scanning. - void setBuffer(char * buffer, size_t len); - int getStatus() const; -}; - -#endif // __QRSCANNER_H__ diff --git a/libraries/libwally-core.lib b/libraries/libwally-core.lib deleted file mode 100644 index a5e299b..0000000 --- a/libraries/libwally-core.lib +++ /dev/null @@ -1 +0,0 @@ -https://github.com/diybitcoinhardware/libwally-core/#c97bd35c982ff8cc986129929a35818aefc488ae diff --git a/libraries/lv_conf.h b/libraries/lv_conf.h deleted file mode 100644 index 3b5f478..0000000 --- a/libraries/lv_conf.h +++ /dev/null @@ -1,489 +0,0 @@ -/** - * @file lv_conf.h - * - */ - -/* - * COPY THIS FILE AS `lv_conf.h` NEXT TO the `lvgl` FOLDER - */ - -#if 1 /*Set it to "1" to enable content*/ - -#ifndef LV_CONF_H -#define LV_CONF_H -/* clang-format off */ - -#include - -/*==================== - Graphical settings - *====================*/ - -/* Maximal horizontal and vertical resolution to support by the library.*/ -#define LV_HOR_RES_MAX (480) -#define LV_VER_RES_MAX (800) - -/* Color depth: - * - 1: 1 byte per pixel - * - 8: RGB233 - * - 16: RGB565 - * - 32: ARGB8888 - */ -#define LV_COLOR_DEPTH 16 - -/* Swap the 2 bytes of RGB565 color. - * Useful if the display has a 8 bit interface (e.g. SPI)*/ -#define LV_COLOR_16_SWAP 0 - -/* 1: Enable screen transparency. - * Useful for OSD or other overlapping GUIs. - * Requires `LV_COLOR_DEPTH = 32` colors and the screen's style should be modified: `style.body.opa = ...`*/ -#define LV_COLOR_SCREEN_TRANSP 0 - -/*Images pixels with this color will not be drawn (with chroma keying)*/ -#define LV_COLOR_TRANSP LV_COLOR_LIME /*LV_COLOR_LIME: pure green*/ - -/* Enable anti-aliasing (lines, and radiuses will be smoothed) */ -#define LV_ANTIALIAS 1 - -/* Default display refresh period. - * Can be changed in the display driver (`lv_disp_drv_t`).*/ -#define LV_DISP_DEF_REFR_PERIOD 30 /*[ms]*/ - -/* Dot Per Inch: used to initialize default sizes. - * E.g. a button with width = LV_DPI / 2 -> half inch wide - * (Not so important, you can adjust it to modify default sizes and spaces)*/ -#define LV_DPI 100 /*[px]*/ - -/* Type of coordinates. Should be `int16_t` (or `int32_t` for extreme cases) */ -typedef int16_t lv_coord_t; - -/*========================= - Memory manager settings - *=========================*/ - -/* LittelvGL's internal memory manager's settings. - * The graphical objects and other related data are stored here. */ - -/* 1: use custom malloc/free, 0: use the built-in `lv_mem_alloc` and `lv_mem_free` */ -#define LV_MEM_CUSTOM 0 -#if LV_MEM_CUSTOM == 0 -/* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/ -# define LV_MEM_SIZE (32U * 1024U) - -/* Complier prefix for a big array declaration */ -# define LV_MEM_ATTR - -/* Set an address for the memory pool instead of allocating it as an array. - * Can be in external SRAM too. */ -# define LV_MEM_ADR 0 - -/* Automatically defrag. on free. Defrag. means joining the adjacent free cells. */ -# define LV_MEM_AUTO_DEFRAG 1 -#else /*LV_MEM_CUSTOM*/ -# define LV_MEM_CUSTOM_INCLUDE /*Header for the dynamic memory function*/ -# define LV_MEM_CUSTOM_ALLOC malloc /*Wrapper to malloc*/ -# define LV_MEM_CUSTOM_FREE free /*Wrapper to free*/ -#endif /*LV_MEM_CUSTOM*/ - -/* Garbage Collector settings - * Used if lvgl is binded to higher level language and the memory is managed by that language */ -#define LV_ENABLE_GC 0 -#if LV_ENABLE_GC != 0 -# define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/ -# define LV_MEM_CUSTOM_REALLOC your_realloc /*Wrapper to realloc*/ -# define LV_MEM_CUSTOM_GET_SIZE your_mem_get_size /*Wrapper to lv_mem_get_size*/ -#endif /* LV_ENABLE_GC */ - -/*======================= - Input device settings - *=======================*/ - -/* Input device default settings. - * Can be changed in the Input device driver (`lv_indev_drv_t`)*/ - -/* Input device read period in milliseconds */ -#define LV_INDEV_DEF_READ_PERIOD 30 - -/* Drag threshold in pixels */ -#define LV_INDEV_DEF_DRAG_LIMIT 10 - -/* Drag throw slow-down in [%]. Greater value -> faster slow-down */ -#define LV_INDEV_DEF_DRAG_THROW 20 - -/* Long press time in milliseconds. - * Time to send `LV_EVENT_LONG_PRESSSED`) */ -#define LV_INDEV_DEF_LONG_PRESS_TIME 400 - -/* Repeated trigger period in long press [ms] - * Time between `LV_EVENT_LONG_PRESSED_REPEAT */ -#define LV_INDEV_DEF_LONG_PRESS_REP_TIME 100 - -/*================== - * Feature usage - *==================*/ - -/*1: Enable the Animations */ -#define LV_USE_ANIMATION 1 -#if LV_USE_ANIMATION - -/*Declare the type of the user data of animations (can be e.g. `void *`, `int`, `struct`)*/ -typedef void * lv_anim_user_data_t; - -#endif - -/* 1: Enable shadow drawing*/ -#define LV_USE_SHADOW 1 - -/* 1: Enable object groups (for keyboard/encoder navigation) */ -#define LV_USE_GROUP 1 -#if LV_USE_GROUP -typedef void * lv_group_user_data_t; -#endif /*LV_USE_GROUP*/ - -/* 1: Enable GPU interface*/ -#define LV_USE_GPU 1 - -/* 1: Enable file system (might be required for images */ -#define LV_USE_FILESYSTEM 1 -#if LV_USE_FILESYSTEM -/*Declare the type of the user data of file system drivers (can be e.g. `void *`, `int`, `struct`)*/ -typedef void * lv_fs_drv_user_data_t; -#endif - -/*1: Add a `user_data` to drivers and objects*/ -#define LV_USE_USER_DATA 1 - -/*======================== - * Image decoder and cache - *========================*/ - -/* 1: Enable indexed (palette) images */ -#define LV_IMG_CF_INDEXED 1 - -/* 1: Enable alpha indexed images */ -#define LV_IMG_CF_ALPHA 1 - -/* Default image cache size. Image caching keeps the images opened. - * If only the built-in image formats are used there is no real advantage of caching. - * (I.e. no new image decoder is added) - * With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. - * However the opened images might consume additional RAM. - * LV_IMG_CACHE_DEF_SIZE must be >= 1 */ -#define LV_IMG_CACHE_DEF_SIZE 1 - -/*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/ -typedef void * lv_img_decoder_user_data_t; - -/*===================== - * Compiler settings - *====================*/ -/* Define a custom attribute to `lv_tick_inc` function */ -#define LV_ATTRIBUTE_TICK_INC - -/* Define a custom attribute to `lv_task_handler` function */ -#define LV_ATTRIBUTE_TASK_HANDLER - -/* With size optimization (-Os) the compiler might not align data to - * 4 or 8 byte boundary. This alignment will be explicitly applied where needed. - * E.g. __attribute__((aligned(4))) */ -#define LV_ATTRIBUTE_MEM_ALIGN - -/* Attribute to mark large constant arrays for example - * font's bitmaps */ -#define LV_ATTRIBUTE_LARGE_CONST - -/*=================== - * HAL settings - *==================*/ - -/* 1: use a custom tick source. - * It removes the need to manually update the tick with `lv_tick_inc`) */ -#define LV_TICK_CUSTOM 0 -#if LV_TICK_CUSTOM == 1 -#define LV_TICK_CUSTOM_INCLUDE "something.h" /*Header for the sys time function*/ -#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current systime in ms*/ -#endif /*LV_TICK_CUSTOM*/ - -typedef void * lv_disp_drv_user_data_t; /*Type of user data in the display driver*/ -typedef void * lv_indev_drv_user_data_t; /*Type of user data in the input device driver*/ - -/*================ - * Log settings - *===============*/ - -/*1: Enable the log module*/ -#define LV_USE_LOG 0 -#if LV_USE_LOG -/* How important log should be added: - * LV_LOG_LEVEL_TRACE A lot of logs to give detailed information - * LV_LOG_LEVEL_INFO Log important events - * LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem - * LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail - */ -# define LV_LOG_LEVEL LV_LOG_LEVEL_WARN - -/* 1: Print the log with 'printf'; - * 0: user need to register a callback with `lv_log_register_print`*/ -# define LV_LOG_PRINTF 0 -#endif /*LV_USE_LOG*/ - -/*================ - * THEME USAGE - *================*/ -#define LV_THEME_LIVE_UPDATE 0 /*1: Allow theme switching at run time. Uses 8..10 kB of RAM*/ - -#define LV_USE_THEME_TEMPL 0 /*Just for test*/ -#define LV_USE_THEME_DEFAULT 1 /*Built mainly from the built-in styles. Consumes very few RAM*/ -#define LV_USE_THEME_ALIEN 1 /*Dark futuristic theme*/ -#define LV_USE_THEME_NIGHT 1 /*Dark elegant theme*/ -#define LV_USE_THEME_MONO 1 /*Mono color theme for monochrome displays*/ -#define LV_USE_THEME_MATERIAL 1 /*Flat theme with bold colors and light shadows*/ -#define LV_USE_THEME_ZEN 1 /*Peaceful, mainly light theme */ -#define LV_USE_THEME_NEMO 1 /*Water-like theme based on the movie "Finding Nemo"*/ - -/*================== - * FONT USAGE - *===================*/ - -/* The built-in fonts contains the ASCII range and some Symbols with 4 bit-per-pixel. - * The symbols are available via `LV_SYMBOL_...` defines - * More info about fonts: https://docs.littlevgl.com/#Fonts - * To create a new font go to: https://littlevgl.com/ttf-font-to-c-array - */ - -/* Robot fonts with bpp = 4 - * https://fonts.google.com/specimen/Roboto */ -#define LV_FONT_ROBOTO_12 1 -#define LV_FONT_ROBOTO_16 1 -#define LV_FONT_ROBOTO_22 1 -#define LV_FONT_ROBOTO_28 1 - -/*Pixel perfect monospace font - * http://pelulamu.net/unscii/ */ -#define LV_FONT_UNSCII_8 0 - -/* Optionally declare your custom fonts here. - * You can use these fonts as default font too - * and they will be available globally. E.g. - * #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) \ - * LV_FONT_DECLARE(my_font_2) - */ -#define LV_FONT_CUSTOM_DECLARE - -/*Always set a default font from the built-in fonts*/ -#define LV_FONT_DEFAULT &lv_font_roboto_22 - -/*Declare the type of the user data of fonts (can be e.g. `void *`, `int`, `struct`)*/ -typedef void * lv_font_user_data_t; - -/*================= - * Text settings - *=================*/ - -/* Select a character encoding for strings. - * Your IDE or editor should have the same character encoding - * - LV_TXT_ENC_UTF8 - * - LV_TXT_ENC_ASCII - * */ -#define LV_TXT_ENC LV_TXT_ENC_UTF8 - - /*Can break (wrap) texts on these chars*/ -#define LV_TXT_BREAK_CHARS " ,.;:-_" - -/*=================== - * LV_OBJ SETTINGS - *==================*/ - -/*Declare the type of the user data of object (can be e.g. `void *`, `int`, `struct`)*/ -typedef int lv_obj_user_data_t; - -/*1: enable `lv_obj_realaign()` based on `lv_obj_align()` parameters*/ -#define LV_USE_OBJ_REALIGN 1 - -/* Enable to make the object clickable on a larger area. - * LV_EXT_CLICK_AREA_OFF or 0: Disable this feature - * LV_EXT_CLICK_AREA_TINY: The extra area can be adjusted horizontally and vertically (0..255 px) - * LV_EXT_CLICK_AREA_FULL: The extra area can be adjusted in all 4 directions (-32k..+32k px) - */ -#define LV_USE_EXT_CLICK_AREA LV_EXT_CLICK_AREA_OFF - -/*================== - * LV OBJ X USAGE - *================*/ -/* - * Documentation of the object types: https://docs.littlevgl.com/#Object-types - */ - -/*Arc (dependencies: -)*/ -#define LV_USE_ARC 1 - -/*Bar (dependencies: -)*/ -#define LV_USE_BAR 1 - -/*Button (dependencies: lv_cont*/ -#define LV_USE_BTN 1 -#if LV_USE_BTN != 0 -/*Enable button-state animations - draw a circle on click (dependencies: LV_USE_ANIMATION)*/ -# define LV_BTN_INK_EFFECT 1 -#endif - -/*Button matrix (dependencies: -)*/ -#define LV_USE_BTNM 1 - -/*Calendar (dependencies: -)*/ -#define LV_USE_CALENDAR 1 - -/*Canvas (dependencies: lv_img)*/ -#define LV_USE_CANVAS 1 - -/*Check box (dependencies: lv_btn, lv_label)*/ -#define LV_USE_CB 1 - -/*Chart (dependencies: -)*/ -#define LV_USE_CHART 1 -#if LV_USE_CHART -# define LV_CHART_AXIS_TICK_LABEL_MAX_LEN 20 -#endif - -/*Container (dependencies: -*/ -#define LV_USE_CONT 1 - -/*Drop down list (dependencies: lv_page, lv_label, lv_symbol_def.h)*/ -#define LV_USE_DDLIST 1 -#if LV_USE_DDLIST != 0 -/*Open and close default animation time [ms] (0: no animation)*/ -# define LV_DDLIST_DEF_ANIM_TIME 50 -#endif - -/*Gauge (dependencies:lv_bar, lv_lmeter)*/ -#define LV_USE_GAUGE 1 - -/*Image (dependencies: lv_label*/ -#define LV_USE_IMG 1 - -/*Image Button (dependencies: lv_btn*/ -#define LV_USE_IMGBTN 1 -#if LV_USE_IMGBTN -/*1: The imgbtn requires left, mid and right parts and the width can be set freely*/ -# define LV_IMGBTN_TILED 0 -#endif - -/*Keyboard (dependencies: lv_btnm)*/ -#define LV_USE_KB 1 - -/*Label (dependencies: -*/ -#define LV_USE_LABEL 1 -#if LV_USE_LABEL != 0 -/*Hor, or ver. scroll speed [px/sec] in 'LV_LABEL_LONG_ROLL/ROLL_CIRC' mode*/ -# define LV_LABEL_DEF_SCROLL_SPEED 25 - -/* Waiting period at beginning/end of animation cycle */ -# define LV_LABEL_WAIT_CHAR_COUNT 3 - -/*Enable selecting text of the label */ -# define LV_LABEL_TEXT_SEL 0 - -/*Store extra some info in labels (12 bytes) to speed up drawing of very long texts*/ -# define LV_LABEL_LONG_TXT_HINT 0 -#endif - -/*LED (dependencies: -)*/ -#define LV_USE_LED 1 - -/*Line (dependencies: -*/ -#define LV_USE_LINE 1 - -/*List (dependencies: lv_page, lv_btn, lv_label, (lv_img optionally for icons ))*/ -#define LV_USE_LIST 1 -#if LV_USE_LIST != 0 -/*Default animation time of focusing to a list element [ms] (0: no animation) */ -# define LV_LIST_DEF_ANIM_TIME 100 -#endif - -/*Line meter (dependencies: *;)*/ -#define LV_USE_LMETER 1 - -/*Message box (dependencies: lv_rect, lv_btnm, lv_label)*/ -#define LV_USE_MBOX 1 - -/*Page (dependencies: lv_cont)*/ -#define LV_USE_PAGE 1 -#if LV_USE_PAGE != 0 -/*Focus default animation time [ms] (0: no animation)*/ -# define LV_PAGE_DEF_ANIM_TIME 400 -#endif - -/*Preload (dependencies: lv_arc, lv_anim)*/ -#define LV_USE_PRELOAD 1 -#if LV_USE_PRELOAD != 0 -# define LV_PRELOAD_DEF_ARC_LENGTH 60 /*[deg]*/ -# define LV_PRELOAD_DEF_SPIN_TIME 1000 /*[ms]*/ -# define LV_PRELOAD_DEF_ANIM LV_PRELOAD_TYPE_SPINNING_ARC -#endif - -/*Roller (dependencies: lv_ddlist)*/ -#define LV_USE_ROLLER 1 -#if LV_USE_ROLLER != 0 -/*Focus animation time [ms] (0: no animation)*/ -# define LV_ROLLER_DEF_ANIM_TIME 200 - -/*Number of extra "pages" when the roller is infinite*/ -# define LV_ROLLER_INF_PAGES 7 -#endif - -/*Slider (dependencies: lv_bar)*/ -#define LV_USE_SLIDER 1 - -/*Spinbox (dependencies: lv_ta)*/ -#define LV_USE_SPINBOX 1 - -/*Switch (dependencies: lv_slider)*/ -#define LV_USE_SW 1 - -/*Text area (dependencies: lv_label, lv_page)*/ -#define LV_USE_TA 1 -#if LV_USE_TA != 0 -# define LV_TA_DEF_CURSOR_BLINK_TIME 400 /*ms*/ -# define LV_TA_DEF_PWD_SHOW_TIME 1500 /*ms*/ -#endif - -/*Table (dependencies: lv_label)*/ -#define LV_USE_TABLE 1 -#if LV_USE_TABLE -# define LV_TABLE_COL_MAX 12 -#endif - -/*Tab (dependencies: lv_page, lv_btnm)*/ -#define LV_USE_TABVIEW 1 -# if LV_USE_TABVIEW != 0 -/*Time of slide animation [ms] (0: no animation)*/ -# define LV_TABVIEW_DEF_ANIM_TIME 300 -#endif - -/*Tileview (dependencies: lv_page) */ -#define LV_USE_TILEVIEW 1 -#if LV_USE_TILEVIEW -/*Time of slide animation [ms] (0: no animation)*/ -# define LV_TILEVIEW_DEF_ANIM_TIME 300 -#endif - -/*Window (dependencies: lv_cont, lv_btn, lv_label, lv_img, lv_page)*/ -#define LV_USE_WIN 1 - -/*================== - * Non-user section - *==================*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) /* Disable warnings for Visual Studio*/ -# define _CRT_SECURE_NO_WARNINGS -#endif - -/*--END OF LV_CONF_H--*/ - -/*Be sure every define has a default value*/ -#include "lvgl/src/lv_conf_checker.h" - -#endif /*LV_CONF_H*/ - -#endif /*End of "Content enable"*/ diff --git a/libraries/lvgl.lib b/libraries/lvgl.lib deleted file mode 100644 index ea6989a..0000000 --- a/libraries/lvgl.lib +++ /dev/null @@ -1 +0,0 @@ -https://github.com/littlevgl/lvgl/#ebb29f09fbf47faaf894211f248a503dd632eb59 diff --git a/libraries/qrcode/qrcode.c b/libraries/qrcode/qrcode.c deleted file mode 100644 index 940c06e..0000000 --- a/libraries/qrcode/qrcode.c +++ /dev/null @@ -1,872 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2017 Richard Moore - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - -#include "qrcode.h" - -#include -#include - -#pragma mark - Error Correction Lookup tables - -#if LOCK_VERSION == 0 - -static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium - { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low - { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High - { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile -}; - -static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { - // Version: (note that index 0 is for padding, and is set to an illegal value) - // 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High - { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile -}; - -static const uint16_t NUM_RAW_DATA_MODULES[40] = { - // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, - // 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, - // 32, 33, 34, 35, 36, 37, 38, 39, 40 - 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 -}; - -// @TODO: Put other LOCK_VERSIONS here -#elif LOCK_VERSION == 3 - -static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = { - 26, 15, 44, 36 -}; - -static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = { - 1, 1, 2, 2 -}; - -static const uint16_t NUM_RAW_DATA_MODULES = 567; - -#else - -#error Unsupported LOCK_VERSION (add it...) - -#endif - - -static int max(int a, int b) { - if (a > b) { return a; } - return b; -} - -/* -static int abs(int value) { - if (value < 0) { return -value; } - return value; -} -*/ - - -#pragma mark - Mode testing and conversion - -static int8_t getAlphanumeric(char c) { - - if (c >= '0' && c <= '9') { return (c - '0'); } - if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); } - - switch (c) { - case ' ': return 36; - case '$': return 37; - case '%': return 38; - case '*': return 39; - case '+': return 40; - case '-': return 41; - case '.': return 42; - case '/': return 43; - case ':': return 44; - } - - return -1; -} - -static bool isAlphanumeric(const char *text, uint16_t length) { - while (length != 0) { - if (getAlphanumeric(text[--length]) == -1) { return false; } - } - return true; -} - - -static bool isNumeric(const char *text, uint16_t length) { - while (length != 0) { - char c = text[--length]; - if (c < '0' || c > '9') { return false; } - } - return true; -} - - -#pragma mark - Counting - -// We store the following tightly packed (less 8) in modeInfo -// <=9 <=26 <= 40 -// NUMERIC ( 10, 12, 14); -// ALPHANUMERIC ( 9, 11, 13); -// BYTE ( 8, 16, 16); -static char getModeBits(uint8_t version, uint8_t mode) { - // Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits - // hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2)) - unsigned int modeInfo = 0x7bbb80a; - -#if LOCK_VERSION == 0 || LOCK_VERSION > 9 - if (version > 9) { modeInfo >>= 9; } -#endif - -#if LOCK_VERSION == 0 || LOCK_VERSION > 26 - if (version > 26) { modeInfo >>= 9; } -#endif - - char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); - if (result == 15) { result = 16; } - - return result; -} - - -#pragma mark - BitBucket - -typedef struct BitBucket { - uint32_t bitOffsetOrWidth; - uint16_t capacityBytes; - uint8_t *data; -} BitBucket; - -/* -void bb_dump(BitBucket *bitBuffer) { - printf("Buffer: "); - for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) { - printf("%02x", bitBuffer->data[i]); - if ((i % 4) == 3) { printf(" "); } - } - printf("\n"); -} -*/ - -static uint16_t bb_getGridSizeBytes(uint8_t size) { - return (((size * size) + 7) / 8); -} - -static uint16_t bb_getBufferSizeBytes(uint32_t bits) { - return ((bits + 7) / 8); -} - -static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) { - bitBuffer->bitOffsetOrWidth = 0; - bitBuffer->capacityBytes = capacityBytes; - bitBuffer->data = data; - - memset(data, 0, bitBuffer->capacityBytes); -} - -static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) { - bitGrid->bitOffsetOrWidth = size; - bitGrid->capacityBytes = bb_getGridSizeBytes(size); - bitGrid->data = data; - - memset(data, 0, bitGrid->capacityBytes); -} - -static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) { - uint32_t offset = bitBuffer->bitOffsetOrWidth; - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } - bitBuffer->bitOffsetOrWidth = offset; -} -/* -void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) { - for (int8_t i = length - 1; i >= 0; i--, offset++) { - bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); - } -} -*/ -static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - if (on) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - uint8_t mask = 1 << (7 - (offset & 0x07)); - bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); - if (on ^ invert) { - bitGrid->data[offset >> 3] |= mask; - } else { - bitGrid->data[offset >> 3] &= ~mask; - } -} - -static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) { - uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; - return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - - -#pragma mark - Drawing Patterns - -// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical -// properties, calling applyMask(m) twice with the same value is equivalent to no change at all. -// This means it is possible to apply a mask, undo it, and try another mask. Note that a final -// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.). -static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) { - uint8_t size = modules->bitOffsetOrWidth; - - for (uint8_t y = 0; y < size; y++) { - for (uint8_t x = 0; x < size; x++) { - if (bb_getBit(isFunction, x, y)) { continue; } - - bool invert = 0; - switch (mask) { - case 0: invert = (x + y) % 2 == 0; break; - case 1: invert = y % 2 == 0; break; - case 2: invert = x % 3 == 0; break; - case 3: invert = (x + y) % 3 == 0; break; - case 4: invert = (x / 3 + y / 2) % 2 == 0; break; - case 5: invert = x * y % 2 + x * y % 3 == 0; break; - case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; - case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; - } - bb_invertBit(modules, x, y, invert); - } - } -} - -static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) { - bb_setBit(modules, x, y, on); - bb_setBit(isFunction, x, y, true); -} - -// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y). -static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { - uint8_t size = modules->bitOffsetOrWidth; - - for (int8_t i = -4; i <= 4; i++) { - for (int8_t j = -4; j <= 4; j++) { - uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm - int16_t xx = x + j, yy = y + i; - if (0 <= xx && xx < size && 0 <= yy && yy < size) { - setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); - } - } - } -} - -// Draws a 5*5 alignment pattern, with the center module at (x, y). -static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { - for (int8_t i = -2; i <= 2; i++) { - for (int8_t j = -2; j <= 2; j++) { - setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); - } - } -} - -// Draws two copies of the format bits (with its own error correction code) -// based on the given mask and this object's error correction level field. -static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) { - - uint8_t size = modules->bitOffsetOrWidth; - - // Calculate error correction code and pack bits - uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3 - uint32_t rem = data; - for (int i = 0; i < 10; i++) { - rem = (rem << 1) ^ ((rem >> 9) * 0x537); - } - - data = data << 10 | rem; - data ^= 0x5412; // uint15 - - // Draw first copy - for (uint8_t i = 0; i <= 5; i++) { - setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); - setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); - setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); - - for (int8_t i = 9; i < 15; i++) { - setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); - } - - // Draw second copy - for (int8_t i = 0; i <= 7; i++) { - setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); - } - - for (int8_t i = 8; i < 15; i++) { - setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); - } - - setFunctionModule(modules, isFunction, 8, size - 8, true); -} - - -// Draws two copies of the version bits (with its own error correction code), -// based on this object's version field (which only has an effect for 7 <= version <= 40). -static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) { - - int8_t size = modules->bitOffsetOrWidth; - -#if LOCK_VERSION != 0 && LOCK_VERSION < 7 - return; - -#else - if (version < 7) { return; } - - // Calculate error correction code and pack bits - uint32_t rem = version; // version is uint6, in the range [7, 40] - for (uint8_t i = 0; i < 12; i++) { - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); - } - - uint32_t data = version << 12 | rem; // uint18 - - // Draw two copies - for (uint8_t i = 0; i < 18; i++) { - bool bit = ((data >> i) & 1) != 0; - uint8_t a = size - 11 + i % 3, b = i / 3; - setFunctionModule(modules, isFunction, a, b, bit); - setFunctionModule(modules, isFunction, b, a, bit); - } - -#endif -} - -static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) { - - uint8_t size = modules->bitOffsetOrWidth; - - // Draw the horizontal and vertical timing patterns - for (uint8_t i = 0; i < size; i++) { - setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); - setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); - } - - // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) - drawFinderPattern(modules, isFunction, 3, 3); - drawFinderPattern(modules, isFunction, size - 4, 3); - drawFinderPattern(modules, isFunction, 3, size - 4); - -#if LOCK_VERSION == 0 || LOCK_VERSION > 1 - - if (version > 1) { - - // Draw the numerous alignment patterns - - uint8_t alignCount = version / 7 + 2; - uint8_t step; - if (version != 32) { - step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2 - } else { // C-C-C-Combo breaker! - step = 26; - } - - uint8_t alignPositionIndex = alignCount - 1; - uint8_t alignPosition[alignCount]; - - alignPosition[0] = 6; - - uint8_t size = version * 4 + 17; - for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) { - alignPosition[alignPositionIndex--] = pos; - } - - for (uint8_t i = 0; i < alignCount; i++) { - for (uint8_t j = 0; j < alignCount; j++) { - if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) { - continue; // Skip the three finder corners - } else { - drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); - } - } - } - } - -#endif - - // Draw configuration data - drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor - drawVersion(modules, isFunction, version); -} - - -// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire -// data area of this QR Code symbol. Function modules need to be marked off before this is called. -static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) { - - uint32_t bitLength = codewords->bitOffsetOrWidth; - uint8_t *data = codewords->data; - - uint8_t size = modules->bitOffsetOrWidth; - - // Bit index into the data - uint32_t i = 0; - - // Do the funny zigzag scan - for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair - if (right == 6) { right = 5; } - - for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter - for (int j = 0; j < 2; j++) { - uint8_t x = right - j; // Actual x coordinate - bool upwards = ((right & 2) == 0) ^ (x < 6); - uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate - if (!bb_getBit(isFunction, x, y) && i < bitLength) { - bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); - i++; - } - // If there are any remainder bits (0 to 7), they are already - // set to 0/false/white when the grid of modules was initialized - } - } - } -} - - - -#pragma mark - Penalty Calculation - -#define PENALTY_N1 3 -#define PENALTY_N2 3 -#define PENALTY_N3 40 -#define PENALTY_N4 10 - -// Calculates and returns the penalty score based on state of this QR Code's current modules. -// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. -// @TODO: This can be optimized by working with the bytes instead of bits. -static uint32_t getPenaltyScore(BitBucket *modules) { - uint32_t result = 0; - - uint8_t size = modules->bitOffsetOrWidth; - - // Adjacent modules in row having same color - for (uint8_t y = 0; y < size; y++) { - - bool colorX = bb_getBit(modules, 0, y); - for (uint8_t x = 1, runX = 1; x < size; x++) { - bool cx = bb_getBit(modules, x, y); - if (cx != colorX) { - colorX = cx; - runX = 1; - - } else { - runX++; - if (runX == 5) { - result += PENALTY_N1; - } else if (runX > 5) { - result++; - } - } - } - } - - // Adjacent modules in column having same color - for (uint8_t x = 0; x < size; x++) { - bool colorY = bb_getBit(modules, x, 0); - for (uint8_t y = 1, runY = 1; y < size; y++) { - bool cy = bb_getBit(modules, x, y); - if (cy != colorY) { - colorY = cy; - runY = 1; - } else { - runY++; - if (runY == 5) { - result += PENALTY_N1; - } else if (runY > 5) { - result++; - } - } - } - } - - uint16_t black = 0; - for (uint8_t y = 0; y < size; y++) { - uint16_t bitsRow = 0, bitsCol = 0; - for (uint8_t x = 0; x < size; x++) { - bool color = bb_getBit(modules, x, y); - - // 2*2 blocks of modules having same color - if (x > 0 && y > 0) { - bool colorUL = bb_getBit(modules, x - 1, y - 1); - bool colorUR = bb_getBit(modules, x, y - 1); - bool colorL = bb_getBit(modules, x - 1, y); - if (color == colorUL && color == colorUR && color == colorL) { - result += PENALTY_N2; - } - } - - // Finder-like pattern in rows and columns - bitsRow = ((bitsRow << 1) & 0x7FF) | color; - bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); - - // Needs 11 bits accumulated - if (x >= 10) { - if (bitsRow == 0x05D || bitsRow == 0x5D0) { - result += PENALTY_N3; - } - if (bitsCol == 0x05D || bitsCol == 0x5D0) { - result += PENALTY_N3; - } - } - - // Balance of black and white modules - if (color) { black++; } - } - } - - // Find smallest k such that (45-5k)% <= dark/total <= (55+5k)% - uint16_t total = size * size; - for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { - result += PENALTY_N4; - } - - return result; -} - - -#pragma mark - Reed-Solomon Generator - -static uint8_t rs_multiply(uint8_t x, uint8_t y) { - // Russian peasant multiplication - // See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication - uint16_t z = 0; - for (int8_t i = 7; i >= 0; i--) { - z = (z << 1) ^ ((z >> 7) * 0x11D); - z ^= ((y >> i) & 1) * x; - } - return z; -} - -static void rs_init(uint8_t degree, uint8_t *coeff) { - memset(coeff, 0, degree); - coeff[degree - 1] = 1; - - // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), - // drop the highest term, and store the rest of the coefficients in order of descending powers. - // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). - uint16_t root = 1; - for (uint8_t i = 0; i < degree; i++) { - // Multiply the current product by (x - r^i) - for (uint8_t j = 0; j < degree; j++) { - coeff[j] = rs_multiply(coeff[j], root); - if (j + 1 < degree) { - coeff[j] ^= coeff[j + 1]; - } - } - root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D) - } -} - -static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) { - // Compute the remainder by performing polynomial division - - //for (uint8_t i = 0; i < degree; i++) { result[] = 0; } - //memset(result, 0, degree); - - for (uint8_t i = 0; i < length; i++) { - uint8_t factor = data[i] ^ result[0]; - for (uint8_t j = 1; j < degree; j++) { - result[(j - 1) * stride] = result[j * stride]; - } - result[(degree - 1) * stride] = 0; - - for (uint8_t j = 0; j < degree; j++) { - result[j * stride] ^= rs_multiply(coeff[j], factor); - } - } -} - - - -#pragma mark - QrCode - -static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) { - int8_t mode = MODE_BYTE; - - if (isNumeric((char*)text, length)) { - mode = MODE_NUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (uint16_t i = 0; i < length; i++) { - accumData = accumData * 10 + ((char)(text[i]) - '0'); - accumCount++; - if (accumCount == 3) { - bb_appendBits(dataCodewords, accumData, 10); - accumData = 0; - accumCount = 0; - } - } - - // 1 or 2 digits remaining - if (accumCount > 0) { - bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); - } - - } else if (isAlphanumeric((char*)text, length)) { - mode = MODE_ALPHANUMERIC; - bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC)); - - uint16_t accumData = 0; - uint8_t accumCount = 0; - for (uint16_t i = 0; i < length; i++) { - accumData = accumData * 45 + getAlphanumeric((char)(text[i])); - accumCount++; - if (accumCount == 2) { - bb_appendBits(dataCodewords, accumData, 11); - accumData = 0; - accumCount = 0; - } - } - - // 1 character remaining - if (accumCount > 0) { - bb_appendBits(dataCodewords, accumData, 6); - } - - } else { - bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4); - bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE)); - for (uint16_t i = 0; i < length; i++) { - bb_appendBits(dataCodewords, (char)(text[i]), 8); - } - } - - //bb_setBits(dataCodewords, length, 4, getModeBits(version, mode)); - - return mode; -} - -static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) { - - // See: http://www.thonky.com/qr-code-tutorial/structure-final-message - -#if LOCK_VERSION == 0 - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; -#else - uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc]; - uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc]; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; -#endif - - uint8_t blockEccLen = totalEcc / numBlocks; - uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; - uint8_t shortBlockLen = moduleCount / 8 / numBlocks; - - uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; - - uint8_t result[data->capacityBytes]; - memset(result, 0, sizeof(result)); - - uint8_t coeff[blockEccLen]; - rs_init(blockEccLen, coeff); - - uint16_t offset = 0; - uint8_t *dataBytes = data->data; - - - // Interleave all short blocks - for (uint8_t i = 0; i < shortDataBlockLen; i++) { - uint16_t index = i; - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if (blockNum == numShortBlocks) { stride++; } -#endif - index += stride; - } - } - - // Version less than 5 only have short blocks -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - { - // Interleave long blocks - uint16_t index = shortDataBlockLen * (numShortBlocks + 1); - uint8_t stride = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { - result[offset++] = dataBytes[index]; - - if (blockNum == 0) { stride++; } - index += stride; - } - } -#endif - - // Add all ecc blocks, interleaved - uint8_t blockSize = shortDataBlockLen; - for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { - -#if LOCK_VERSION == 0 || LOCK_VERSION >= 5 - if (blockNum == numShortBlocks) { blockSize++; } -#endif - rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); - dataBytes += blockSize; - } - - memcpy(data->data, result, data->capacityBytes); - data->bitOffsetOrWidth = moduleCount; -} - -// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits) -// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc) -static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); - - -#pragma mark - Public QRCode functions - -uint16_t qrcode_getBufferSize(uint8_t version) { - return bb_getGridSizeBytes(4 * version + 17); -} - -// @TODO: Return error if data is too big. -int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) { - uint8_t size = version * 4 + 17; - qrcode->version = version; - qrcode->size = size; - qrcode->ecc = ecc; - qrcode->modules = modules; - - uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; - -#if LOCK_VERSION == 0 - uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; -#else - version = LOCK_VERSION; - uint16_t moduleCount = NUM_RAW_DATA_MODULES; - uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits]; -#endif - - struct BitBucket codewords; - uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; - bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); - - // Place the data code words into the buffer - int8_t mode = encodeDataCodewords(&codewords, data, length, version); - - if (mode < 0) { return -1; } - qrcode->mode = mode; - - // Add terminator and pad up to a byte if applicable - uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; - if (padding > 4) { padding = 4; } - bb_appendBits(&codewords, 0, padding); - bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); - - // Pad with alternate bytes until data capacity is reached - for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) { - bb_appendBits(&codewords, padByte, 8); - } - - BitBucket modulesGrid; - bb_initGrid(&modulesGrid, modules, size); - - BitBucket isFunctionGrid; - uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; - bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); - - // Draw function patterns, draw all codewords, do masking - drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); - performErrorCorrection(version, eccFormatBits, &codewords); - drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); - - // Find the best (lowest penalty) mask - uint8_t mask = 0; - int32_t minPenalty = INT32_MAX; - for (uint8_t i = 0; i < 8; i++) { - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); - applyMask(&modulesGrid, &isFunctionGrid, i); - int penalty = getPenaltyScore(&modulesGrid); - if (penalty < minPenalty) { - mask = i; - minPenalty = penalty; - } - applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR - } - - qrcode->mask = mask; - - // Overwrite old format bits - drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); - - // Apply the final choice of mask - applyMask(&modulesGrid, &isFunctionGrid, mask); - - return 0; -} - -int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) { - return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); -} - -bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { - if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) { - return false; - } - - uint32_t offset = y * qrcode->size + x; - return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; -} - -/* -uint8_t qrcode_getHexLength(QRCode *qrcode) { - return ((qrcode->size * qrcode->size) + 7) / 4; -} - -void qrcode_getHex(QRCode *qrcode, char *result) { - -} -*/ diff --git a/libraries/qrcode/qrcode.h b/libraries/qrcode/qrcode.h deleted file mode 100644 index ef429d7..0000000 --- a/libraries/qrcode/qrcode.h +++ /dev/null @@ -1,95 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2017 Richard Moore - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/** - * Special thanks to Nayuki (https://www.nayuki.io/) from which this library was - * heavily inspired and compared against. - * - * See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp - */ - - -#ifndef __QRCODE_H_ -#define __QRCODE_H_ - -#ifndef __cplusplus -typedef unsigned char bool; -static const bool false = 0; -static const bool true = 1; -#endif - -#include - - -// QR Code Format Encoding -#define MODE_NUMERIC 0 -#define MODE_ALPHANUMERIC 1 -#define MODE_BYTE 2 - - -// Error Correction Code Levels -#define ECC_LOW 0 -#define ECC_MEDIUM 1 -#define ECC_QUARTILE 2 -#define ECC_HIGH 3 - - -// If set to non-zero, this library can ONLY produce QR codes at that version -// This saves a lot of dynamic memory, as the codeword tables are skipped -#ifndef LOCK_VERSION -#define LOCK_VERSION 0 -#endif - - -typedef struct QRCode { - uint8_t version; - uint8_t size; - uint8_t ecc; - uint8_t mode; - uint8_t mask; - uint8_t *modules; -} QRCode; - - -#ifdef __cplusplus -extern "C"{ -#endif /* __cplusplus */ - - - -uint16_t qrcode_getBufferSize(uint8_t version); - -int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); -int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); - -bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); - - - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - - -#endif /* __QRCODE_H_ */ diff --git a/libraries/secp256k1.lib b/libraries/secp256k1.lib deleted file mode 100644 index 780565a..0000000 --- a/libraries/secp256k1.lib +++ /dev/null @@ -1 +0,0 @@ -https://github.com/diybitcoinhardware/secp256k1/#2f88d7fa31180909e6eb89b9c8b5e6aacad353fe diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 06aba6b..0000000 --- a/main.cpp +++ /dev/null @@ -1,564 +0,0 @@ -/** - * DISCLAIMER - * This is our "functional prototype", this means that even though - * it is kinda functional, there are plenty of security holes and bugs. - * That's why you are not able to store your private keys here - - * only public information. And you should NOT trust this wallet - * Use it carefully, on the testnet, otherwise you could lose your funds. - * - * Also architecture and the whole codebase will be refactored significantly - * in the future and we are not maintaining backwards compatibility. - */ - -#include "mbed.h" - -#include -#include -#include - -#include "specter_config.h" -#include "helpers.h" -#include "storage.h" -#include "gui.h" -#include "rng.h" -#include "host.h" -#include "keystore.h" -#include "networks.h" - -#include "wally_core.h" -#include "wally_bip39.h" -#include "wally_address.h" - -#include "wally_psbt.h" -#include "wally_script.h" - -using std::string; - -#define NO_ACTION 0 -#define VERIFY_ADDRESS 1 -#define SIGN_PSBT 2 -#define NEW_WALLET 3 - -static char * mnemonic = NULL; -static char * password = NULL; - -static keystore_t keystore; -static wallet_t wallet; -static const network_t * network = &Testnet; - -static int in_action = NO_ACTION; -static struct wally_psbt * psbt = NULL; - -Serial pc(SERIAL_TX, SERIAL_RX, 115200); -DigitalIn btn(USER_BUTTON); - -static char * temp_data = NULL; - -// generates a mnemonic from entropy -// TODO: should it be moved to keystore? -void generate_mnemonic(size_t n){ - uint8_t * rnd; - rnd = (uint8_t *) malloc(n); - rng_get_random_buffer(rnd, n); - bip39_mnemonic_from_bytes(NULL, rnd, n, &mnemonic); - wally_bzero(rnd, n); - free(rnd); -} - -// securely copies the string and zeroes input -void sstrcopy(char * input, char ** output){ - if(*output!=NULL){ - wally_bzero(*output, strlen(*output)); - free(*output); - *output = NULL; - } - *output = (char*)calloc(strlen(input)+1, sizeof(char)); - strcpy(*output, input); - wally_bzero(input, strlen(input)); -} - -// initializes keystore from mnemonic and password -void init_keys(const char * mnemonic, const char * password, keystore_t * keys){ - logit("main", "init_keys"); - keystore_init(mnemonic, password, keys); -} - -// sets default extended keys paths in the GUI -void set_default_xpubs(){ - // set default xpubs derivations - char single[20]; - char multisig[20]; - sprintf(single, "m/84h/%luh/0h", network->bip32); - sprintf(multisig, "m/48h/%luh/0h/2h", network->bip32); - gui_set_default_xpubs(single, multisig); -} - -// parses psbt, constructs all the addresses and amounts and sends to GUI -static int show_psbt(const struct wally_psbt * psbt){ - // check if we can sign it and all fields are ok - int res = keystore_check_psbt(&keystore, network, psbt, &wallet); - if(res!=0){ - if(res & KEYSTORE_PSBTERR_CANNOT_SIGN){ - show_err("Can't sign the transaction"); - return -1; - } - if(res & KEYSTORE_PSBTERR_MIXED_INPUTS){ - show_err("Mixed inputs are not supported yet"); - return -1; - } - if(res & KEYSTORE_PSBTERR_WRONG_FIELDS){ - show_err("Something is wrong with transaction fields"); - return -1; - } - if(res & KEYSTORE_PSBTERR_UNSUPPORTED_POLICY){ - show_err("Script policy is not supported"); - return -1; - } - show_err("Something is wrong with transaction"); - return -1; - } - - uint64_t in_amount = 0; - uint64_t out_amount = 0; - uint64_t change_amount = 0; - uint64_t fee = 0; - - for(int i = 0; i < psbt->num_inputs; i++){ - if(!psbt->inputs[i].witness_utxo){ - show_err("Unsupported legacy transaction or missing prevout information"); - return -1; - } - in_amount += psbt->inputs[i].witness_utxo->satoshi; - } - - txout_t * outputs; - outputs = (txout_t *)calloc(psbt->num_outputs, sizeof(txout_t)); - - for(int i=0; i < psbt->tx->num_outputs; i++){ - size_t script_type; - wally_scriptpubkey_get_type(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, &script_type); - char * addr = NULL; - uint8_t bytes[21]; - // should deal with all script types, only P2WPKH for now - switch(script_type){ - case WALLY_SCRIPT_TYPE_P2WPKH: - case WALLY_SCRIPT_TYPE_P2WSH: - wally_addr_segwit_from_bytes(psbt->tx->outputs[i].script, psbt->tx->outputs[i].script_len, network->bech32, 0, &addr); - break; - case WALLY_SCRIPT_TYPE_P2SH: - bytes[0] = network->p2sh; - memcpy(bytes+1, psbt->tx->outputs[i].script+2, 20); - wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr); - break; - case WALLY_SCRIPT_TYPE_P2PKH: - bytes[0] = network->p2pkh; - memcpy(bytes+1, psbt->tx->outputs[i].script+3, 20); - wally_base58_from_bytes(bytes, 21, BASE58_FLAG_CHECKSUM, &addr); - break; - } - if(!addr){ - addr = (char *)malloc(20); - sprintf(addr, "...custom script..."); - } - outputs[i].address = addr; - outputs[i].amount = psbt->tx->outputs[i].satoshi; - char * warning = NULL; - outputs[i].is_change = wallet_output_is_change(&wallet, psbt, i, &warning); - outputs[i].warning = warning; - if(outputs[i].is_change){ - change_amount += psbt->tx->outputs[i].satoshi; - }else{ - out_amount += psbt->tx->outputs[i].satoshi; - } - } - fee = in_amount-out_amount-change_amount; - gui_show_psbt(wallet.name, out_amount, change_amount, fee, psbt->num_outputs, outputs); - for(int i=0; inum_outputs; i++){ - if(outputs[i].address != NULL){ - wally_free_string(outputs[i].address); - } - if(outputs[i].warning != NULL){ - wally_free_string(outputs[i].warning); - } - } - free(outputs); - return 0; -} - -// handles user action from GUI -void process_action(int action){ - switch(action){ - case GUI_SECURE_SHUTDOWN: - { - logit("main", "shutting down..."); - char * s = gui_get_str(); - wally_bzero(s, strlen(s)); - wally_cleanup(0); - // TODO: reboot? - exit(0); - break; // no need really - } - case GUI_LIST_WALLETS: - { - logit("main", "listing multisig wallets"); - char ** wallets = NULL; - keystore_get_wallets(&keystore, network, &wallets); - gui_show_wallets(wallets); - keystore_free_wallets(wallets); - break; - } - case GUI_SELECT_WALLET: - { - int val = gui_get_value(); - keystore_get_wallet(&keystore, network, val, &wallet); - char * base58_addr; - char * bech32_addr; - int res = wallet_get_addresses(&wallet, &base58_addr, &bech32_addr); - if(res < 0){ - show_err("Failed to compute wallet addresses"); - return; - } - gui_navigate_wallet(wallet.name, wallet.address, bech32_addr, base58_addr); - wally_free_string(base58_addr); - wally_free_string(bech32_addr); - break; - } - case GUI_NEW_WALLET: - { - in_action = NEW_WALLET; - host_request_data(); - break; - } - case GUI_CONFIRM_NEW_WALLET: - { - int id = keystore_add_wallet(&keystore, network, temp_data, &wallet); - if(temp_data!=NULL){ - free(temp_data); - temp_data = NULL; - } - process_action(GUI_LIST_WALLETS); - break; - } - case GUI_DELETE_WALLET: - { - int err = keystore_del_wallet(&keystore, network, &wallet); - if(err){ - show_err("Failed to delete wallet"); - }else{ - process_action(GUI_LIST_WALLETS); - } - break; - } - case GUI_CANCEL_NEW_WALLET: - { - if(temp_data!=NULL){ - free(temp_data); - temp_data = NULL; - } - process_action(GUI_LIST_WALLETS); - break; - } - case GUI_GET_WALLET_ADDRESS: - { - int val = gui_get_value(); - wallet.address = val; - char * base58_addr; - char * bech32_addr; - wallet_get_addresses(&wallet, &base58_addr, &bech32_addr); - gui_navigate_wallet(wallet.name, wallet.address, bech32_addr, base58_addr); - wally_free_string(base58_addr); - wally_free_string(bech32_addr); - break; - } - case GUI_GENERATE_KEY: - { - logit("main", "generating a key..."); - int v = gui_get_value(); - if(v % 3 != 0 || v < 12 || v > 24){ - v = SPECTER_MNEMONIC_WORDS; - } - generate_mnemonic(v*16/12); - gui_show_mnemonic(mnemonic); - break; - } - case GUI_PROCESS_MNEMONIC: - { - logit("main", "processing mnemonic..."); - char * str = gui_get_str(); - if(bip39_mnemonic_validate(NULL, str)!=WALLY_OK){ - show_err("mnemonic is not correct"); - }else{ - sstrcopy(str, &mnemonic); - logit("main", "mnemonic is saved in memory"); - gui_get_password(); - } - break; - } - case GUI_PROCESS_PASSWORD: - { - logit("main", "processing password"); - char * str = gui_get_str(); - sstrcopy(str, &password); - logit("main", "password is saved in memory"); - init_keys(mnemonic, password, &keystore); - // delete password from memory - we don't need it anymore - wally_bzero(password, strlen(password)); - free(password); - password = NULL; - - gui_show_main_screen(); - break; - } - case GUI_PROCESS_NETWORK: - { - int val = gui_get_value(); - if(val >= 0 && val < NETWORKS_NUM){ - network = networks[val]; - gui_set_network(val); - set_default_xpubs(); - gui_show_main_screen(); - }else{ - show_err("No such network"); - } - break; - } - case GUI_SHOW_XPUB: - { - char * str = gui_get_str(); - char *xpub = NULL; - keystore_get_xpub(&keystore, str, network, USE_SLIP132, &xpub); // keys, derivation, network, string - gui_show_xpub(keystore.fingerprint, str, xpub); //[fingerprint/derivation]xpub - wally_free_string(xpub); - break; - } - case GUI_VERIFY_ADDRESS: - { - logit("main", "verify address triggered"); - in_action = VERIFY_ADDRESS; - host_request_data(); - break; - } - case GUI_SIGN_PSBT: - { - logit("main", "PSBT triggered"); - in_action = SIGN_PSBT; - host_request_data(); - break; - } - case GUI_PSBT_CONFIRMED: - { - logit("main", "Signing transaction..."); - char * output = NULL; - if(wallet_sign_psbt(&wallet, psbt, &output) == 0){ - printf("%s\r\n", output); - gui_show_signed_psbt(output); - wally_free_string(output); - }else{ - show_err("failed to sign transaction"); - } - break; - } - case GUI_BACK: - { - gui_show_init_screen(); - break; - } - case GUI_SHOW_MNEMONIC: - { - gui_show_reckless_mnemonic(mnemonic); - break; - } - case GUI_SAVE_MNEMONIC: - { // TODO: set and verify pin code - int res = storage_save_mnemonic(mnemonic); - if(res < 0){ - show_err("Failed to save mnemonic"); - }else{ - gui_alert_create("Success!", "Your recovery phrase is saved to memory", "Ok"); - } - break; - } - case GUI_DELETE_MNEMONIC: - { // TODO: securely erase this file - int res = storage_delete_mnemonic(); - if(res < 0){ - show_err("Failed to delete mnemonic"); - }else{ - gui_alert_create("Success!", "Your recovery phrase is removed from memory", "Ok"); - } - break; - } - case GUI_LOAD_MNEMONIC: - { - if(mnemonic != NULL){ - wally_free_string(mnemonic); - mnemonic = NULL; - } - int res = storage_load_mnemonic(&mnemonic); - if(res < 0){ - show_err("Failed to load mnemonic"); - }else{ - if(bip39_mnemonic_validate(NULL, mnemonic)!=WALLY_OK){ - show_err("mnemonic is not correct"); - }else{ - logit("main", "mnemonic is saved in memory"); - gui_get_password(); - } - } - break; - } - default: - show_err("unrecognized action"); - } -} - -static void verify_address(const char * buf){ - if(memcmp(buf, "bitcoin:", 8)==0){ - buf = buf+8; - } - char addr[80]; - int index; - int res = sscanf(buf, "%80[^?]?index=%d", addr, &index); - if(res != 2){ - show_err("Failed to parse address index. Is it in the QR code?"); - return; - } - uint32_t path[2] = {0, index}; - char * wallet_name; - res = keystore_verify_address(&keystore, network, addr, path, 2, &wallet_name); - if(res >= 0){ - string title = "Wallet \""; - title += wallet_name; - title += "\""; - string qrmsg = string("bitcoin:")+addr; - gui_qr_alert_create(title.c_str(), qrmsg.c_str(), addr, "Ok"); - }else{ - show_err("Failed to verify address. Are you sure it belongs to this network and wallet?"); - } -} - -static void check_new_wallet(char * buf){ - int err = keystore_check_wallet(&keystore, network, buf); - if(err != 0){ - switch(err){ - case KEYSTORE_WALLET_ERR_NOT_INCLUDED: - show_err("Key is not in the wallet"); - break; - case KEYSTORE_WALLET_ERR_WRONG_XPUB: - show_err("Wrong xpub"); - break; - default: - show_err("Something is wrong with the wallet format"); - } - }else{ - if(temp_data != NULL){ - free(temp_data); - } - temp_data = (char *)calloc(strlen(buf), 1); - strcpy(temp_data, buf); - gui_confirm_new_wallet(buf); - } -} - -// handles data from the host -static void process_data(int action, uint8_t * buf, size_t len){ - int err; - char * b64 = NULL; - switch(action){ - case NEW_WALLET: - { - check_new_wallet((char *)buf); - break; - } - case VERIFY_ADDRESS: - { - verify_address((char *)buf); - break; - } - case SIGN_PSBT: - { - b64 = (char *) buf; - if(psbt!=NULL){ - wally_psbt_free(psbt); - psbt = NULL; - } - err = wally_psbt_from_base64(b64, &psbt); - if(err!=WALLY_OK){ - show_err("failed to parse psbt transaction"); - return; - } - err = show_psbt(psbt); - if(err){ - wally_psbt_free(psbt); - psbt = NULL; - } - break; - } - } -} - -void update(){ - gui_update(); - int action = gui_get_action(); - if(action != GUI_NO_ACTION){ - process_action(action); - gui_clear_action(); - } - host_update(); - if(in_action != NO_ACTION){ - size_t len = host_data_available(); - if(len > 0){ - logit("main", "data!"); - uint8_t * buf = host_get_data(); - process_data(in_action, buf, len); - host_flush(); - in_action = NO_ACTION; - } - } - - if(btn){ // If blue button is pressed - calibrate touchscreen - while(btn){ - wait(0.1); - } - gui_calibrate(); - } -} - -int main(){ - int err = 0; - - rng_init(); // random number generator - storage_init(); // on-board memory & sd card - // on-board memory is on external chip => untrusted - host_init(HOST_DEFAULT, 5); // communication - functions to scan qr codes - // and talk to sd card storage - wally_init(0); // init wally library - // key storage module - signs, derives addresses etc. - // if mnemonic and password are NULL just allocates space for the key - keystore_init(NULL, NULL, &keystore); - - gui_init(); // display functions - - // available networks - static const char * available_networks[] = {"Mainnet", "Testnet", "Regtest", "Signet", ""}; - gui_set_available_networks(available_networks); - gui_set_network(1); // default network - testnet - set_default_xpubs(); // sets default xpub derivations - - // for debug purposes - hardcoded mnemonic - // TODO: add reckless storage option -#ifdef DEBUG_MNEMONIC - char debug_mnemonic[] = DEBUG_MNEMONIC; - sstrcopy(debug_mnemonic, &mnemonic); - gui_get_password(); // go directly to "enter password" screen -#else - gui_start(); // start the gui -#endif - - while(1){ - update(); - } - // should never reach this, but still - wally_cleanup(0); - return err; -} diff --git a/main.py b/main.py new file mode 100644 index 0000000..5a3c27b --- /dev/null +++ b/main.py @@ -0,0 +1,157 @@ +import gui +from gui import screens +from gui.decorators import queued + +import urandom, os +import ujson as json +from ubinascii import hexlify, unhexlify + +from bitcoin import ec, hashes, bip39, bip32 +from keystore import KeyStore + +from qrscanner import QRScanner + +qr_scanner = QRScanner() + +# entropy that will be converted to mnemonic +entropy = None +# our key storage +keystore = KeyStore() + +# detect if it's a hardware device or linuxport +try: + import pyb + simulator = False +except: + simulator = True + +if simulator: + reckless_fname = "reckless.json" +else: + reckless_fname = "/flash/reckless.json" + +def cancel_scan(): + print("Cancel scan!") + qr_scanner.stop() + show_main() + +def wallets_menu(): + pass + +def xpubs_menu(): + pass + +def scan_transaction(): + pass + +def scan_address(): + pass + +def network_menu(): + pass + +def show_mnemonic(): + print(bip39.mnemonic_from_bytes(entropy)) + +def save_entropy(): + with open(reckless_fname, "w") as f: + f.write('{"entropy":"%s"}' % hexlify(entropy).decode('utf-8')) + with open(reckless_fname, "r") as f: + d = json.loads(f.read()) + if "entropy" in d and d["entropy"] == hexlify(entropy).decode('utf-8'): + gui.alert("Success!", "Your key is saved in the memory now") + else: + gui.error("Something went wrong") + +def delete_entropy(): + try: + os.remove(reckless_fname) + gui.alert("Success!", "Your key is deleted") + except: + gui.error("Failed to delete the key") + +def reckless_menu(): + gui.create_menu(buttons=[ + ("Show recovery phrase", show_mnemonic), + ("Save key to memory", save_entropy), + ("Delete key from memory", delete_entropy), + ("Back to main", show_main) + ]) + + +def show_main(): + gui.create_menu(buttons=[ + ("Wallets", wallets_menu), + ("Master keys", xpubs_menu), + ("Sign transaction", scan_transaction), + ("Verify address", scan_address), + ("Use another password", ask_for_password), + ("Switch network", network_menu), + ("# Reckless", reckless_menu) + ]) + +def get_new_mnemonic(words=12): + entropy_len = words*4//3 + global entropy + entropy = bytes([urandom.getrandbits(8) for i in range(entropy_len)]) + return bip39.mnemonic_from_bytes(entropy) + +def gen_new_key(words=12): + mnemonic = get_new_mnemonic(words) + screens.new_mnemonic(mnemonic, cb_continue=ask_for_password, cb_back=show_init, cb_update=get_new_mnemonic) + +def recover_key(): + screens.ask_for_mnemonic(cb_continue=mnemonic_entered, cb_back=show_init, check_mnemonic=bip39.mnemonic_is_valid, words_lookup=bip39.find_candidates) + +def mnemonic_entered(mnemonic): + global entropy + entropy = bip39.mnemonic_to_bytes(mnemonic) + ask_for_password() + +def load_key(): + global entropy + try: + with open(reckless_fname, "r") as f: + d = json.loads(f.read()) + entropy = unhexlify(d["entropy"]) + ask_for_password() + except: + gui.error("Something went wrong, sorry") + +def show_init(): + buttons = [ + ("Generate new key", gen_new_key), + ("Enter recovery phrase", recover_key) + ] + # check if reckless.json file exists + # os.path is not implemented in micropython :( + try: + with open(reckless_fname,"r") as f: + c = f.read() + if len(c) == 0: + raise RuntimeError("File is empty") + # if ok - add an extra button + buttons.append(("Load key from memory", load_key)) + except: + pass + screens.create_menu(buttons=buttons) + +def ask_for_password(): + screens.ask_for_password(init_keys) + +def init_keys(password): + mnemonic = bip39.mnemonic_from_bytes(entropy) + seed = bip39.mnemonic_to_seed(mnemonic, password) + keystore.load_seed(seed) + show_main() + +def main(blocking=True): + gui.init() + show_init() + if blocking: + while True: + gui.update() + qr_scanner.update() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/mbed-os.lib b/mbed-os.lib deleted file mode 100644 index ef1fe8d..0000000 --- a/mbed-os.lib +++ /dev/null @@ -1 +0,0 @@ -https://github.com/ARMmbed/mbed-os/#f2236d26f850733348fc313914947033a5d65f01 diff --git a/mbed_app.json b/mbed_app.json deleted file mode 100644 index c1d62d5..0000000 --- a/mbed_app.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "target_overrides": { - "*": { - "rtos.main-thread-stack-size": "8192" - } - } -} diff --git a/qrscanner.py b/qrscanner.py new file mode 100644 index 0000000..c2ed6cd --- /dev/null +++ b/qrscanner.py @@ -0,0 +1,97 @@ +try: + import pyb + linuxport = False +except: + linuxport = True + +import utime as time + +if not linuxport: + class QRScanner: + def __init__(self, trigger="D5", uart="YA", baudrate=9600): + self.trigger = pyb.Pin(trigger, pyb.Pin.OUT) + self.trigger.on() + self.uart = pyb.UART(uart, baudrate, read_buf_len=1024) + self.uart.read(self.uart.any()) + self.scanning = False + self.t0 = None + self.callback = None + + def start_scan(self, callback=None, timeout=None): + self.trigger.off() + self.t0 = time.time() + self.data = "" + self.scanning = True + self.callback = callback + + def scan(self, timeout=None): + self.trigger.off() + r = "" + t0 = time.time() + while len(r)==0 or not r.endswith("\r"): + res = self.uart.read(self.uart.any()) + if len(res) > 0: + r += res.decode('utf-8') + time.sleep(0.01) + if timeout is not None: + if time.time() > t0+timeout: + break + self.trigger.on() + if r.endswith("\r"): + return r[:-1] + return r + + def update(self): + if self.scanning: + res = self.uart.read(self.uart.any()) + if len(res) > 0: + self.data += res.decode('utf-8') + if self.is_done() and self.callback is not None: + data = self.data[:-1] + self.reset() + self.callback(data) + + def is_done(self): + return self.data.endswith("\r") + + def reset(self): + self.scanning = False + self.data = "" + self.t0 = None + self.trigger.on() + + def stop(self): + self.reset() + return self.data +else: + # dummy for linuxport + class QRScanner: + def __init__(self, *args, **kwargs): + self.scanning = False + self.t0 = None + self.callback = None + + def scan(self, timeout=None): + print("Enter what QRScanner would have scanned:") + r = input() + if r.endswith("\r") or r.endswith("\n"): + return r[:-1] + return r + + def reset(self): + pass + + def start_scan(self, callback=None, timeout=None): + self.data = "" + self.scanning = True + self.callback = callback + + def update(self): + if self.scanning: + data = self.scan() + self.scanning = False + if self.callback is not None: + self.callback(data) + + def stop(self): + pass \ No newline at end of file diff --git a/specter_config.h b/specter_config.h deleted file mode 100644 index 2710a6e..0000000 --- a/specter_config.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef __SPECTER_CONFIG_H__ -#define __SPECTER_CONFIG_H__ - -// default number of words to use in the mnemonic -#define SPECTER_MNEMONIC_WORDS 12 -// buffer size for host module -#define SPECTER_HOST_INPUT_SIZE 3000 -// ypub, zpub -#define USE_SLIP132 1 - -#endif // __SPECTER_CONFIG_H__ \ No newline at end of file From 57df564f5c9a0dfdd2caf0474e39cca88d82a4a9 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Thu, 14 Nov 2019 15:25:08 +0100 Subject: [PATCH 02/11] xpubs screens --- gui/common.py | 22 ++++++++-------- gui/popups.py | 38 +++++++++++++++++++++++---- gui/screens.py | 4 ++- keystore.py | 9 ++++++- main.py | 70 ++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 117 insertions(+), 26 deletions(-) diff --git a/gui/common.py b/gui/common.py index 29c14df..9477a32 100644 --- a/gui/common.py +++ b/gui/common.py @@ -66,24 +66,14 @@ def add_button_pair(text1, callback1, text2, callback2, scr=None, y=700): btn.set_x(HOR_RES//2+PADDING//2) return btn -def add_qrcode(text, y=QR_PADDING, scr=None, style=None): - """Helper functions that creates a title-styled label""" - if scr is None: - scr = lv.scr_act() - +def qr_update(lbl, text): qr = qrcode.encode_to_string(text) size = int(math.sqrt(len(qr))) - - scr = lv.scr_act() - # canvas = lv.canvas(scr) - width = HOR_RES scale = width//(size+4) sizes = [1,2,3,5,7,10] fontsize = [s for s in sizes if s < scale][-1] font = getattr(lv, "square%d" % fontsize) - - lbl = add_label("Text", y=y, scr=scr) style = lv.style_t() lv.style_copy(style, lv.style_plain) style.body.main_color = lv.color_make(0xFF,0xFF,0xFF) @@ -97,6 +87,16 @@ def add_qrcode(text, y=QR_PADDING, scr=None, style=None): lbl.set_text(qr) del qr gc.collect() + +def add_qrcode(text, y=QR_PADDING, scr=None, style=None): + """Helper functions that creates a title-styled label""" + if scr is None: + scr = lv.scr_act() + + scr = lv.scr_act() + + lbl = add_label("Text", y=y, scr=scr) + qr_update(lbl, text) return lbl def table_set_mnemonic(table, mnemonic): diff --git a/gui/popups.py b/gui/popups.py index bf27908..d4874e6 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -48,15 +48,43 @@ def qr_alert(title, message, message_text=None, callback=None): scr = lv.obj() lv.scr_load(scr) add_label(title, style="title") - obj = add_qrcode(message, scr=scr, y=PADDING+100) + qrobj = add_qrcode(message, scr=scr, y=PADDING+100) + msgobj = None if message_text is not None: - y = obj.get_y()+obj.get_height()+20 - add_label(message_text, y=y) + y = qrobj.get_y()+qrobj.get_height()+20 + msg_obj = add_label(message_text, y=y) def cb(obj, event): if event == lv.EVENT.RELEASED: lv.scr_load(old_scr) - obj.delete() - print(gc.collect()) + qrobj.delete() + gc.collect() if callback is not None: callback() add_button("OK", cb) + # objects to manupulate if necessary + return (qrobj, msg_obj) + +def show_xpub(name, xpub, fingerprint=None, derivation=None, callback=None): + prefix = "" + if derivation is not None and fingerprint is not None: + derivation = "%s%s" % (fingerprint, derivation[1:]) + if derivation is not None: + prefix = "[%s]" % derivation + msg = prefix+xpub + qrobj, msgobj = qr_alert("Master "+name, msg, msg, callback) + scr = lv.scr_act() + # add checkbox + if len(prefix) > 0: + def cb(): + txt = msgobj.get_text() + if prefix in txt: + txt = xpub + else: + txt = prefix+xpub + msgobj.set_text(txt) + qr_update(qrobj, txt) + btn = add_button("Toggle derivation", on_release(cb), y=600) + +def show_mnemonic(mnemonic): + alert("Your recovery phrase:", "") + add_mnemonic_table(mnemonic, y=100) \ No newline at end of file diff --git a/gui/screens.py b/gui/screens.py index 002e2e4..b44a759 100644 --- a/gui/screens.py +++ b/gui/screens.py @@ -44,7 +44,7 @@ def cb(obj, event): btnm.set_event_cb(cb); @queued -def create_menu(buttons=[], title="What do you want to do?", y0=100): +def create_menu(buttons=[], title="What do you want to do?", y0=100, cb_back=None): scr = lv.scr_act() scr.clean() add_label(title) @@ -52,6 +52,8 @@ def create_menu(buttons=[], title="What do you want to do?", y0=100): for text, callback in buttons: add_button(text, on_release(callback), y=y) y+=100 + if cb_back is not None: + add_button(lv.SYMBOL.LEFT+" Back", on_release(cb_back)) @queued def show_progress(title, text, callback=None): diff --git a/keystore.py b/keystore.py index 251d3ed..f0c3bad 100644 --- a/keystore.py +++ b/keystore.py @@ -7,4 +7,11 @@ def __init__(self, seed=None): def load_seed(self, seed): if seed is not None: - self.root = bip32.HDKey.from_seed(seed) \ No newline at end of file + self.root = bip32.HDKey.from_seed(seed) + self.fingerprint = self.root.child(0).fingerprint + + def get_xpub(self, derivation): + xpub = self.root.derive(derivation) + ver = bip32.detect_version(derivation) + xpub.version = ver + return xpub.to_public() \ No newline at end of file diff --git a/main.py b/main.py index 5a3c27b..b543ac3 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,14 @@ import gui -from gui import screens +from gui import screens, popups from gui.decorators import queued +import gui.common import urandom, os import ujson as json from ubinascii import hexlify, unhexlify from bitcoin import ec, hashes, bip39, bip32 +from bitcoin.networks import NETWORKS from keystore import KeyStore from qrscanner import QRScanner @@ -15,6 +17,8 @@ # entropy that will be converted to mnemonic entropy = None +# network we are using +network = None # our key storage keystore = KeyStore() @@ -25,11 +29,14 @@ except: simulator = True +# path to store #reckless entropy if simulator: reckless_fname = "reckless.json" else: reckless_fname = "/flash/reckless.json" +DEFAULT_XPUBS = [] + def cancel_scan(): print("Cancel scan!") qr_scanner.stop() @@ -38,8 +45,21 @@ def cancel_scan(): def wallets_menu(): pass +def show_xpub(name, derivation): + xpub = keystore.get_xpub(derivation).to_base58() + fingerprint = hexlify(keystore.fingerprint).decode('utf-8') + popups.show_xpub(name, xpub, fingerprint=fingerprint, derivation=derivation) + def xpubs_menu(): - pass + def selector(name, derivation): + def cb(): + show_xpub(name, derivation) + return cb + buttons = [] + for name, derivation in DEFAULT_XPUBS: + buttons.append((name, selector(name, derivation))) + # buttons.append(("Back to Main menu", show_main)) + gui.create_menu(buttons=buttons, cb_back=show_main) def scan_transaction(): pass @@ -47,11 +67,44 @@ def scan_transaction(): def scan_address(): pass +def set_default_xpubs(net): + while len(DEFAULT_XPUBS) > 0: + DEFAULT_XPUBS.pop() + DEFAULT_XPUBS.append(("Single key", "m/84h/%dh/0h" % network["bip32"])) + DEFAULT_XPUBS.append(("Miltisig", "m/48h/%dh/0h/2h" % network["bip32"])) + +def select_network(name): + global network + if name in NETWORKS: + network = NETWORKS[name] + set_default_xpubs(network) + else: + raise RuntimeError("Unknown network") + def network_menu(): - pass + def selector(name): + def cb(): + try: + select_network(name) + show_main() + except Exception as e: + print(e) + gui.error("%r" % e) + return cb + # could be done with iterator + # but order is unknown then + gui.create_menu(buttons=[ + ("Mainnet", selector("main")), + ("Testnet", selector("test")), + ("Regtest", selector("regtest")), + ("Signet", selector("signet")) + ]) + def show_mnemonic(): - print(bip39.mnemonic_from_bytes(entropy)) + # print(bip39.mnemonic_from_bytes(entropy)) + popups.show_mnemonic(bip39.mnemonic_from_bytes(entropy)) + def save_entropy(): with open(reckless_fname, "w") as f: @@ -74,9 +127,8 @@ def reckless_menu(): gui.create_menu(buttons=[ ("Show recovery phrase", show_mnemonic), ("Save key to memory", save_entropy), - ("Delete key from memory", delete_entropy), - ("Back to main", show_main) - ]) + ("Delete key from memory", delete_entropy) + ], cb_back=show_main) def show_main(): @@ -86,7 +138,7 @@ def show_main(): ("Sign transaction", scan_transaction), ("Verify address", scan_address), ("Use another password", ask_for_password), - ("Switch network", network_menu), + ("Switch network (%s)" % network["name"], network_menu), ("# Reckless", reckless_menu) ]) @@ -147,6 +199,8 @@ def init_keys(password): def main(blocking=True): gui.init() + # choose testnet by default + select_network("test") show_init() if blocking: while True: From 840f9e73834d93bf0828149dfc3c82e8190c7b8f Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Fri, 15 Nov 2019 18:07:11 +0100 Subject: [PATCH 03/11] wallet logic --- gui/popups.py | 9 +-- keystore.py | 214 ++++++++++++++++++++++++++++++++++++++++++++++---- main.py | 24 +++++- 3 files changed, 221 insertions(+), 26 deletions(-) diff --git a/gui/popups.py b/gui/popups.py index d4874e6..b31251f 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -64,17 +64,12 @@ def cb(obj, event): # objects to manupulate if necessary return (qrobj, msg_obj) -def show_xpub(name, xpub, fingerprint=None, derivation=None, callback=None): - prefix = "" - if derivation is not None and fingerprint is not None: - derivation = "%s%s" % (fingerprint, derivation[1:]) - if derivation is not None: - prefix = "[%s]" % derivation +def show_xpub(name, xpub, prefix=None, callback=None): msg = prefix+xpub qrobj, msgobj = qr_alert("Master "+name, msg, msg, callback) scr = lv.scr_act() # add checkbox - if len(prefix) > 0: + if prefix is not None: def cb(): txt = msgobj.get_text() if prefix in txt: diff --git a/keystore.py b/keystore.py index f0c3bad..def57e0 100644 --- a/keystore.py +++ b/keystore.py @@ -1,17 +1,201 @@ -from bitcoin import bip32 +from bitcoin import bip32, ec, script, hashes +from bitcoin.networks import NETWORKS +import os +import ujson as json +import ure as re +from ubinascii import unhexlify + +is_simulator = False +try: + import pyb +except: + is_simulator = True + +def multi(*args): + return script.multisig(args[0], args[1:]) + +def sortedmulti(*args): + return multi(args[0], *sorted(args[1:])) + +DESCRIPTOR_SCRIPTS = { + "pkh": script.p2pkh, + "wpkh": script.p2wpkh, + "sh": script.p2sh, + "wsh": script.p2wsh, + "multi": multi, + "sortedmulti": sortedmulti +} class KeyStore: - def __init__(self, seed=None): - self.root = None - self.load_seed(seed) - - def load_seed(self, seed): - if seed is not None: - self.root = bip32.HDKey.from_seed(seed) - self.fingerprint = self.root.child(0).fingerprint - - def get_xpub(self, derivation): - xpub = self.root.derive(derivation) - ver = bip32.detect_version(derivation) - xpub.version = ver - return xpub.to_public() \ No newline at end of file + def __init__(self, seed=None): + self.root = None + self._wallets = [] + self.network = None + self.storage_path = None + self.load_seed(seed) + self.idkey = None + + def load_seed(self, seed): + if seed is not None: + self.root = bip32.HDKey.from_seed(seed) + self.fingerprint = self.root.child(0).fingerprint + # id key to sign wallet files stored on untrusted external chip + self.idkey = self.root.child(0x1D, hardened=True) + + def get_xpub(self, derivation): + xpub = self.root.derive(derivation) + ver = bip32.detect_version(derivation) + xpub.version = ver + return xpub.to_public() + + def load_wallets(self, network_name): + self.network = NETWORKS[network_name] + if is_simulator: + self.storage_path = "%s/" % network_name + else: + self.storage_path = "/flash/%s/" % network_name + try: + os.mkdir(self.storage_path) + except: + pass # probably exists + files = [f[0] for f in os.ilistdir(self.storage_path) if f[0].endswith("_wallet.json")] + self._wallets = [] + for fname in files: + with open(self.storage_path+fname) as f: + content = f.read() + with open(self.storage_path+fname.replace(".json",".sig"),"rb") as f: + sig = ec.Signature.parse(f.read()) + if self.idkey.verify(sig, hashes.sha256(content)): + self._wallets.append(Wallet.parse(content, self.network)) + else: + raise RuntimeError("Invalid signature for wallet") + for w in self._wallets: + print(w.name, w.descriptor) + + def get_wallet_fname(self): + files = [int(f[0].split("_")[0]) for f in os.ilistdir(self.storage_path) if f[0].endswith("_wallet.json")] + if len(files) > 0: + idx = max(files)+1 + else: + idx = 0 + fname = self.storage_path+("%d_wallet.json" % idx) + return fname + + def create_wallet(self, name, descriptor): + w = Wallet(name, descriptor, self.network) + fname = self.get_wallet_fname() + data = w.save(fname) + h = hashes.sha256(data) + sig = self.idkey.sign(h) + with open(fname.replace(".json",".sig"),"wb") as f: + f.write(sig.serialize()) + self._wallets.append(w) + + @property + def wallets(self): + return self._wallets + + @property + def is_initialized(self): + return (self.root is not None) + +class DerivedKey: + def __init__(self, key, fingerprint=None, parent_derivation=None, address_derivation="_"): + self.key = key + self.fingerprint = fingerprint + self.parent_derivation = parent_derivation + self.address_derivation = address_derivation + + @classmethod + def parse(cls, s): + fingerprint = None + parent_derivation = None + address_derivation = "_" + m = re.match("\[(.*)\](.*)", s) + if m: + parent_derivation = m.group(1) + if not parent_derivation.startswith("m/"): + arr = parent_derivation.split("/") + fingerprint = unhexlify(arr[0]) + if len(fingerprint) != 4: + raise ValueError("Invalid fingerprint in derivation path") + parent_derivation = "m/"+"/".join(arr[1:]) + parent_derivation = bip32.parse_path(parent_derivation) + s = m.group(2) + if "/" in s: + arr = s.split("/") + address_derivation = "/".join(arr[1:]) + s = arr[0] + key = bip32.HDKey.from_base58(s) + return cls(key, fingerprint, parent_derivation, address_derivation) + +def parse_argument(e): + # for now int, HDKey or pubkey + # int + if len(e) < 4: + return int(e) + # pubkey + if len(e) == 66 or len(e) == 130: + return ec.PublicKey.parse(unhexlify(e)) + # otherwise - xpub + return DerivedKey.parse(e) + +def parse_descriptor(desc): + # remove checksum if it is there + desc = desc.split("#")[0] + # for now only pkh, sh, wpkh, wsh, multi, sortedmulti + is_multisig = False + wrappers = [] + d = desc + m = re.match('(\w+)\((.*)\)$', d) + while m: + wrappers.append(m.group(1)) + d = m.group(2) + m = re.match('(\w+)\((.*)\)$', d) + wrappers = list(reversed([DESCRIPTOR_SCRIPTS[w] for w in wrappers])) + args = [] + for e in d.split(","): + args.append(parse_argument(e)) + return wrappers, args + +class Wallet: + def __init__(self, name, descriptor, network): + self.name = name + self.descriptor = descriptor + self.network = network + # FIXME: parse descriptor here + self.wrappers, self.args = parse_descriptor(descriptor) + + def address(self, idx, change=False): + args = [] + # derive args if possible + for arg in self.args: + # if DerivedKey we should get public key with right index + try: + pub = arg.key.child(int(change)).child(idx).key + args.append(pub) + except: + args.append(arg) + sc = self.wrappers[0](*args) + for wrapper in self.wrappers[1:]: + sc = wrapper(sc) + print(sc) + return sc.address(network=self.network) + + def save(self, fname): + obj = {"name": self.name, "descriptor": self.descriptor} + data = json.dumps(obj) + with open(fname,"w") as f: + f.write(data) + return data + + @classmethod + def load(cls, fname, network): + with open(fname, "r") as f: + content = f.read() + return cls.parse(content, network) + + @classmethod + def parse(cls, s, network): + content = json.loads(s) + return cls(content["name"], content["descriptor"], network) diff --git a/main.py b/main.py index b543ac3..673e75a 100644 --- a/main.py +++ b/main.py @@ -48,7 +48,8 @@ def wallets_menu(): def show_xpub(name, derivation): xpub = keystore.get_xpub(derivation).to_base58() fingerprint = hexlify(keystore.fingerprint).decode('utf-8') - popups.show_xpub(name, xpub, fingerprint=fingerprint, derivation=derivation) + prefix = "[%s%s]" % (fingerprint, derivation[1:]) + popups.show_xpub(name, xpub, prefix=prefix) def xpubs_menu(): def selector(name, derivation): @@ -77,7 +78,22 @@ def select_network(name): global network if name in NETWORKS: network = NETWORKS[name] - set_default_xpubs(network) + if keystore.is_initialized: + set_default_xpubs(network) + # load existing wallets for this network + keystore.load_wallets(name) + # create a default wallet if it doesn't exist + if len(keystore.wallets) == 0: + # create a wallet descriptor + # this is not exactly compatible with Bitcoin Core though. + # '_' means 0/* or 1/* - standard receive and change + # derivation patterns + derivation = DEFAULT_XPUBS[0][1] + xpub = keystore.get_xpub(derivation).to_base58() + fingerprint = hexlify(keystore.fingerprint).decode('utf-8') + prefix = "[%s%s]" % (fingerprint, derivation[1:]) + descriptor = "wpkh(%s%s/_)" % (prefix, xpub) + keystore.create_wallet("Default", descriptor) else: raise RuntimeError("Unknown network") @@ -195,12 +211,12 @@ def init_keys(password): mnemonic = bip39.mnemonic_from_bytes(entropy) seed = bip39.mnemonic_to_seed(mnemonic, password) keystore.load_seed(seed) + # choose testnet by default + select_network("test") show_main() def main(blocking=True): gui.init() - # choose testnet by default - select_network("test") show_init() if blocking: while True: From 3c0c0c22dbc02d22784a30c5288500dadfa9c944 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Fri, 15 Nov 2019 23:14:05 +0100 Subject: [PATCH 04/11] wallet addresses navigation --- gui/common.py | 12 ++++++------ gui/popups.py | 30 +++++++++++++++++++++++++++--- keystore.py | 35 ++++++++++++++++++++++++----------- main.py | 18 ++++++++++++++---- 4 files changed, 71 insertions(+), 24 deletions(-) diff --git a/gui/common.py b/gui/common.py index 9477a32..d406426 100644 --- a/gui/common.py +++ b/gui/common.py @@ -59,12 +59,12 @@ def add_mnemonic(mnemonic, scr=None, y=200): def add_button_pair(text1, callback1, text2, callback2, scr=None, y=700): """Helper function that creates a button with a text label""" w = (HOR_RES-3*PADDING)//2 - btn = add_button(text1, callback1, scr=scr, y=y) - btn.set_width(w) - btn = add_button(text2, callback2, scr=scr, y=y) - btn.set_width(w) - btn.set_x(HOR_RES//2+PADDING//2) - return btn + btn1 = add_button(text1, callback1, scr=scr, y=y) + btn1.set_width(w) + btn2 = add_button(text2, callback2, scr=scr, y=y) + btn2.set_width(w) + btn2.set_x(HOR_RES//2+PADDING//2) + return btn1, btn2 def qr_update(lbl, text): qr = qrcode.encode_to_string(text) diff --git a/gui/popups.py b/gui/popups.py index b31251f..5feb342 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -43,7 +43,7 @@ def cb_cancel(obj, event): def error(message): alert("Error!", message) -def qr_alert(title, message, message_text=None, callback=None): +def qr_alert(title, message, message_text=None, callback=None, ok_text="OK"): old_scr = lv.scr_act() scr = lv.obj() lv.scr_load(scr) @@ -60,7 +60,7 @@ def cb(obj, event): gc.collect() if callback is not None: callback() - add_button("OK", cb) + add_button(ok_text, cb) # objects to manupulate if necessary return (qrobj, msg_obj) @@ -82,4 +82,28 @@ def cb(): def show_mnemonic(mnemonic): alert("Your recovery phrase:", "") - add_mnemonic_table(mnemonic, y=100) \ No newline at end of file + add_mnemonic_table(mnemonic, y=100) + +def show_wallet(wallet): + idx = 0 + addr = wallet.address(idx) + qrobj, msgobj = qr_alert("Wallet \"%s\"" % wallet.name, "bitcoin:"+addr, addr, ok_text="Close") + lbl = add_label("Receiving address #%d" % idx, y=80) + def cb_update(delta): + idx = int(lbl.get_text().split("#")[1]) + if idx+delta >= 0: + idx += delta + addr = wallet.address(idx) + msgobj.set_text(addr) + qr_update(qrobj, "bitcoin:"+addr) + lbl.set_text("Receiving address #%d" % idx) + if idx > 0: + prv.set_state(lv.btn.STATE.REL) + else: + prv.set_state(lv.btn.STATE.INA) + def cb_next(): + cb_update(1) + def cb_prev(): + cb_update(-1) + prv, nxt = add_button_pair("Previous", on_release(cb_prev), "Next", on_release(cb_next), y=600) + prv.set_state(lv.btn.STATE.INA) diff --git a/keystore.py b/keystore.py index def57e0..2396420 100644 --- a/keystore.py +++ b/keystore.py @@ -61,16 +61,15 @@ def load_wallets(self, network_name): files = [f[0] for f in os.ilistdir(self.storage_path) if f[0].endswith("_wallet.json")] self._wallets = [] for fname in files: - with open(self.storage_path+fname) as f: + fname = self.storage_path+fname + with open(fname) as f: content = f.read() - with open(self.storage_path+fname.replace(".json",".sig"),"rb") as f: + with open(fname.replace(".json",".sig"),"rb") as f: sig = ec.Signature.parse(f.read()) if self.idkey.verify(sig, hashes.sha256(content)): - self._wallets.append(Wallet.parse(content, self.network)) + self._wallets.append(Wallet.parse(content, self.network, fname=fname)) else: raise RuntimeError("Invalid signature for wallet") - for w in self._wallets: - print(w.name, w.descriptor) def get_wallet_fname(self): files = [int(f[0].split("_")[0]) for f in os.ilistdir(self.storage_path) if f[0].endswith("_wallet.json")] @@ -82,8 +81,11 @@ def get_wallet_fname(self): return fname def create_wallet(self, name, descriptor): - w = Wallet(name, descriptor, self.network) + for w in self._wallets: + if w.name == name: + raise ValueError("Wallet '%s' already exists", name) fname = self.get_wallet_fname() + w = Wallet(name, descriptor, self.network, fname=fname) data = w.save(fname) h = hashes.sha256(data) sig = self.idkey.sign(h) @@ -91,6 +93,17 @@ def create_wallet(self, name, descriptor): f.write(sig.serialize()) self._wallets.append(w) + def delete_wallet(self, w): + if w in self._wallets: + self._wallets.pop(w) + os.remove(w.fname) + os.remove(w.fname.replace(".json",".sig")) + + def get_wallet_by_name(self, name): + for w in self.wallets: + if w == name: + return w + @property def wallets(self): return self._wallets @@ -159,7 +172,8 @@ def parse_descriptor(desc): return wrappers, args class Wallet: - def __init__(self, name, descriptor, network): + def __init__(self, name, descriptor, network, fname=None): + self.fname = fname self.name = name self.descriptor = descriptor self.network = network @@ -179,7 +193,6 @@ def address(self, idx, change=False): sc = self.wrappers[0](*args) for wrapper in self.wrappers[1:]: sc = wrapper(sc) - print(sc) return sc.address(network=self.network) def save(self, fname): @@ -193,9 +206,9 @@ def save(self, fname): def load(cls, fname, network): with open(fname, "r") as f: content = f.read() - return cls.parse(content, network) + return cls.parse(content, network, fname) @classmethod - def parse(cls, s, network): + def parse(cls, s, network, fname=None): content = json.loads(s) - return cls(content["name"], content["descriptor"], network) + return cls(content["name"], content["descriptor"], network, fname) diff --git a/main.py b/main.py index 673e75a..998fb86 100644 --- a/main.py +++ b/main.py @@ -42,8 +42,18 @@ def cancel_scan(): qr_scanner.stop() show_main() +def select_wallet(w): + popups.show_wallet(w) + def wallets_menu(): - pass + buttons = [] + def wrapper(w): + def cb(): + select_wallet(w) + return cb + for wallet in keystore.wallets: + buttons.append((wallet.name, wrapper(wallet))) + gui.create_menu(buttons=buttons, cb_back=show_main, title="Select the wallet") def show_xpub(name, derivation): xpub = keystore.get_xpub(derivation).to_base58() @@ -60,7 +70,7 @@ def cb(): for name, derivation in DEFAULT_XPUBS: buttons.append((name, selector(name, derivation))) # buttons.append(("Back to Main menu", show_main)) - gui.create_menu(buttons=buttons, cb_back=show_main) + gui.create_menu(buttons=buttons, cb_back=show_main, title="Select the master key") def scan_transaction(): pass @@ -114,7 +124,7 @@ def cb(): ("Testnet", selector("test")), ("Regtest", selector("regtest")), ("Signet", selector("signet")) - ]) + ], title="Select the network") def show_mnemonic(): @@ -144,7 +154,7 @@ def reckless_menu(): ("Show recovery phrase", show_mnemonic), ("Save key to memory", save_entropy), ("Delete key from memory", delete_entropy) - ], cb_back=show_main) + ], cb_back=show_main,title="Careful. Think twice.") def show_main(): From a377e70400b743dc7e60c8689241eb684b1db445 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sat, 16 Nov 2019 13:43:38 +0100 Subject: [PATCH 05/11] address verification --- gui/core.py | 2 +- keystore.py | 17 ++++++++++++----- main.py | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/gui/core.py b/gui/core.py index fdd2c1e..5e2b649 100644 --- a/gui/core.py +++ b/gui/core.py @@ -23,10 +23,10 @@ def init(): lv.scr_load(scr) def update(dt:int=30): - time.sleep_ms(dt) display.update(dt) handle_queue() def ioloop(dt:int=30): while True: + time.sleep_ms(dt) update(dt) diff --git a/keystore.py b/keystore.py index 2396420..6fcf95a 100644 --- a/keystore.py +++ b/keystore.py @@ -3,7 +3,7 @@ import os import ujson as json import ure as re -from ubinascii import unhexlify +from ubinascii import unhexlify, hexlify is_simulator = False try: @@ -50,10 +50,17 @@ def get_xpub(self, derivation): def load_wallets(self, network_name): self.network = NETWORKS[network_name] + fingerprint = hexlify(self.fingerprint).decode('utf-8') if is_simulator: - self.storage_path = "%s/" % network_name + self.storage_path = "%s/%s" % (network_name, fingerprint) else: - self.storage_path = "/flash/%s/" % network_name + self.storage_path = "/flash/%s/%s" % (network_name, fingerprint) + # FIXME: refactor with for loop + try: # network folder + d = "/".join(self.storage_path.split("/")[:-1]) + os.mkdir(d) + except: + pass try: os.mkdir(self.storage_path) except: @@ -61,7 +68,7 @@ def load_wallets(self, network_name): files = [f[0] for f in os.ilistdir(self.storage_path) if f[0].endswith("_wallet.json")] self._wallets = [] for fname in files: - fname = self.storage_path+fname + fname = self.storage_path+"/"+fname with open(fname) as f: content = f.read() with open(fname.replace(".json",".sig"),"rb") as f: @@ -77,7 +84,7 @@ def get_wallet_fname(self): idx = max(files)+1 else: idx = 0 - fname = self.storage_path+("%d_wallet.json" % idx) + fname = self.storage_path+"/"+("%d_wallet.json" % idx) return fname def create_wallet(self, name, descriptor): diff --git a/main.py b/main.py index 998fb86..0a624e7 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ from gui.decorators import queued import gui.common +import utime as time import urandom, os import ujson as json from ubinascii import hexlify, unhexlify @@ -69,14 +70,45 @@ def cb(): buttons = [] for name, derivation in DEFAULT_XPUBS: buttons.append((name, selector(name, derivation))) - # buttons.append(("Back to Main menu", show_main)) gui.create_menu(buttons=buttons, cb_back=show_main, title="Select the master key") def scan_transaction(): + # show_main() + # gui.update(100) pass +def verify_address(s): + # we will go here afterwards + show_main() + # we need to update gui because screens are queued + gui.update(100) + # verifies address in the form [bitcoin:]addr?index=i + s = s.replace("bitcoin:", "") + arr = s.split("?") + index = None + addr = None + # check that ?index= is there + if len(arr) > 1: + addr = arr[0] + meta_arr = arr[1].split("&") + # search for `index=` + for meta in meta_arr: + if meta.startswith("index="): + index = int(meta.split("=")[1]) + if index is None or addr is None: + # where we will go next + gui.error("No derivation index in the address metadata - can't verify.") + return + for w in keystore.wallets: + if w.address(index) == addr: + popups.qr_alert("Address #%d from wallet\n\"%s\"" % (index+1, w.name), addr, message_text=addr) + return + gui.error("Address doesn't belong to any wallet. Wrong device or network?") + def scan_address(): - pass + screens.show_progress("Scan address to verify", "Scanning.. Click \"Cancel\" to stop.", callback=cancel_scan) + gui.update(30) + qr_scanner.start_scan(verify_address) def set_default_xpubs(net): while len(DEFAULT_XPUBS) > 0: @@ -230,7 +262,8 @@ def main(blocking=True): show_init() if blocking: while True: - gui.update() + time.sleep_ms(30) + gui.update(30) qr_scanner.update() if __name__ == '__main__': From e43f7770d68e5642403c78591b540ee945faaab1 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sat, 16 Nov 2019 15:34:12 +0100 Subject: [PATCH 06/11] transaction display and sign --- gui/common.py | 1 + gui/popups.py | 2 +- keystore.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++-- main.py | 65 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 142 insertions(+), 8 deletions(-) diff --git a/gui/common.py b/gui/common.py index d406426..d2fdce6 100644 --- a/gui/common.py +++ b/gui/common.py @@ -67,6 +67,7 @@ def add_button_pair(text1, callback1, text2, callback2, scr=None, y=700): return btn1, btn2 def qr_update(lbl, text): + print("QRcode on the screen:", text) qr = qrcode.encode_to_string(text) size = int(math.sqrt(len(qr))) width = HOR_RES diff --git a/gui/popups.py b/gui/popups.py index 5feb342..f6d29c7 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -49,7 +49,7 @@ def qr_alert(title, message, message_text=None, callback=None, ok_text="OK"): lv.scr_load(scr) add_label(title, style="title") qrobj = add_qrcode(message, scr=scr, y=PADDING+100) - msgobj = None + msg_obj = None if message_text is not None: y = qrobj.get_y()+qrobj.get_height()+20 msg_obj = add_label(message_text, y=y) diff --git a/keystore.py b/keystore.py index 6fcf95a..4908624 100644 --- a/keystore.py +++ b/keystore.py @@ -119,6 +119,42 @@ def wallets(self): def is_initialized(self): return (self.root is not None) + def check_psbt(self, tx): + obj = {} + # print how much we are spending and where + total_in = 0 + wallet = None + # detect wallet tx inputs belong to + for w in self.wallets: + if w.owns(tx.inputs[0]): + wallet = w + break + if wallet is None: + raise ValueError("Can't find the wallet owning input") + # calculate the amount we are spending + for inp in tx.inputs: + total_in += inp.witness_utxo.value + if not wallet.owns(psbt_in=inp): + raise ValueError("Mixed inputs from different wallets!") + obj["total_in"] = total_in + change_out = 0 # value that goes back to us + send_outputs = [] + for i, out in enumerate(tx.outputs): + # check if it is a change or not: + if wallet.owns(psbt_out=out, tx_out=tx.tx.vout[i]): + change_out += tx.tx.vout[i].value + else: + send_outputs.append(tx.tx.vout[i]) + obj["wallet"] = wallet + obj["spending"] = total_in-change_out + obj["send_outputs"] = [{"value": out.value, "address": out.script_pubkey.address(self.network)} for out in send_outputs] + obj["fee"] = total_in-change_out-sum([out.value for out in send_outputs]) + return obj + + def sign(self, tx): + tx.sign_with(self.root) + return tx + class DerivedKey: def __init__(self, key, fingerprint=None, parent_derivation=None, address_derivation="_"): self.key = key @@ -184,10 +220,9 @@ def __init__(self, name, descriptor, network, fname=None): self.name = name self.descriptor = descriptor self.network = network - # FIXME: parse descriptor here self.wrappers, self.args = parse_descriptor(descriptor) - def address(self, idx, change=False): + def script(self, idx, change=False): args = [] # derive args if possible for arg in self.args: @@ -200,8 +235,51 @@ def address(self, idx, change=False): sc = self.wrappers[0](*args) for wrapper in self.wrappers[1:]: sc = wrapper(sc) + return sc + + def address(self, idx, change=False): + sc = self.script(idx, change) return sc.address(network=self.network) + def owns(self, psbt_in=None, psbt_out=None, tx_out=None): + """Pass psbt_in or psbt_out + tx_out to check if it is owned by the wallet""" + # FIXME: implement check + bip32_derivations = None + output = None + if psbt_in is not None: + bip32_derivations = psbt_in.bip32_derivations + output = psbt_in.witness_utxo + if psbt_out is not None: + bip32_derivations = psbt_out.bip32_derivations + output = tx_out + args = [] + for arg in self.args: + # check if it is DerivedKey + try: + fingerprint = arg.fingerprint + except: + args.append(arg) + continue + derived = False + parent_len = len(arg.parent_derivation) + for pub in bip32_derivations: + if ((bip32_derivations[pub].fingerprint == arg.fingerprint) and + (len(bip32_derivations[pub].derivation) == (parent_len+2)) and + (bip32_derivations[pub].derivation[:parent_len] == arg.parent_derivation)): + mypub = arg.key.derive(bip32_derivations[pub].derivation[parent_len:]).key + if mypub != pub: + return False + else: + args.append(mypub) + derived = True + break + if not derived: + return False + sc = self.wrappers[0](*args) + for wrapper in self.wrappers[1:]: + sc = wrapper(sc) + return (sc == output.script_pubkey) + def save(self, fname): obj = {"name": self.name, "descriptor": self.descriptor} data = json.dumps(obj) diff --git a/main.py b/main.py index 0a624e7..349d589 100644 --- a/main.py +++ b/main.py @@ -7,13 +7,17 @@ import urandom, os import ujson as json from ubinascii import hexlify, unhexlify +# base64 encoding +from ubinascii import a2b_base64, b2a_base64 from bitcoin import ec, hashes, bip39, bip32 from bitcoin.networks import NETWORKS +from bitcoin import psbt from keystore import KeyStore from qrscanner import QRScanner + qr_scanner = QRScanner() # entropy that will be converted to mnemonic @@ -72,13 +76,60 @@ def cb(): buttons.append((name, selector(name, derivation))) gui.create_menu(buttons=buttons, cb_back=show_main, title="Select the master key") +def sign_psbt(wallet=None, tx=None): + keystore.sign(tx) + # remove everything but partial sigs + # to reduce QR code size + tx.unknown = {} + tx.xpubs = {} + for i in range(len(tx.inputs)): + tx.inputs[i].unknown = {} + tx.inputs[i].non_witness_utxo = None + tx.inputs[i].witness_utxo = None + tx.inputs[i].sighash_type = None + tx.inputs[i].bip32_derivations = {} + tx.inputs[i].witness_script = None + tx.inputs[i].redeem_script = None + for i in range(len(tx.outputs)): + tx.outputs[i].unknown = {} + tx.outputs[i].bip32_derivations = {} + tx.outputs[i].witness_script = None + tx.outputs[i].redeem_script = None + b64_tx = b2a_base64(tx.serialize()).decode('utf-8') + if b64_tx[-1:] == "\n": + b64_tx = b64_tx[:-1] + popups.qr_alert("Signed transaction:", b64_tx, "Scan it with your software wallet") + +def parse_transaction(b64_tx): + # we will go to main afterwards + show_main() + # we need to update gui because screens are queued + gui.update(100) + try: + raw = a2b_base64(b64_tx) + tx = psbt.PSBT.parse(raw) + except: + gui.error("Failed at transaction parsing") + return + try: + data = keystore.check_psbt(tx) + except Exception as e: + gui.error("Problem with the transaction: %r" % e) + return + title = "Spending %u\nfrom %s" % (data["spending"], data["wallet"].name) + message = "" + for out in data["send_outputs"]: + message += "%u sat to %s\n" % (out["value"], out["address"]) + message += "\nFee: %u satoshi" % data["fee"] + popups.prompt(title, message, ok=sign_psbt, wallet=data["wallet"], tx=tx) + def scan_transaction(): - # show_main() - # gui.update(100) - pass + screens.show_progress("Scan transaction to sign", "Scanning.. Click \"Cancel\" to stop.", callback=cancel_scan) + gui.update(30) + qr_scanner.start_scan(parse_transaction) def verify_address(s): - # we will go here afterwards + # we will go to main afterwards show_main() # we need to update gui because screens are queued gui.update(100) @@ -94,7 +145,11 @@ def verify_address(s): # search for `index=` for meta in meta_arr: if meta.startswith("index="): - index = int(meta.split("=")[1]) + try: + index = int(meta.split("=")[1]) + except: + gui.error("Index is not an integer...") + return if index is None or addr is None: # where we will go next gui.error("No derivation index in the address metadata - can't verify.") From 396ae928d2fb46d46ee691729be2c59b89b4a90d Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sat, 16 Nov 2019 17:41:44 +0100 Subject: [PATCH 07/11] rng module --- main.py | 6 +++--- rng.py | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 rng.py diff --git a/main.py b/main.py index 349d589..ebf8409 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ import gui.common import utime as time -import urandom, os +import os import ujson as json from ubinascii import hexlify, unhexlify # base64 encoding @@ -16,7 +16,7 @@ from keystore import KeyStore from qrscanner import QRScanner - +from rng import get_random_bytes qr_scanner = QRScanner() @@ -258,7 +258,7 @@ def show_main(): def get_new_mnemonic(words=12): entropy_len = words*4//3 global entropy - entropy = bytes([urandom.getrandbits(8) for i in range(entropy_len)]) + entropy = get_random_bytes(entropy_len) return bip39.mnemonic_from_bytes(entropy) def gen_new_key(words=12): diff --git a/rng.py b/rng.py new file mode 100644 index 0000000..f9ef8aa --- /dev/null +++ b/rng.py @@ -0,0 +1,10 @@ +# random number generator +# if os.urandom is available - entropy goes from TRNG +# otherwise - use whatever we have +# FIXME: mix in extra entropy +try: + from os import urandom as get_random_bytes +except: + import urandom + def get_random_bytes(nbytes): + return bytes([urandom.getrandbits(8) for i in range(nbytes)]) From 23ec571cdd1eee5184641bf1fcd6f0913175388f Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Mon, 18 Nov 2019 11:41:31 +0100 Subject: [PATCH 08/11] some formatting and improved derivation check --- README.md | 5 ++--- gui/popups.py | 8 ++++++-- keystore.py | 8 +++++++- main.py | 23 +++++++++++++++++------ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index fa0f9ef..dde38f1 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,12 @@ We are also working on the kit that you could buy from us that will include a 3d * When the board is done loading the bin the board will reset itself and begin running the Specter UI. * The on-screen UI should prompt you to calibrate the screen and then will take you to the Specter UI's "What do you want to do?" startup screen. - ## Dev plan -- [ ] Single key functionality +- [x] Single key functionality - [x] Reckless storage - [ ] Multisig -- [ ] SD card support +- [ ] Dynamic SD card support - [ ] Secure element integration - [ ] Secure boot - [ ] DIY kit diff --git a/gui/popups.py b/gui/popups.py index f6d29c7..551cbe2 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -87,7 +87,9 @@ def show_mnemonic(mnemonic): def show_wallet(wallet): idx = 0 addr = wallet.address(idx) - qrobj, msgobj = qr_alert("Wallet \"%s\"" % wallet.name, "bitcoin:"+addr, addr, ok_text="Close") + qrobj, msgobj = qr_alert("Wallet \"%s\"" % wallet.name, + "bitcoin:"+addr, addr, + ok_text="Close") lbl = add_label("Receiving address #%d" % idx, y=80) def cb_update(delta): idx = int(lbl.get_text().split("#")[1]) @@ -105,5 +107,7 @@ def cb_next(): cb_update(1) def cb_prev(): cb_update(-1) - prv, nxt = add_button_pair("Previous", on_release(cb_prev), "Next", on_release(cb_next), y=600) + prv, nxt = add_button_pair("Previous", on_release(cb_prev), + "Next", on_release(cb_next), + y=600) prv.set_state(lv.btn.STATE.INA) diff --git a/keystore.py b/keystore.py index 4908624..c0d57bf 100644 --- a/keystore.py +++ b/keystore.py @@ -266,7 +266,13 @@ def owns(self, psbt_in=None, psbt_out=None, tx_out=None): if ((bip32_derivations[pub].fingerprint == arg.fingerprint) and (len(bip32_derivations[pub].derivation) == (parent_len+2)) and (bip32_derivations[pub].derivation[:parent_len] == arg.parent_derivation)): - mypub = arg.key.derive(bip32_derivations[pub].derivation[parent_len:]).key + der = bip32_derivations[pub].derivation[parent_len:] + if der[0] > 1: + raise ValueError("Change index cant be more than one") + if der[1] > 0x80000000: + raise ValueError("Address index cant be hardened") + # FIXME: add check if index is too large + mypub = arg.key.derive(der).key if mypub != pub: return False else: diff --git a/main.py b/main.py index ebf8409..9c4d59b 100644 --- a/main.py +++ b/main.py @@ -124,7 +124,9 @@ def parse_transaction(b64_tx): popups.prompt(title, message, ok=sign_psbt, wallet=data["wallet"], tx=tx) def scan_transaction(): - screens.show_progress("Scan transaction to sign", "Scanning.. Click \"Cancel\" to stop.", callback=cancel_scan) + screens.show_progress("Scan transaction to sign", + "Scanning.. Click \"Cancel\" to stop.", + callback=cancel_scan) gui.update(30) qr_scanner.start_scan(parse_transaction) @@ -156,12 +158,15 @@ def verify_address(s): return for w in keystore.wallets: if w.address(index) == addr: - popups.qr_alert("Address #%d from wallet\n\"%s\"" % (index+1, w.name), addr, message_text=addr) + popups.qr_alert("Address #%d from wallet\n\"%s\"" % (index+1, w.name), + addr, message_text=addr) return gui.error("Address doesn't belong to any wallet. Wrong device or network?") def scan_address(): - screens.show_progress("Scan address to verify", "Scanning.. Click \"Cancel\" to stop.", callback=cancel_scan) + screens.show_progress("Scan address to verify", + "Scanning.. Click \"Cancel\" to stop.", + callback=cancel_scan) gui.update(30) qr_scanner.start_scan(verify_address) @@ -263,10 +268,16 @@ def get_new_mnemonic(words=12): def gen_new_key(words=12): mnemonic = get_new_mnemonic(words) - screens.new_mnemonic(mnemonic, cb_continue=ask_for_password, cb_back=show_init, cb_update=get_new_mnemonic) + screens.new_mnemonic(mnemonic, + cb_continue=ask_for_password, + cb_back=show_init, + cb_update=get_new_mnemonic) def recover_key(): - screens.ask_for_mnemonic(cb_continue=mnemonic_entered, cb_back=show_init, check_mnemonic=bip39.mnemonic_is_valid, words_lookup=bip39.find_candidates) + screens.ask_for_mnemonic(cb_continue=mnemonic_entered, + cb_back=show_init, + check_mnemonic=bip39.mnemonic_is_valid, + words_lookup=bip39.find_candidates) def mnemonic_entered(mnemonic): global entropy @@ -294,7 +305,7 @@ def show_init(): with open(reckless_fname,"r") as f: c = f.read() if len(c) == 0: - raise RuntimeError("File is empty") + raise RuntimeError("%s file is empty" % reckless_fname) # if ok - add an extra button buttons.append(("Load key from memory", load_key)) except: From 8adfd86d015feef9157cc8d78357ce4ae724eaee Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Mon, 18 Nov 2019 12:50:54 +0100 Subject: [PATCH 09/11] multisig wallets --- keystore.py | 23 +++++++++++++++++++++++ main.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/keystore.py b/keystore.py index c0d57bf..1deaf67 100644 --- a/keystore.py +++ b/keystore.py @@ -34,6 +34,7 @@ def __init__(self, seed=None): self.storage_path = None self.load_seed(seed) self.idkey = None + self.fingerprint = None def load_seed(self, seed): if seed is not None: @@ -100,6 +101,28 @@ def create_wallet(self, name, descriptor): f.write(sig.serialize()) self._wallets.append(w) + def check_new_wallet(self, name, descriptor): + for w in self._wallets: + if w.name == name: + raise ValueError("Wallet \"%s\" already exists" % name) + try: + w = Wallet(name, descriptor, self.network) + except: + raise ValueError("Descriptor is invalid") + includes_myself = False + for arg in w.args: + try: + fingerprint = arg.fingerprint + except: + continue + if fingerprint == self.fingerprint: + key = self.root.derive(arg.parent_derivation).to_public() + if arg.key == key: + includes_myself = True + if not includes_myself: + raise ValueError("Wallet is not controlled by my key") + + def delete_wallet(self, w): if w in self._wallets: self._wallets.pop(w) diff --git a/main.py b/main.py index 9c4d59b..e2245bc 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ from gui import screens, popups from gui.decorators import queued import gui.common +import lvgl as lv import utime as time import os @@ -50,6 +51,33 @@ def cancel_scan(): def select_wallet(w): popups.show_wallet(w) +def new_wallet_confirm(name, descriptor): + # print("creating wallet %s:" % name,descriptor) + keystore.create_wallet(name, descriptor) + +def confirm_new_wallet(s): + show_main() + gui.update(30) + # wallet format: + # name&descriptor + arr = s.split("&") + if len(arr) != 2: + gui.error("Invalid wallet format") + return + try: + keystore.check_new_wallet(*arr) + except Exception as e: + gui.error("%r" % e) + return + popups.prompt("Add wallet \"%s\"?" % arr[0], arr[1], ok=new_wallet_confirm, name=arr[0], descriptor=arr[1]) + +def add_new_wallet(): + screens.show_progress("Scan wallet to add", + "Scanning.. Click \"Cancel\" to stop.", + callback=cancel_scan) + gui.update(30) + qr_scanner.start_scan(confirm_new_wallet) + def wallets_menu(): buttons = [] def wrapper(w): @@ -58,6 +86,7 @@ def cb(): return cb for wallet in keystore.wallets: buttons.append((wallet.name, wrapper(wallet))) + buttons.append((lv.SYMBOL.PLUS+" Add new wallet (scan)", add_new_wallet)) gui.create_menu(buttons=buttons, cb_back=show_main, title="Select the wallet") def show_xpub(name, derivation): From 377e7eaf9326724b9ba5d651307051cb6a2864a8 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Tue, 19 Nov 2019 14:12:10 +0100 Subject: [PATCH 10/11] qr size and re fix --- gui/common.py | 14 ++++++++------ gui/popups.py | 17 ++++++++++++----- keystore.py | 22 ++++++++++------------ main.py | 9 +++++++-- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/gui/common.py b/gui/common.py index d2fdce6..81b530c 100644 --- a/gui/common.py +++ b/gui/common.py @@ -18,8 +18,10 @@ def init_styles(): lv.style_copy(styles["title"], lv.style_plain) styles["title"].text.font = lv.font_roboto_28 -def add_label(text, y=PADDING, scr=None, style=None): +def add_label(text, y=PADDING, scr=None, style=None, width=None): """Helper functions that creates a title-styled label""" + if width is None: + width = HOR_RES-2*PADDING if scr is None: scr = lv.scr_act() lbl = lv.label(scr) @@ -27,8 +29,8 @@ def add_label(text, y=PADDING, scr=None, style=None): if style in styles: lbl.set_style(0, styles[style]) lbl.set_long_mode(lv.label.LONG.BREAK) - lbl.set_width(HOR_RES-2*PADDING) - lbl.set_x(PADDING) + lbl.set_width(width) + lbl.set_x((HOR_RES-width)//2) lbl.set_align(lv.label.ALIGN.CENTER) lbl.set_y(y) return lbl @@ -70,7 +72,7 @@ def qr_update(lbl, text): print("QRcode on the screen:", text) qr = qrcode.encode_to_string(text) size = int(math.sqrt(len(qr))) - width = HOR_RES + width = lbl.get_width() scale = width//(size+4) sizes = [1,2,3,5,7,10] fontsize = [s for s in sizes if s < scale][-1] @@ -89,14 +91,14 @@ def qr_update(lbl, text): del qr gc.collect() -def add_qrcode(text, y=QR_PADDING, scr=None, style=None): +def add_qrcode(text, y=QR_PADDING, scr=None, style=None, width=None): """Helper functions that creates a title-styled label""" if scr is None: scr = lv.scr_act() scr = lv.scr_act() - lbl = add_label("Text", y=y, scr=scr) + lbl = add_label("Text", y=y, scr=scr, width=width) qr_update(lbl, text) return lbl diff --git a/gui/popups.py b/gui/popups.py index 551cbe2..b6fdf6b 100644 --- a/gui/popups.py +++ b/gui/popups.py @@ -43,12 +43,12 @@ def cb_cancel(obj, event): def error(message): alert("Error!", message) -def qr_alert(title, message, message_text=None, callback=None, ok_text="OK"): +def qr_alert(title, message, message_text=None, callback=None, ok_text="OK", width=None): old_scr = lv.scr_act() scr = lv.obj() lv.scr_load(scr) add_label(title, style="title") - qrobj = add_qrcode(message, scr=scr, y=PADDING+100) + qrobj = add_qrcode(message, scr=scr, y=PADDING+100, width=width) msg_obj = None if message_text is not None: y = qrobj.get_y()+qrobj.get_height()+20 @@ -84,12 +84,13 @@ def show_mnemonic(mnemonic): alert("Your recovery phrase:", "") add_mnemonic_table(mnemonic, y=100) -def show_wallet(wallet): +def show_wallet(wallet, delete_cb=None): + old_scr = lv.scr_act() idx = 0 addr = wallet.address(idx) qrobj, msgobj = qr_alert("Wallet \"%s\"" % wallet.name, "bitcoin:"+addr, addr, - ok_text="Close") + ok_text="Close", width=300) lbl = add_label("Receiving address #%d" % idx, y=80) def cb_update(delta): idx = int(lbl.get_text().split("#")[1]) @@ -107,7 +108,13 @@ def cb_next(): cb_update(1) def cb_prev(): cb_update(-1) + def cb_del(): + delete_cb(wallet) + lv.scr_load(old_scr) + qrobj.delete() + gc.collect() + delbtn = add_button("Delete wallet", on_release(cb_del), y=600) prv, nxt = add_button_pair("Previous", on_release(cb_prev), "Next", on_release(cb_next), - y=600) + y=500) prv.set_state(lv.btn.STATE.INA) diff --git a/keystore.py b/keystore.py index 1deaf67..23731f4 100644 --- a/keystore.py +++ b/keystore.py @@ -105,10 +105,10 @@ def check_new_wallet(self, name, descriptor): for w in self._wallets: if w.name == name: raise ValueError("Wallet \"%s\" already exists" % name) - try: - w = Wallet(name, descriptor, self.network) - except: - raise ValueError("Descriptor is invalid") + # try: + w = Wallet(name, descriptor, self.network) + # except: + # raise ValueError("Descriptor is invalid") includes_myself = False for arg in w.args: try: @@ -122,10 +122,10 @@ def check_new_wallet(self, name, descriptor): if not includes_myself: raise ValueError("Wallet is not controlled by my key") - def delete_wallet(self, w): if w in self._wallets: - self._wallets.pop(w) + idx = self._wallets.index(w) + self._wallets.pop(idx) os.remove(w.fname) os.remove(w.fname.replace(".json",".sig")) @@ -224,13 +224,11 @@ def parse_descriptor(desc): desc = desc.split("#")[0] # for now only pkh, sh, wpkh, wsh, multi, sortedmulti is_multisig = False - wrappers = [] d = desc - m = re.match('(\w+)\((.*)\)$', d) - while m: - wrappers.append(m.group(1)) - d = m.group(2) - m = re.match('(\w+)\((.*)\)$', d) + # re.match doesnt work for some reason + arr = d.split("(") + wrappers = arr[:-1] + d = arr[-1].split(")")[0] wrappers = list(reversed([DESCRIPTOR_SCRIPTS[w] for w in wrappers])) args = [] for e in d.split(","): diff --git a/main.py b/main.py index e2245bc..b0bec62 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ import lvgl as lv import utime as time -import os +import os, gc import ujson as json from ubinascii import hexlify, unhexlify # base64 encoding @@ -48,8 +48,12 @@ def cancel_scan(): qr_scanner.stop() show_main() +def del_wallet(w): + keystore.delete_wallet(w) + show_main() + def select_wallet(w): - popups.show_wallet(w) + popups.show_wallet(w, delete_cb=del_wallet) def new_wallet_confirm(name, descriptor): # print("creating wallet %s:" % name,descriptor) @@ -350,6 +354,7 @@ def init_keys(password): keystore.load_seed(seed) # choose testnet by default select_network("test") + gc.collect() show_main() def main(blocking=True): From ed7a8e67c1df6c7c896a839511c38f3a6f2f2d07 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Tue, 19 Nov 2019 16:32:42 +0100 Subject: [PATCH 11/11] reorg, submodules and build script --- .gitmodules | 3 +++ README.md | 16 +++++++++++++--- build.sh | 10 ++++++++++ f469-disco | 1 + manifest.py | 2 ++ {gui => src/gui}/__init__.py | 0 {gui => src/gui}/common.py | 0 {gui => src/gui}/core.py | 0 {gui => src/gui}/decorators.py | 0 {gui => src/gui}/display_unixport.py | 0 {gui => src/gui}/popups.py | 0 {gui => src/gui}/screens.py | 0 keystore.py => src/keystore.py | 0 main.py => src/main.py | 0 qrscanner.py => src/qrscanner.py | 0 rng.py => src/rng.py | 0 16 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 100755 build.sh create mode 160000 f469-disco create mode 100644 manifest.py rename {gui => src/gui}/__init__.py (100%) rename {gui => src/gui}/common.py (100%) rename {gui => src/gui}/core.py (100%) rename {gui => src/gui}/decorators.py (100%) rename {gui => src/gui}/display_unixport.py (100%) rename {gui => src/gui}/popups.py (100%) rename {gui => src/gui}/screens.py (100%) rename keystore.py => src/keystore.py (100%) rename main.py => src/main.py (100%) rename qrscanner.py => src/qrscanner.py (100%) rename rng.py => src/rng.py (100%) diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1ea6e04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "f469-disco"] + path = f469-disco + url = https://github.com/diybitcoinhardware/f469-disco diff --git a/README.md b/README.md index dde38f1..33f2258 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ We are also working on the kit that you could buy from us that will include a 3d - [x] Single key functionality - [x] Reckless storage -- [ ] Multisig +- [x] Multisig - [ ] Dynamic SD card support - [ ] Secure element integration - [ ] Secure boot @@ -84,6 +84,16 @@ A few crappy pictures: ## Compiling the code yourself _(This is an optional step for developers. Typical users can just run off the pre-compiled `specter-diy.bin` file referenced above)_ -TBD. - Micropython now. Ref: https://github.com/diybitcoinhardware/f469-disco + +To compile the firmware you will need `arm-none-eabi-gcc` compiler. + +On MacOS install it using brew: `brew install arm-none-eabi-gcc` + +On Linux: `sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi gdb-arm-none-eabi openocd` + +Run `./build.sh` script, it may work. Or not. If not - please open an issue and we will try to figure out. + +At the end you should get a `specter-diy.bin` file in the root that you can copy to the device. + +Simulator: TBD. \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2a7d80f --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# mpy cross-compiler +pushd f469-disco/micropython/mpy-cross +make +popd +# f469 board +pushd f469-disco/micropython/ports/stm32 +make BOARD=STM32F469DISC USER_C_MODULES=../../../usermods FROZEN_MANIFEST=../../../../manifest.py +arm-none-eabi-objcopy -O binary build-STM32F469DISC/firmware.elf ../../../../specter-diy.bin +popd diff --git a/f469-disco b/f469-disco new file mode 160000 index 0000000..b8ac28c --- /dev/null +++ b/f469-disco @@ -0,0 +1 @@ +Subproject commit b8ac28cfb30fd82bd12efa137f755904926c971c diff --git a/manifest.py b/manifest.py new file mode 100644 index 0000000..ca23905 --- /dev/null +++ b/manifest.py @@ -0,0 +1,2 @@ +include('f469-disco/manifest_f469.py') +freeze('src') \ No newline at end of file diff --git a/gui/__init__.py b/src/gui/__init__.py similarity index 100% rename from gui/__init__.py rename to src/gui/__init__.py diff --git a/gui/common.py b/src/gui/common.py similarity index 100% rename from gui/common.py rename to src/gui/common.py diff --git a/gui/core.py b/src/gui/core.py similarity index 100% rename from gui/core.py rename to src/gui/core.py diff --git a/gui/decorators.py b/src/gui/decorators.py similarity index 100% rename from gui/decorators.py rename to src/gui/decorators.py diff --git a/gui/display_unixport.py b/src/gui/display_unixport.py similarity index 100% rename from gui/display_unixport.py rename to src/gui/display_unixport.py diff --git a/gui/popups.py b/src/gui/popups.py similarity index 100% rename from gui/popups.py rename to src/gui/popups.py diff --git a/gui/screens.py b/src/gui/screens.py similarity index 100% rename from gui/screens.py rename to src/gui/screens.py diff --git a/keystore.py b/src/keystore.py similarity index 100% rename from keystore.py rename to src/keystore.py diff --git a/main.py b/src/main.py similarity index 100% rename from main.py rename to src/main.py diff --git a/qrscanner.py b/src/qrscanner.py similarity index 100% rename from qrscanner.py rename to src/qrscanner.py diff --git a/rng.py b/src/rng.py similarity index 100% rename from rng.py rename to src/rng.py