Просмотр исходного кода

feat: flipbip > flipchess framework updates

Struan Clark 2 лет назад
Родитель
Сommit
44ca70e832

+ 30 - 0
.github/workflows/build.yml

@@ -0,0 +1,30 @@
+name: Build
+
+on:
+  push:
+    branches:
+      - main
+      - develop
+
+env:
+  firmware_version: '0.86.1'
+
+jobs:
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Flipper Zero Firmware
+        uses: actions/checkout@v3
+        with:
+          repository: 'flipperdevices/flipperzero-firmware'
+          ref: ${{ env.firmware_version }}
+          submodules: true
+      - name: Checkout flip-chess
+        uses: actions/checkout@v3
+        with:
+          path: 'applications_user/flip-chess'
+      - name: Build FAPs
+        run: ./fbt COMPACT=1 DEBUG=0 faps
+      - name: Check flip-chess Built
+        run: test -f build/f7-firmware-C/.extapps/flipchess.fap

+ 44 - 0
.github/workflows/release.yml

@@ -0,0 +1,44 @@
+name: Release
+
+on:
+  push:
+    tags:
+      - 'v[0-9]+.[0-9]+.[0-9]+'
+
+env:
+  firmware_version: '0.86.1'
+
+jobs:
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout Flipper Zero Firmware
+        uses: actions/checkout@v3
+        with:
+          repository: 'flipperdevices/flipperzero-firmware'
+          ref: ${{ env.firmware_version }}
+          submodules: true
+      - name: Checkout flip-chess
+        uses: actions/checkout@v3
+        with:
+          path: 'applications_user/flip-chess'
+      - name: Build FAPs
+        run: ./fbt COMPACT=1 DEBUG=0 faps
+      - name: Check flip-chess Built
+        run: test -f build/f7-firmware-C/.extapps/flip-chess.fap
+      - name: Get Tag
+        id: tag
+        uses: dawidd6/action-get-tag@v1
+        with:
+          strip_v: false
+      - name: Publish flip-chess
+        uses: softprops/action-gh-release@v1
+        with:
+          files: |
+            build/f7-firmware-C/.extapps/flip-chess.fap
+            applications_user/flip-chess/README.md
+          name: ${{steps.tag.outputs.tag}}
+          body: Built against Flipper Zero firmware v${{ env.firmware_version }}
+          generate_release_notes: true
+          fail_on_unmatched_files: true

+ 68 - 17
README.md

@@ -1,26 +1,77 @@
-# flip-onity-opener
+# flip-chess - BIP32/39/44
 
 
-Flipper zero exploiting vulnerability to open an Onity hotel room lock.
+[![Build](https://github.com/xtruan/flip-chess/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/xtruan/flip-chess/actions/workflows/build.yml)
 
 
-[Vulnerability described here](reference/brocious_bhpaper2018.md)
+## Crypto toolkit for Flipper Zero
+- Last built against `0.86.1` Flipper Zero firmware release
+- Using Trezor crypto libs from `core/v2.5.3` release
+- Included in [RogueMaster Custom Firmware](https://github.com/RogueMaster/flipperzero-firmware-wPlugins)
 
 
-### Installation
+### DONATE IF YOU FIND THE APP USEFUL
+  - ETH (or ERC-20): `xtruan.eth` or `0xa9Ad79502cdaf4F6881f3C2ef260713e5B771CE2`
+  - BTC: `16RP5Ui5QrWrVh2rR7NKAPwE5A4uFjCfbs`
 
 
-- Download [last release fap file](https://github.com/xtruan/flip-onity-opener/releases/latest)
-- Copy fap file to the apps folder of your Flipper SD card
+## Background
 
 
-### Usage
+The goal of this project is to see how much crypto functionality can be brought to the Flipper Zero.
 
 
-- Start "Onity Opener" plugin
-- Place wires as described on the plugin screen
-- Press enter
-- Open hotel lock
+## How to install on Flipper Zero
+- If you do not have one, download a Flipper Zero firmware to get the `fbt` build tool
+- Plug your Flipper Zero in via USB
+- Copy the contents of this folder into the `applications_user` folder of your firmware
+- Modify the `site_scons/cc.scons` file in the Flipper Zero firmware to add the `"-Os"` flag
 
 
-### Build
+Then run the command: 
+ ```
+./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=applications_user/flip-chess
+ ```
+The application will be compiled and copied onto your device
 
 
-- Recursively clone your base firmware (official or not)
-- Clone this repository in `applications_user`
-- Build with `./fbt fap_dist APPSRC=applications_user/flip-onity-opener`
-- Retreive build fap in dist subfolders
+## Status
 
 
-(More info about build tool [here](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md))
+### Complete
+
+- Trezor crypto C code ported into `crypto` subfolder
+  - Adapted to use Flipper hardware RNG (see `crypto/rand.c`)
+  - Imports and some C library functions modified for compatibility with FBT
+- Navigation and UI adapted from FAP Boilerplate app
+- BIP39 mnemonic generation
+  - 24, 18, or 12 words configured in settings
+- BIP39 mnemonic to BIP39 seed generation
+- Hierarchical Deterministic (HD) wallet generation from seed
+  - Generation of offline `m/44'/0'/0'/0` BTC wallet
+  - Generation of offline `m/44'/60'/0'/0` ETH wallet (coded from the $SPORK Castle of ETHDenver 2023!)
+  - Generation of offline `m/44'/3'/0'/0` DOGE wallet
+  - Similar features to: https://iancoleman.io/bip39/
+- Saving wallets to SD card
+  - Wallets are saved to SD card upon creation in `apps_data/flipchess`
+      - NOTE: `apps_data` folder must already exist on SD card!
+  - Saved wallets can be viewed between app runs
+  - Wallets are encrypted with a randomly generated key, and that key is also encrypted
+      - `.flipchess.dat` and `.flipchess.key` files are both required to be in `apps_data/flipchess`
+      - Backups of both these files `.flipchess.dat.bak` and `.flipchess.key.bak` are also maintained
+      - If you want to externally back up your wallet, I recommend copying all these files, and storing the `key` and `dat` files seperately
+  - NOTE: The wallets should be decently tough to crack off of a Flipper, however any Flipper with the app installed can load a wallet in the `apps_data/flipchess` directory if both the `key` and `dat` file are present. Therefore, it is HIGHLY RECOMMENDED to use the BIP39 passphrase functionality and store the passphrase in your brain or on paper separately from the Flipper!
+- BIP39 passphrase support
+  - Configured in settings, not persisted between runs for security
+- Import your own mnemonic
+  - Lots of typing required but you can now use the wallet with an existing mnemonic you have saved
+  - Useful to convert paper backup to keys and receive addresses without relying on a laptop or phone
+- Improved receive address generation features
+  - Addresses are now generated at the same time as other pieces of wallet info
+    - This slows down initial wallet load, but makes UI much more responsive
+  - QR code files are now generated for each address and stored in the `apps_data/flipchess` directory
+    - This app is required to view the QR code files: https://github.com/bmatcuk/flipperzero-qrcode (included in RM firmware)
+    - NOTE: This happens during the `View Wallet` step; you must view a wallet after generating/importing a wallet in order to ensure the address QR files are correct
+- Broke out crypto functionality into its own library using `fap_private_libs` feature
+
+### Work in Progress
+
+- More coin types
+  - Support for more custom BIP32 wallet paths
+
+### (FAR) Future
+
+- Custom wallet security
+  - User specified password
+- USB/Bluetooth wallet functionality

+ 6 - 1
application.fam

@@ -3,9 +3,14 @@ App(
     name="Chess",
     name="Chess",
     apptype=FlipperAppType.EXTERNAL,
     apptype=FlipperAppType.EXTERNAL,
     entry_point="flip_chess_app",
     entry_point="flip_chess_app",
-    requires=["gui"],
+        requires=[
+        "gui",
+    ],
     stack_size=2 * 1024,
     stack_size=2 * 1024,
     order=40,
     order=40,
     fap_icon="keycard_10px.png",
     fap_icon="keycard_10px.png",
     fap_category="Games",
     fap_category="Games",
+    fap_description="Crypto toolkit for Flipper",
+    fap_author="Struan Clark (xtruan)",
+    fap_weburl="https://github.com/xtruan/flip-chess",
 )
 )

+ 0 - 0
smallchesslib.h → chess/smallchesslib.h


+ 5 - 4
flip_chess_app.c

@@ -31,6 +31,7 @@ typedef struct {
 
 
 uint8_t paramPlayerW = 0;
 uint8_t paramPlayerW = 0;
 uint8_t paramPlayerB = 0;
 uint8_t paramPlayerB = 0;
+
 // uint8_t paramBoard = 1;
 // uint8_t paramBoard = 1;
 uint8_t paramAnalyze = 255; // depth of analysis
 uint8_t paramAnalyze = 255; // depth of analysis
 uint8_t paramMoves = 0;
 uint8_t paramMoves = 0;
@@ -38,17 +39,17 @@ uint8_t paramMoves = 0;
 uint8_t paramInfo = 1;
 uint8_t paramInfo = 1;
 //uint8_t paramDraw = 1;
 //uint8_t paramDraw = 1;
 uint8_t paramFlipBoard = 0;
 uint8_t paramFlipBoard = 0;
-uint8_t paramHelp = 0;
+//uint8_t paramHelp = 0;
 uint8_t paramExit = 0;
 uint8_t paramExit = 0;
 uint16_t paramStep = 0;
 uint16_t paramStep = 0;
 char* paramFEN = NULL;
 char* paramFEN = NULL;
 char* paramPGN = NULL;
 char* paramPGN = NULL;
-uint16_t paramRandom = 0;
-uint8_t paramBlind = 0;
+//uint16_t paramRandom = 0;
+//uint8_t paramBlind = 0;
+
 int clockSeconds = -1;
 int clockSeconds = -1;
 SCL_Game game;
 SCL_Game game;
 SCL_Board startState = SCL_BOARD_START_STATE;
 SCL_Board startState = SCL_BOARD_START_STATE;
-
 int16_t random960PosNumber = -1;
 int16_t random960PosNumber = -1;
 
 
 uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
 uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];

BIN
flipbip_10px.png


+ 199 - 0
flipchess.c

@@ -0,0 +1,199 @@
+#include "flipchess.h"
+#include "helpers/flipchess_haptic.h"
+
+bool flipchess_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    FlipChess* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+void flipchess_tick_event_callback(void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+//leave app if back button pressed
+bool flipchess_navigation_event_callback(void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void text_input_callback(void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    bool handled = false;
+
+    // check that there is text in the input
+    if(strlen(app->input_text) > 0) {
+        if(app->input_state == FlipChessTextInputPassphrase) {
+            if(app->passphrase == FlipChessPassphraseOn) {
+                strcpy(app->passphrase_text, app->input_text);
+            }
+            // clear input text
+            memzero(app->input_text, TEXT_BUFFER_SIZE);
+            // reset input state
+            app->input_state = FlipChessTextInputDefault;
+            handled = true;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdSettings);
+        } else if(app->input_state == FlipChessTextInputMnemonic) {
+            if(app->import_from_mnemonic == 1) {
+                strcpy(app->import_mnemonic_text, app->input_text);
+
+                int status = FlipChessStatusSuccess;
+                // Check if the mnemonic is valid
+                if(mnemonic_check(app->import_mnemonic_text) == 0)
+                    status = FlipChessStatusMnemonicCheckError; // 13 = mnemonic check error
+                // Save the mnemonic to persistent storage
+                else if(!flipchess_save_file_secure(app->import_mnemonic_text))
+                    status = FlipChessStatusSaveError; // 12 = save error
+
+                if(status == FlipChessStatusSuccess) {
+                    //notification_message(app->notification, &sequence_blink_cyan_100);
+                    flipchess_play_happy_bump(app);
+                } else {
+                    //notification_message(app->notification, &sequence_blink_red_100);
+                    flipchess_play_long_bump(app);
+                }
+
+                memzero(app->import_mnemonic_text, TEXT_BUFFER_SIZE);
+            }
+            // clear input text
+            memzero(app->input_text, TEXT_BUFFER_SIZE);
+            // reset input state
+            app->input_state = FlipChessTextInputDefault;
+            handled = true;
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
+        }
+    }
+
+    if(!handled) {
+        // clear input text
+        memzero(app->input_text, TEXT_BUFFER_SIZE);
+        // reset input state
+        app->input_state = FlipChessTextInputDefault;
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
+    }
+}
+
+FlipChess* flipchess_app_alloc() {
+    FlipChess* app = malloc(sizeof(FlipChess));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&flipchess_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, flipchess_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, flipchess_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, flipchess_custom_event_callback);
+    app->submenu = submenu_alloc();
+
+    // Settings
+    app->haptic = FlipChessHapticOn;
+    app->led = FlipChessLedOn;
+    app->bip39_strength = FlipChessStrength256; // 256 bits (24 words)
+    app->passphrase = FlipChessPassphraseOff;
+
+    // Main menu
+    app->bip44_coin = FlipChessCoinBTC0; // 0 (BTC)
+    app->overwrite_saved_seed = 0;
+    app->import_from_mnemonic = 0;
+
+    // Text input
+    app->input_state = FlipChessTextInputDefault;
+
+    view_dispatcher_add_view(
+        app->view_dispatcher, FlipChessViewIdMenu, submenu_get_view(app->submenu));
+    app->flipchess_startscreen = flipchess_startscreen_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        FlipChessViewIdStartscreen,
+        flipchess_startscreen_get_view(app->flipchess_startscreen));
+    app->flipchess_scene_1 = flipchess_scene_1_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, FlipChessViewIdScene1, flipchess_scene_1_get_view(app->flipchess_scene_1));
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        FlipChessViewIdSettings,
+        variable_item_list_get_view(app->variable_item_list));
+
+    app->text_input = text_input_alloc();
+    text_input_set_result_callback(
+        app->text_input,
+        text_input_callback,
+        (void*)app,
+        app->input_text,
+        TEXT_BUFFER_SIZE,
+        //clear default text
+        true);
+    text_input_set_header_text(app->text_input, "Input");
+    view_dispatcher_add_view(
+        app->view_dispatcher, FlipChessViewIdTextInput, text_input_get_view(app->text_input));
+
+    //End Scene Additions
+
+    return app;
+}
+
+void flipchess_app_free(FlipChess* app) {
+    furi_assert(app);
+
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    text_input_free(app->text_input);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdScene1);
+    view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdSettings);
+    view_dispatcher_remove_view(app->view_dispatcher, FlipChessViewIdTextInput);
+    submenu_free(app->submenu);
+
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+
+    app->gui = NULL;
+    app->notification = NULL;
+
+    //Remove whatever is left
+    memzero(app, sizeof(FlipChess));
+    free(app);
+}
+
+int32_t flipchess_app(void* p) {
+    UNUSED(p);
+    FlipChess* app = flipchess_app_alloc();
+
+    // Disabled because causes exit on custom firmwares such as RM
+    /*if(!furi_hal_region_is_provisioned()) {
+        flipchess_app_free(app);
+        return 1;
+    }*/
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    scene_manager_next_scene(
+        app->scene_manager, FlipChessSceneStartscreen); //Start with start screen
+    //scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu); //if you want to directly start with Menu
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    furi_hal_power_suppress_charge_exit();
+    flipchess_app_free(app);
+
+    return 0;
+}

+ 74 - 0
flipchess.h

@@ -0,0 +1,74 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/text_input.h>
+#include "scenes/flipchess_scene.h"
+#include "views/flipchess_startscreen.h"
+#include "views/flipchess_scene_1.h"
+
+#define FLIPCHESS_VERSION "v1.0.0"
+
+#define TEXT_BUFFER_SIZE 256
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    TextInput* text_input;
+    FlipChessStartscreen* flipchess_startscreen;
+    FlipChessScene1* flipchess_scene_1;
+    // Settings options
+    int haptic;
+    int white_mode;
+    int black_mode;
+    // Main menu options
+    int import_game;
+    // Text input
+    int input_state;
+    char import_game_text[TEXT_BUFFER_SIZE];
+    char input_text[TEXT_BUFFER_SIZE];
+} FlipChess;
+
+typedef enum {
+    FlipChessViewIdStartscreen,
+    FlipChessViewIdMenu,
+    FlipChessViewIdScene1,
+    FlipChessViewIdSettings,
+    FlipChessViewIdTextInput,
+} FlipChessViewId;
+
+typedef enum {
+    FlipChessHapticOff,
+    FlipChessHapticOn,
+} FlipChessHapticState;
+
+typedef enum {
+    FlipChessPlayerHuman,
+    FlipChessPlayerAI1,
+    FlipChessPlayerAI2,
+    FlipChessPlayerAI3,
+} FlipChessPlayerMode;
+
+typedef enum {
+    FlipChessTextInputDefault,
+    FlipChessTextInputGame
+} FlipChessTextInputState;
+
+typedef enum {
+    FlipChessStatusSuccess = 0,
+    FlipChessStatusReturn = 10,
+    FlipChessStatusLoadError = 11,
+    FlipChessStatusSaveError = 12,
+} FlipChessStatus;

+ 16 - 0
helpers/flipchess_custom_event.h

@@ -0,0 +1,16 @@
+#pragma once
+
+typedef enum {
+    FlipChessCustomEventStartscreenUp,
+    FlipChessCustomEventStartscreenDown,
+    FlipChessCustomEventStartscreenLeft,
+    FlipChessCustomEventStartscreenRight,
+    FlipChessCustomEventStartscreenOk,
+    FlipChessCustomEventStartscreenBack,
+    FlipChessCustomEventScene1Up,
+    FlipChessCustomEventScene1Down,
+    FlipChessCustomEventScene1Left,
+    FlipChessCustomEventScene1Right,
+    FlipChessCustomEventScene1Ok,
+    FlipChessCustomEventScene1Back,
+} FlipChessCustomEvent;

+ 35 - 0
helpers/flipchess_haptic.c

@@ -0,0 +1,35 @@
+#include "flipchess_haptic.h"
+#include "../flipchess.h"
+
+void flipchess_play_happy_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void flipchess_play_bad_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void flipchess_play_long_bump(void* context) {
+    FlipChess* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    for(int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 7 - 0
helpers/flipchess_haptic.h

@@ -0,0 +1,7 @@
+#include <notification/notification_messages.h>
+
+void flipchess_play_happy_bump(void* context);
+
+void flipchess_play_bad_bump(void* context);
+
+void flipchess_play_long_bump(void* context);

BIN
icons/Keychain_39x36.png


BIN
keycard_10px.png


+ 30 - 0
scenes/flipchess_scene.c

@@ -0,0 +1,30 @@
+#include "flipchess_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const flipchess_on_enter_handlers[])(void*) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const flipchess_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const flipchess_on_exit_handlers[])(void* context) = {
+#include "flipchess_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers flipchess_scene_handlers = {
+    .on_enter_handlers = flipchess_on_enter_handlers,
+    .on_event_handlers = flipchess_on_event_handlers,
+    .on_exit_handlers = flipchess_on_exit_handlers,
+    .scene_num = FlipChessSceneNum,
+};

+ 29 - 0
scenes/flipchess_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) FlipChessScene##id,
+typedef enum {
+#include "flipchess_scene_config.h"
+    FlipChessSceneNum,
+} FlipChessScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers flipchess_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "flipchess_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "flipchess_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "flipchess_scene_config.h"
+#undef ADD_SCENE

+ 4 - 0
scenes/flipchess_scene_config.h

@@ -0,0 +1,4 @@
+ADD_SCENE(flipchess, startscreen, Startscreen)
+ADD_SCENE(flipchess, menu, Menu)
+ADD_SCENE(flipchess, scene_1, Scene_1)
+ADD_SCENE(flipchess, settings, Settings)

+ 79 - 0
scenes/flipchess_scene_menu.c

@@ -0,0 +1,79 @@
+#include "../flipchess.h"
+#include "../helpers/flipchess_file.h"
+
+enum SubmenuIndex {
+    SubmenuIndexScene1New = 10,
+    SubmenuIndexScene1Import,
+    SubmenuIndexSettings,
+};
+
+void flipchess_scene_menu_submenu_callback(void* context, uint32_t index) {
+    FlipChess* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void flipchess_scene_menu_on_enter(void* context) {
+    FlipChess* app = context;
+
+    submenu_add_item(
+        app->submenu,
+        "New game",
+        SubmenuIndexScene1New,
+        flipchess_scene_menu_submenu_callback,
+        app);
+
+    submenu_add_item(
+        app->submenu,
+        "Import game",
+        SubmenuIndexScene1Import,
+        flipchess_scene_menu_submenu_callback,
+        app);
+
+    submenu_add_item(
+        app->submenu, 
+        "Settings", 
+        SubmenuIndexSettings, 
+        flipchess_scene_menu_submenu_callback, 
+        app);
+
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, FlipChessSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdMenu);
+}
+
+bool flipchess_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    FlipChess* app = context;
+    //UNUSED(app);
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexScene1New) {
+            app->import = 0;
+            scene_manager_set_scene_state(
+                app->scene_manager, FlipChessSceneMenu, SubmenuIndexScene1New);
+            scene_manager_next_scene(app->scene_manager, FlipChessSceneScene_1);
+            return true;
+        } else if(event.event == SubmenuIndexScene1Import) {
+            app->import = 1;
+            app->input_state = FlipChessTextInputMnemonic;
+            text_input_set_header_text(app->text_input, "Enter game phrase");
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdTextInput);
+            return true;
+        } else if(event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, FlipChessSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, FlipChessSceneSettings);
+            return true;
+        }
+    }
+    return false;
+}
+
+void flipchess_scene_menu_on_exit(void* context) {
+    FlipChess* app = context;
+    submenu_reset(app->submenu);
+}

+ 50 - 0
scenes/flipchess_scene_scene_1.c

@@ -0,0 +1,50 @@
+#include "../flipchess.h"
+#include "../helpers/flipchess_custom_event.h"
+#include "../views/flipchess_scene_1.h"
+
+void flipchess_scene_1_callback(FlipChessCustomEvent event, void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void flipchess_scene_scene_1_on_enter(void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    flipchess_scene_1_set_callback(app->flipchess_scene_1, flipchess_scene_1_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdScene1);
+}
+
+bool flipchess_scene_scene_1_on_event(void* context, SceneManagerEvent event) {
+    FlipChess* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case FlipChessCustomEventScene1Left:
+        case FlipChessCustomEventScene1Right:
+            break;
+        case FlipChessCustomEventScene1Up:
+        case FlipChessCustomEventScene1Down:
+            break;
+        case FlipChessCustomEventScene1Back:
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, FlipChessSceneMenu)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void flipchess_scene_scene_1_on_exit(void* context) {
+    FlipChess* app = context;
+    UNUSED(app);
+}

+ 97 - 0
scenes/flipchess_scene_settings.c

@@ -0,0 +1,97 @@
+#include "../flipchess.h"
+#include <lib/toolbox/value_index.h>
+
+#define TEXT_LABEL_ON "ON"
+#define TEXT_LABEL_OFF "OFF"
+
+const char* const haptic_text[2] = {
+    TEXT_LABEL_OFF,
+    TEXT_LABEL_ON,
+};
+const uint32_t haptic_value[2] = {
+    FlipChessHapticOff,
+    FlipChessHapticOn,
+};
+
+const char* const player_mode_text[4] = {
+    "Human",
+    "CPU 1",
+    "CPU 2",
+    "CPU 3",
+};
+const uint32_t player_mode_value[4] = {
+    FlipChessPlayerHuman,
+    FlipChessPlayerAI1,
+    FlipChessPlayerAI2,
+    FlipChessPlayerAI3,
+};
+
+static void flipchess_scene_settings_set_haptic(VariableItem* item) {
+    FlipChess* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, haptic_text[index]);
+    app->haptic = haptic_value[index];
+}
+
+static void flipchess_scene_settings_set_white_mode(VariableItem* item) {
+    FlipChess* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, player_mode_text[index]);
+    app->white_mode = player_mode_value[index];
+}
+
+static void flipchess_scene_settings_set_black_mode(VariableItem* item) {
+    FlipChess* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, player_mode_text[index]);
+    app->black_mode = player_mode_value[index];
+}
+
+void flipchess_scene_settings_submenu_callback(void* context, uint32_t index) {
+    FlipChess* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void flipchess_scene_settings_on_enter(void* context) {
+    FlipChess* app = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    // White mode
+    item = variable_item_list_add(
+        app->variable_item_list, "White:", 4, flipchess_scene_settings_set_white_mode, app);
+    value_index = value_index_uint32(app->white_mode, player_mode_value, 0);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, player_mode_text[value_index]);
+
+    // Black mode
+    item = variable_item_list_add(
+        app->variable_item_list, "Black:", 4, flipchess_scene_settings_set_black_mode, app);
+    value_index = value_index_uint32(app->black_mode, player_mode_value, 1);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, player_mode_text[value_index]);
+
+    // Vibro on/off
+    item = variable_item_list_add(
+        app->variable_item_list, "Vibro/Haptic:", 2, flipchess_scene_settings_set_haptic, app);
+    value_index = value_index_uint32(app->haptic, haptic_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdSettings);
+}
+
+bool flipchess_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    FlipChess* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+    }
+    return consumed;
+}
+
+void flipchess_scene_settings_on_exit(void* context) {
+    FlipChess* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+}

+ 55 - 0
scenes/flipchess_scene_startscreen.c

@@ -0,0 +1,55 @@
+#include "../flipchess.h"
+#include "../helpers/flipchess_custom_event.h"
+#include "../views/flipchess_startscreen.h"
+
+void flipchess_scene_startscreen_callback(FlipChessCustomEvent event, void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void flipchess_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    FlipChess* app = context;
+    flipchess_startscreen_set_callback(
+        app->flipchess_startscreen, flipchess_scene_startscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipChessViewIdStartscreen);
+}
+
+bool flipchess_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    FlipChess* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case FlipChessCustomEventStartscreenLeft:
+        case FlipChessCustomEventStartscreenRight:
+            break;
+        case FlipChessCustomEventStartscreenUp:
+        case FlipChessCustomEventStartscreenDown:
+            break;
+        case FlipChessCustomEventStartscreenOk:
+            scene_manager_next_scene(app->scene_manager, FlipChessSceneMenu);
+            consumed = true;
+            break;
+        case FlipChessCustomEventStartscreenBack:
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, FlipChessSceneStartscreen)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void flipchess_scene_startscreen_on_exit(void* context) {
+    FlipChess* app = context;
+    UNUSED(app);
+}

+ 738 - 0
views/flipchess_scene_1.c

@@ -0,0 +1,738 @@
+#include "../flipchess.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+//#include <dolphin/dolphin.h>
+#include <storage/storage.h>
+#include <string.h>
+#include "flipchess_icons.h"
+#include "../helpers/flipchess_haptic.h"
+
+#define MAX_TEXT_LEN 30 // 30 = max length of text
+#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator
+#define MAX_ADDR_BUF (42 + 1) // 42 = max length of address + null terminator
+#define NUM_ADDRS 6
+
+#define PAGE_LOADING 0
+#define PAGE_INFO 1
+#define PAGE_MNEMONIC 2
+#define PAGE_SEED 3
+#define PAGE_XPRV_ROOT 4
+#define PAGE_XPRV_ACCT 5
+#define PAGE_XPUB_ACCT 6
+#define PAGE_XPRV_EXTD 7
+#define PAGE_XPUB_EXTD 8
+#define PAGE_ADDR_BEGIN 9
+#define PAGE_ADDR_END (PAGE_ADDR_BEGIN + NUM_ADDRS - 1)
+
+#define TEXT_LOADING "Loading..."
+#define TEXT_NEW_WALLET "New wallet"
+#define TEXT_DEFAULT_COIN "Coin"
+#define TEXT_RECEIVE_ADDRESS "receive address:"
+#define TEXT_DEFAULT_DERIV "m/44'/X'/0'/0"
+const char* TEXT_INFO = "-Scroll pages with up/down-"
+                        "p1,2)   BIP39 Mnemonic/Seed"
+                        "p3)       BIP32 Root Key   "
+                        "p4,5)  Prv/Pub Account Keys"
+                        "p6,7)  Prv/Pub BIP32 Keys  "
+                        "p8+)    Receive Addresses  ";
+
+// #define TEXT_SAVE_QR "Save QR"
+#define TEXT_QRFILE_EXT ".qrcode" // 7 chars + 1 null
+
+// bip44_coin, xprv_version, xpub_version, addr_version, wif_version, addr_format
+const uint32_t COIN_INFO_ARRAY[3][6] = {
+    {COIN_BTC, 0x0488ade4, 0x0488b21e, 0x00, 0x80, FlipChessCoinBTC0},
+    {COIN_ETH, 0x0488ade4, 0x0488b21e, 0x00, 0x80, FlipChessCoinETH60},
+    {COIN_DOGE, 0x02fac398, 0x02facafd, 0x1e, 0x9e, FlipChessCoinBTC0}};
+
+// coin_name, derivation_path
+const char* COIN_TEXT_ARRAY[3][3] = {
+    {"BTC", "m/44'/0'/0'/0", "bitcoin:"},
+    {"ETH", "m/44'/60'/0'/0", "ethereum:"},
+    {"DOGE", "m/44'/3'/0'/0", "dogecoin:"}};
+
+struct FlipChessScene1 {
+    View* view;
+    FlipChessScene1Callback callback;
+    void* context;
+};
+typedef struct {
+    int page;
+    int strength;
+    uint32_t coin;
+    bool overwrite;
+    bool mnemonic_only;
+    CONFIDENTIAL const char* mnemonic;
+    CONFIDENTIAL uint8_t seed[64];
+    CONFIDENTIAL const HDNode* node;
+    CONFIDENTIAL const char* xprv_root;
+    CONFIDENTIAL const char* xprv_account;
+    CONFIDENTIAL const char* xpub_account;
+    CONFIDENTIAL const char* xprv_extended;
+    CONFIDENTIAL const char* xpub_extended;
+    char* recv_addresses[NUM_ADDRS];
+} FlipChessScene1Model;
+
+// Node for the receive address
+static CONFIDENTIAL HDNode* s_addr_node = NULL;
+// Generic display text
+static CONFIDENTIAL char* s_disp_text1 = NULL;
+static CONFIDENTIAL char* s_disp_text2 = NULL;
+static CONFIDENTIAL char* s_disp_text3 = NULL;
+static CONFIDENTIAL char* s_disp_text4 = NULL;
+static CONFIDENTIAL char* s_disp_text5 = NULL;
+static CONFIDENTIAL char* s_disp_text6 = NULL;
+// Derivation path text
+static const char* s_derivation_text = TEXT_DEFAULT_DERIV;
+// Warning text
+static bool s_warn_insecure = false;
+#define WARN_INSECURE_TEXT_1 "Recommendation:"
+#define WARN_INSECURE_TEXT_2 "Set BIP39 Passphrase"
+//static bool s_busy = false;
+
+void flipchess_scene_1_set_callback(
+    FlipChessScene1* instance,
+    FlipChessScene1Callback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+static void flipchess_scene_1_init_address(
+    char* addr_text,
+    const HDNode* node,
+    uint32_t coin_type,
+    uint32_t addr_index) {
+    //s_busy = true;
+
+    // buffer for address serialization
+    // subtract 2 for "0x", 1 for null terminator
+    const size_t buflen = MAX_ADDR_BUF - (2 + 1);
+    // subtract 2 for "0x"
+    char buf[MAX_ADDR_BUF - 2] = {0};
+
+    // Use static node for address generation
+    memcpy(s_addr_node, node, sizeof(HDNode));
+    memzero(addr_text, MAX_ADDR_BUF);
+
+    hdnode_private_ckd(s_addr_node, addr_index);
+    hdnode_fill_public_key(s_addr_node);
+
+    // coin info
+    // bip44_coin, xprv_version, xpub_version, addr_version, wif_version, addr_format
+    uint32_t coin_info[6] = {0};
+    for(size_t i = 0; i < 6; i++) {
+        coin_info[i] = COIN_INFO_ARRAY[coin_type][i];
+    }
+
+    if(coin_info[5] == FlipChessCoinBTC0) { // BTC / DOGE style address
+        // BTC / DOGE style address
+        ecdsa_get_address(
+            s_addr_node->public_key, coin_info[3], HASHER_SHA2_RIPEMD, HASHER_SHA2D, buf, buflen);
+        strcpy(addr_text, buf);
+
+        //ecdsa_get_wif(addr_node->private_key, WIF_VERSION, HASHER_SHA2D, buf, buflen);
+
+    } else if(coin_info[5] == FlipChessCoinETH60) { // ETH
+        // ETH style address
+        hdnode_get_ethereum_pubkeyhash(s_addr_node, (uint8_t*)buf);
+        addr_text[0] = '0';
+        addr_text[1] = 'x';
+        // Convert the hash to a hex string
+        flipchess_btox((uint8_t*)buf, 20, addr_text + 2);
+    }
+
+    // Clear the address node
+    memzero(s_addr_node, sizeof(HDNode));
+
+    //s_busy = false;
+}
+
+static void
+    flipchess_scene_1_draw_generic(const char* text, const size_t line_len, const bool chunk) {
+    // Split the text into parts
+    size_t len = line_len;
+    if(len > MAX_TEXT_LEN) {
+        len = MAX_TEXT_LEN;
+    }
+    for(size_t si = 1; si <= 6; si++) {
+        char* ptr = NULL;
+
+        if(si == 1)
+            ptr = s_disp_text1;
+        else if(si == 2)
+            ptr = s_disp_text2;
+        else if(si == 3)
+            ptr = s_disp_text3;
+        else if(si == 4)
+            ptr = s_disp_text4;
+        else if(si == 5)
+            ptr = s_disp_text5;
+        else if(si == 6)
+            ptr = s_disp_text6;
+
+        memzero(ptr, MAX_TEXT_BUF);
+        strncpy(ptr, text + ((si - 1) * len), len);
+        // add a space every 4 characters and shift the text
+        if(len < 23 && chunk) {
+            for(size_t i = 0; i < strlen(ptr); i++) {
+                if(i % 5 == 0) {
+                    for(size_t j = strlen(ptr); j > i; j--) {
+                        ptr[j] = ptr[j - 1];
+                    }
+                    ptr[i] = ' ';
+                }
+            }
+        }
+    }
+}
+
+static void flipchess_scene_1_draw_mnemonic(const char* mnemonic) {
+    // Delineate sections of the mnemonic every 4 words
+    const size_t mnemonic_working_len = strlen(mnemonic) + 1;
+    char* mnemonic_working = malloc(mnemonic_working_len);
+    strcpy(mnemonic_working, mnemonic);
+    int word = 0;
+    for(size_t i = 0; i < strlen(mnemonic_working); i++) {
+        if(mnemonic_working[i] == ' ') {
+            word++;
+            if(word % 4 == 0) {
+                mnemonic_working[i] = ',';
+            }
+        }
+    }
+
+    // Split the mnemonic into parts
+    char* mnemonic_part = flipchess_strtok(mnemonic_working, ",");
+    int mi = 0;
+    while(mnemonic_part != NULL) {
+        char* ptr = NULL;
+        mi++;
+
+        if(mi == 1)
+            ptr = s_disp_text1;
+        else if(mi == 2)
+            ptr = s_disp_text2;
+        else if(mi == 3)
+            ptr = s_disp_text3;
+        else if(mi == 4)
+            ptr = s_disp_text4;
+        else if(mi == 5)
+            ptr = s_disp_text5;
+        else if(mi == 6)
+            ptr = s_disp_text6;
+
+        memzero(ptr, MAX_TEXT_BUF);
+        if(strlen(mnemonic_part) > MAX_TEXT_LEN) {
+            strncpy(ptr, mnemonic_part, MAX_TEXT_LEN);
+        } else {
+            strncpy(ptr, mnemonic_part, strlen(mnemonic_part));
+        }
+
+        mnemonic_part = flipchess_strtok(NULL, ",");
+    }
+
+    // Free the working mnemonic memory
+    memzero(mnemonic_working, mnemonic_working_len);
+    free(mnemonic_working);
+}
+
+static void flipchess_scene_1_draw_seed(FlipChessScene1Model* const model) {
+    const size_t seed_working_len = 64 * 2 + 1;
+    char* seed_working = malloc(seed_working_len);
+    // Convert the seed to a hex string
+    flipchess_btox(model->seed, 64, seed_working);
+
+    flipchess_scene_1_draw_generic(seed_working, 22, false);
+
+    // Free the working seed memory
+    memzero(seed_working, seed_working_len);
+    free(seed_working);
+}
+
+static void flipchess_scene_1_clear_text() {
+    memzero((void*)s_disp_text1, MAX_TEXT_BUF);
+    memzero((void*)s_disp_text2, MAX_TEXT_BUF);
+    memzero((void*)s_disp_text3, MAX_TEXT_BUF);
+    memzero((void*)s_disp_text4, MAX_TEXT_BUF);
+    memzero((void*)s_disp_text5, MAX_TEXT_BUF);
+    memzero((void*)s_disp_text6, MAX_TEXT_BUF);
+}
+
+void flipchess_scene_1_draw(Canvas* canvas, FlipChessScene1Model* model) {
+    //UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    flipchess_scene_1_clear_text();
+    if(model->page == PAGE_INFO) {
+        flipchess_scene_1_draw_generic(TEXT_INFO, 27, false);
+    } else if(model->page == PAGE_MNEMONIC) {
+        flipchess_scene_1_draw_mnemonic(model->mnemonic);
+    } else if(model->page == PAGE_SEED) {
+        flipchess_scene_1_draw_seed(model);
+    } else if(model->page == PAGE_XPRV_ROOT) {
+        flipchess_scene_1_draw_generic(model->xprv_root, 20, false);
+    } else if(model->page == PAGE_XPRV_ACCT) {
+        flipchess_scene_1_draw_generic(model->xprv_account, 20, false);
+    } else if(model->page == PAGE_XPUB_ACCT) {
+        flipchess_scene_1_draw_generic(model->xpub_account, 20, false);
+    } else if(model->page == PAGE_XPRV_EXTD) {
+        flipchess_scene_1_draw_generic(model->xprv_extended, 20, false);
+    } else if(model->page == PAGE_XPUB_EXTD) {
+        flipchess_scene_1_draw_generic(model->xpub_extended, 20, false);
+    } else if(model->page >= PAGE_ADDR_BEGIN && model->page <= PAGE_ADDR_END) {
+        size_t line_len = 12;
+        if(model->coin == FlipChessCoinETH60) {
+            line_len = 14;
+        }
+        flipchess_scene_1_draw_generic(
+            model->recv_addresses[model->page - PAGE_ADDR_BEGIN], line_len, true);
+    }
+
+    if(model->page == PAGE_LOADING) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, 2, 10, TEXT_LOADING);
+        canvas_draw_str(canvas, 7, 30, s_derivation_text);
+        canvas_draw_icon(canvas, 86, 22, &I_Keychain_39x36);
+        if (s_warn_insecure) {
+            canvas_set_font(canvas, FontSecondary);
+            canvas_draw_str(canvas, 2, 50, WARN_INSECURE_TEXT_1);
+            canvas_draw_str(canvas, 2, 60, WARN_INSECURE_TEXT_2);
+        }
+    } else if(model->page >= PAGE_ADDR_BEGIN && model->page <= PAGE_ADDR_END) {
+        // draw address header
+        canvas_set_font(canvas, FontSecondary);
+        // coin_name, derivation_path
+        const char* receive_text = COIN_TEXT_ARRAY[model->coin][0];
+        if(receive_text == NULL) {
+            receive_text = TEXT_DEFAULT_COIN;
+        }
+        const size_t receive_len = strlen(receive_text) * 7;
+        canvas_draw_str_aligned(canvas, 2, 2, AlignLeft, AlignTop, receive_text);
+        canvas_draw_str_aligned(
+            canvas, receive_len + 1, 2, AlignLeft, AlignTop, TEXT_RECEIVE_ADDRESS);
+
+        // draw address number
+        const unsigned char addr_num[1] = {(unsigned char)(model->page - PAGE_ADDR_BEGIN)};
+        char addr_num_text[3] = {0};
+        flipchess_btox(addr_num, 1, addr_num_text);
+        addr_num_text[0] = '/';
+        canvas_draw_str_aligned(canvas, 125, 2, AlignRight, AlignTop, addr_num_text);
+
+        // draw QR code file path
+        char addr_name_text[14] = {0};
+        strcpy(addr_name_text, COIN_TEXT_ARRAY[model->coin][0]);
+        flipchess_btox(addr_num, 1, addr_name_text + strlen(addr_name_text));
+        strcpy(addr_name_text + strlen(addr_name_text), TEXT_QRFILE_EXT);
+        //elements_button_right(canvas, addr_name_text);
+        canvas_draw_str_aligned(canvas, 125, 53, AlignRight, AlignTop, addr_name_text);
+
+        // draw address
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, 7, 22, s_disp_text1);
+        canvas_draw_str(canvas, 7, 34, s_disp_text2);
+        canvas_draw_str(canvas, 7, 46, s_disp_text3);
+        canvas_draw_str(canvas, 7, 58, s_disp_text4);
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 1, 2, AlignLeft, AlignTop, s_disp_text1);
+        canvas_draw_str_aligned(canvas, 1, 12, AlignLeft, AlignTop, s_disp_text2);
+        canvas_draw_str_aligned(canvas, 1, 22, AlignLeft, AlignTop, s_disp_text3);
+        canvas_draw_str_aligned(canvas, 1, 32, AlignLeft, AlignTop, s_disp_text4);
+        canvas_draw_str_aligned(canvas, 1, 42, AlignLeft, AlignTop, s_disp_text5);
+        canvas_draw_str_aligned(canvas, 1, 52, AlignLeft, AlignTop, s_disp_text6);
+    }
+}
+
+static int flipchess_scene_1_model_init(
+    FlipChessScene1Model* const model,
+    const int strength,
+    const uint32_t coin,
+    const bool overwrite,
+    const char* passphrase_text) {
+    model->page = PAGE_LOADING;
+    model->mnemonic_only = false;
+    model->strength = strength;
+    model->coin = coin;
+    model->overwrite = overwrite;
+
+    // Allocate memory for mnemonic
+    char* mnemonic = malloc(TEXT_BUFFER_SIZE);
+    memzero(mnemonic, TEXT_BUFFER_SIZE);
+
+    // Check if the mnemonic key & data is already saved in persistent storage, or overwrite is true
+    if(overwrite || (!flipchess_has_file(FlipChessFileKey, NULL, false) &&
+                     !flipchess_has_file(FlipChessFileDat, NULL, false))) {
+        // Set mnemonic only mode
+        model->mnemonic_only = true;
+        // Generate a random mnemonic using trezor-crypto
+        const char* mnemonic_gen = mnemonic_generate(strength);
+        // Check if the mnemonic is valid
+        if(mnemonic_check(mnemonic_gen) == 0)
+            return FlipChessStatusMnemonicCheckError; // 13 = mnemonic check error
+        // Save the mnemonic to persistent storage
+        else if(!flipchess_save_file_secure(mnemonic_gen))
+            return FlipChessStatusSaveError; // 12 = save error
+        // Clear the generated mnemonic from memory
+        mnemonic_clear();
+    }
+
+    // Load the mnemonic from persistent storage
+    if(!flipchess_load_file_secure(mnemonic)) {
+        // Set mnemonic only mode for this error for memory cleanup purposes
+        model->mnemonic_only = true;
+        return FlipChessStatusLoadError; // 11 = load error
+    }
+    model->mnemonic = mnemonic;
+    // Check if the mnemonic is valid
+    if(mnemonic_check(model->mnemonic) == 0) {
+        // Set mnemonic only mode for this error for memory cleanup purposes
+        model->mnemonic_only = true;
+        return FlipChessStatusMnemonicCheckError; // 13 = mnemonic check error
+    }
+
+    // test return values
+    //model->mnemonic_only = true;
+    //return FlipChessStatusMnemonicCheckError; // 13 = mnemonic check error
+
+    // if we are only generating the mnemonic, return
+    if(model->mnemonic_only) {
+        return FlipChessStatusReturn; // 10 = mnemonic only, return from parent
+    }
+
+    // Generate a BIP39 seed from the mnemonic
+    mnemonic_to_seed(model->mnemonic, passphrase_text, model->seed, 0);
+
+    // Generate a BIP32 root HD node from the mnemonic
+    HDNode* root = malloc(sizeof(HDNode));
+    hdnode_from_seed(model->seed, 64, SECP256K1_NAME, root);
+
+    // buffer for key serialization
+    const size_t buflen = 128;
+    char buf[128 + 1] = {0};
+
+    // coin info
+    // bip44_coin, xprv_version, xpub_version, addr_version, wif_version, addr_format
+    uint32_t coin_info[6] = {0};
+    for(size_t i = 0; i < 6; i++) {
+        coin_info[i] = COIN_INFO_ARRAY[coin][i];
+    }
+
+    // root
+    uint32_t fingerprint = 0;
+    hdnode_serialize_private(root, fingerprint, coin_info[1], buf, buflen);
+    char* xprv_root = malloc(buflen + 1);
+    strncpy(xprv_root, buf, buflen);
+    model->xprv_root = xprv_root;
+
+    HDNode* node = root;
+
+    // purpose m/44'
+    fingerprint = hdnode_fingerprint(node);
+    hdnode_private_ckd_prime(node, DERIV_PURPOSE); // purpose
+
+    // coin m/44'/0' or m/44'/60'
+    fingerprint = hdnode_fingerprint(node);
+    hdnode_private_ckd_prime(node, coin_info[0]); // coin
+
+    // account m/44'/0'/0' or m/44'/60'/0'
+    fingerprint = hdnode_fingerprint(node);
+    hdnode_private_ckd_prime(node, DERIV_ACCOUNT); // account
+
+    hdnode_serialize_private(node, fingerprint, coin_info[1], buf, buflen);
+    char* xprv_acc = malloc(buflen + 1);
+    strncpy(xprv_acc, buf, buflen);
+    model->xprv_account = xprv_acc;
+
+    hdnode_serialize_public(node, fingerprint, coin_info[2], buf, buflen);
+    char* xpub_acc = malloc(buflen + 1);
+    strncpy(xpub_acc, buf, buflen);
+    model->xpub_account = xpub_acc;
+
+    // external/internal (change) m/44'/0'/0'/0 or m/44'/60'/0'/0
+    fingerprint = hdnode_fingerprint(node);
+    hdnode_private_ckd(node, DERIV_CHANGE); // external/internal (change)
+
+    hdnode_serialize_private(node, fingerprint, coin_info[1], buf, buflen);
+    char* xprv_ext = malloc(buflen + 1);
+    strncpy(xprv_ext, buf, buflen);
+    model->xprv_extended = xprv_ext;
+
+    hdnode_serialize_public(node, fingerprint, coin_info[2], buf, buflen);
+    char* xpub_ext = malloc(buflen + 1);
+    strncpy(xpub_ext, buf, buflen);
+    model->xpub_extended = xpub_ext;
+
+    model->node = node;
+
+    // Initialize addresses
+    for(uint8_t a = 0; a < NUM_ADDRS; a++) {
+        model->recv_addresses[a] = malloc(MAX_ADDR_BUF);
+        memzero(model->recv_addresses[a], MAX_ADDR_BUF);
+        flipchess_scene_1_init_address(model->recv_addresses[a], node, coin, a);
+
+        // Save QR code file
+        memzero(buf, buflen);
+        strcpy(buf, COIN_TEXT_ARRAY[coin][0]);
+        const unsigned char addr_num[1] = {a};
+        flipchess_btox(addr_num, 1, buf + strlen(buf));
+        strcpy(buf + strlen(buf), TEXT_QRFILE_EXT);
+        flipchess_save_qrfile(COIN_TEXT_ARRAY[coin][2], model->recv_addresses[a], buf);
+        memzero(buf, buflen);
+    }
+
+    model->page = PAGE_INFO;
+
+#if USE_BIP39_CACHE
+    // Clear the BIP39 cache
+    bip39_cache_clear();
+#endif
+
+    // 0 = success
+    return FlipChessStatusSuccess;
+}
+
+bool flipchess_scene_1_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FlipChessScene1* instance = context;
+
+    // Ignore input if busy
+    // if(s_busy) {
+    //     return false;
+    // }
+
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                FlipChessScene1Model * model,
+                {
+                    UNUSED(model);
+                    instance->callback(FlipChessCustomEventScene1Back, instance->context);
+                },
+                true);
+            break;
+        case InputKeyRight:
+        case InputKeyDown:
+            with_view_model(
+                instance->view,
+                FlipChessScene1Model * model,
+                {
+                    //UNUSED(model);
+                    int page = (model->page + 1) % (PAGE_ADDR_END + 1);
+                    if(page == 0) {
+                        page = PAGE_INFO;
+                    }
+                    model->page = page;
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyUp:
+            with_view_model(
+                instance->view,
+                FlipChessScene1Model * model,
+                {
+                    //UNUSED(model);
+                    int page = (model->page - 1) % (PAGE_ADDR_END + 1);
+                    if(page == 0) {
+                        page = PAGE_ADDR_END;
+                    }
+                    model->page = page;
+                },
+                true);
+            break;
+        // case InputKeyRight:
+        case InputKeyOk:
+            // with_view_model(
+            //     instance->view,
+            //     FlipChessScene1Model * model,
+            //     {
+            //         if(model->page >= PAGE_ADDR_BEGIN && model->page <= PAGE_ADDR_END) {
+
+            //         }
+            //     },
+            //     true);
+            // break;
+        // case InputKeyLeft:
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void flipchess_scene_1_exit(void* context) {
+    furi_assert(context);
+    FlipChessScene1* instance = (FlipChessScene1*)context;
+
+    with_view_model(
+        instance->view,
+        FlipChessScene1Model * model,
+        {
+            model->page = PAGE_LOADING;
+            model->strength = FlipChessStrength256;
+            model->coin = FlipChessCoinBTC0;
+            memzero(model->seed, 64);
+            // if mnemonic_only is true, then we don't need to free the data here
+            if(!model->mnemonic_only) {
+                memzero((void*)model->mnemonic, strlen(model->mnemonic));
+                free((void*)model->mnemonic);
+                memzero((void*)model->node, sizeof(HDNode));
+                free((void*)model->node);
+                memzero((void*)model->xprv_root, strlen(model->xprv_root));
+                memzero((void*)model->xprv_account, strlen(model->xprv_account));
+                memzero((void*)model->xpub_account, strlen(model->xpub_account));
+                memzero((void*)model->xprv_extended, strlen(model->xprv_extended));
+                memzero((void*)model->xpub_extended, strlen(model->xpub_extended));
+                free((void*)model->xprv_root);
+                free((void*)model->xprv_account);
+                free((void*)model->xpub_account);
+                free((void*)model->xprv_extended);
+                free((void*)model->xpub_extended);
+                for(int a = 0; a < NUM_ADDRS; a++) {
+                    memzero((void*)model->recv_addresses[a], MAX_ADDR_BUF);
+                    free((void*)model->recv_addresses[a]);
+                }
+            }
+        },
+        true);
+
+    flipchess_scene_1_clear_text();
+}
+
+void flipchess_scene_1_enter(void* context) {
+    furi_assert(context);
+    FlipChessScene1* instance = (FlipChessScene1*)context;
+
+    FlipChess* app = instance->context;
+
+    // BIP39 Strength setting
+    int strength = 256; // FlipChessStrength256 // 24 words (256 bit)
+    if(app->bip39_strength == FlipChessStrength128) {
+        strength = 128; // 12 words (128 bit)
+    } else if(app->bip39_strength == FlipChessStrength192) {
+        strength = 192; // 18 words (192 bit)
+    }
+
+    // BIP39 Passphrase setting
+    const char* passphrase_text = "";
+    if(app->passphrase == FlipChessPassphraseOn && strlen(app->passphrase_text) > 0) {
+        passphrase_text = app->passphrase_text;
+        s_warn_insecure = false;
+    } else {
+        s_warn_insecure = true;
+    }
+
+    // BIP44 Coin setting
+    const uint32_t coin = app->bip44_coin;
+    // coin_name, derivation_path
+    s_derivation_text = COIN_TEXT_ARRAY[coin][1];
+
+    // Overwrite the saved seed with a new one setting
+    bool overwrite = app->overwrite_saved_seed != 0;
+    if(overwrite) {
+        s_derivation_text = TEXT_NEW_WALLET;
+    }
+
+    flipchess_play_happy_bump(app);
+    //notification_message(app->notification, &sequence_blink_cyan_100);
+    flipchess_led_set_rgb(app, 255, 0, 0);
+
+    with_view_model(
+        instance->view,
+        FlipChessScene1Model * model,
+        {
+            // s_busy = true;
+
+            const int status =
+                flipchess_scene_1_model_init(model, strength, coin, overwrite, passphrase_text);
+
+            // nonzero status, free the mnemonic
+            if(status != FlipChessStatusSuccess) {
+                memzero((void*)model->mnemonic, strlen(model->mnemonic));
+                free((void*)model->mnemonic);
+            }
+
+            // if error, set the error message
+            if(status == FlipChessStatusSaveError) {
+                model->mnemonic = "ERROR:,Save error";
+                model->page = PAGE_MNEMONIC;
+                flipchess_play_long_bump(app);
+            } else if(status == FlipChessStatusLoadError) {
+                model->mnemonic = "ERROR:,Load error";
+                model->page = PAGE_MNEMONIC;
+                flipchess_play_long_bump(app);
+            } else if(status == FlipChessStatusMnemonicCheckError) {
+                model->mnemonic = "ERROR:,Mnemonic check error";
+                model->page = PAGE_MNEMONIC;
+                flipchess_play_long_bump(app);
+            }
+
+            // s_busy = false;
+
+            // if overwrite is set and mnemonic generated, return from scene immediately
+            if(status == FlipChessStatusReturn) {
+                instance->callback(FlipChessCustomEventScene1Back, instance->context);
+            }
+        },
+        true);
+}
+
+FlipChessScene1* flipchess_scene_1_alloc() {
+    FlipChessScene1* instance = malloc(sizeof(FlipChessScene1));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessScene1Model));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_scene_1_draw);
+    view_set_input_callback(instance->view, flipchess_scene_1_input);
+    view_set_enter_callback(instance->view, flipchess_scene_1_enter);
+    view_set_exit_callback(instance->view, flipchess_scene_1_exit);
+
+    // allocate the address node
+    s_addr_node = (HDNode*)malloc(sizeof(HDNode));
+
+    // allocate the display text
+    s_disp_text1 = (char*)malloc(MAX_TEXT_BUF);
+    s_disp_text2 = (char*)malloc(MAX_TEXT_BUF);
+    s_disp_text3 = (char*)malloc(MAX_TEXT_BUF);
+    s_disp_text4 = (char*)malloc(MAX_TEXT_BUF);
+    s_disp_text5 = (char*)malloc(MAX_TEXT_BUF);
+    s_disp_text6 = (char*)malloc(MAX_TEXT_BUF);
+
+    return instance;
+}
+
+void flipchess_scene_1_free(FlipChessScene1* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, FlipChessScene1Model * model, { UNUSED(model); }, true);
+
+    // free the address node
+    memzero(s_addr_node, sizeof(HDNode));
+    free(s_addr_node);
+
+    // free the display text
+    flipchess_scene_1_clear_text();
+    free(s_disp_text1);
+    free(s_disp_text2);
+    free(s_disp_text3);
+    free(s_disp_text4);
+    free(s_disp_text5);
+    free(s_disp_text6);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* flipchess_scene_1_get_view(FlipChessScene1* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
views/flipchess_scene_1.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/flipchess_custom_event.h"
+
+typedef struct FlipChessScene1 FlipChessScene1;
+
+typedef void (*FlipChessScene1Callback)(FlipChessCustomEvent event, void* context);
+
+void flipchess_scene_1_set_callback(
+    FlipChessScene1* flipchess_scene_1,
+    FlipChessScene1Callback callback,
+    void* context);
+
+View* flipchess_scene_1_get_view(FlipChessScene1* flipchess_static);
+
+FlipChessScene1* flipchess_scene_1_alloc();
+
+void flipchess_scene_1_free(FlipChessScene1* flipchess_static);

+ 130 - 0
views/flipchess_startscreen.c

@@ -0,0 +1,130 @@
+#include "../flipchess.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include "flipchess_icons.h"
+
+struct FlipChessStartscreen {
+    View* view;
+    FlipChessStartscreenCallback callback;
+    void* context;
+};
+
+typedef struct {
+    int some_value;
+} FlipChessStartscreenModel;
+
+void flipchess_startscreen_set_callback(
+    FlipChessStartscreen* instance,
+    FlipChessStartscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void flipchess_startscreen_draw(Canvas* canvas, FlipChessStartscreenModel* model) {
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_draw_icon(canvas, 1, 33, &I_Auth_62x31);
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 18, 11, "Chess");
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 23, 22, "Chess for Flipper");
+    canvas_draw_str(canvas, 99, 34, FLIPCHESS_VERSION);
+
+    elements_button_right(canvas, "Start");
+}
+
+static void flipchess_startscreen_model_init(FlipChessStartscreenModel* const model) {
+    model->some_value = 1;
+}
+
+bool flipchess_startscreen_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FlipChessStartscreen* instance = context;
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                FlipChessStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(FlipChessCustomEventStartscreenBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                FlipChessStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(FlipChessCustomEventStartscreenOk, instance->context);
+                },
+                true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void flipchess_startscreen_exit(void* context) {
+    furi_assert(context);
+}
+
+void flipchess_startscreen_enter(void* context) {
+    furi_assert(context);
+    FlipChessStartscreen* instance = (FlipChessStartscreen*)context;
+    with_view_model(
+        instance->view,
+        FlipChessStartscreenModel * model,
+        { flipchess_startscreen_model_init(model); },
+        true);
+}
+
+FlipChessStartscreen* flipchess_startscreen_alloc() {
+    FlipChessStartscreen* instance = malloc(sizeof(FlipChessStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessStartscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_startscreen_draw);
+    view_set_input_callback(instance->view, flipchess_startscreen_input);
+    //view_set_enter_callback(instance->view, flipchess_startscreen_enter);
+    //view_set_exit_callback(instance->view, flipchess_startscreen_exit);
+
+    with_view_model(
+        instance->view,
+        FlipChessStartscreenModel * model,
+        { flipchess_startscreen_model_init(model); },
+        true);
+
+    return instance;
+}
+
+void flipchess_startscreen_free(FlipChessStartscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, FlipChessStartscreenModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* flipchess_startscreen_get_view(FlipChessStartscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
views/flipchess_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/flipchess_custom_event.h"
+
+typedef struct FlipChessStartscreen FlipChessStartscreen;
+
+typedef void (*FlipChessStartscreenCallback)(FlipChessCustomEvent event, void* context);
+
+void flipchess_startscreen_set_callback(
+    FlipChessStartscreen* flipchess_startscreen,
+    FlipChessStartscreenCallback callback,
+    void* context);
+
+View* flipchess_startscreen_get_view(FlipChessStartscreen* flipchess_static);
+
+FlipChessStartscreen* flipchess_startscreen_alloc();
+
+void flipchess_startscreen_free(FlipChessStartscreen* flipchess_static);