Преглед изворни кода

Add flame_rng from https://github.com/OrionW06/Flame-RNG

git-subtree-dir: flame_rng
git-subtree-mainline: efa122344322d3a873ba088ed12a96095e0b484d
git-subtree-split: f6507c15329555a291f19c1a0ffd92ee0e9c77e6
Willy-JL пре 8 месеци
родитељ
комит
8291f15f7f

+ 41 - 0
flame_rng/.github/workflows/build.yml

@@ -0,0 +1,41 @@
+name: "FAP: Build for multiple SDK sources"
+# This will build your app for dev and release channels on GitHub. 
+# It will also build your app every day to make sure it's up to date with the latest SDK changes.
+# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information
+
+on:
+  push:
+    ## put your main branch name under "branches"
+    #branches: 
+    #  - master 
+  pull_request:
+  schedule: 
+    # do a build every day
+    - cron: "1 1 * * *"
+
+jobs:
+  ufbt-build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        include:
+          - name: dev channel
+            sdk-channel: dev
+          - name: release channel
+            sdk-channel: release
+          # You can add unofficial channels here. See ufbt action docs for more info.
+    name: 'ufbt: Build for ${{ matrix.name }}'
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+      - name: Build with ufbt
+        uses: flipperdevices/flipperzero-ufbt-action@v0.1
+        id: build-app
+        with:
+          sdk-channel: ${{ matrix.sdk-channel }}
+      - name: Upload app artifacts
+        uses: actions/upload-artifact@v4
+        with:
+          # See ufbt action docs for other output variables
+          name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }}
+          path: ${{ steps.build-app.outputs.fap-artifacts }}

+ 7 - 0
flame_rng/.gitignore

@@ -0,0 +1,7 @@
+dist/*
+.vscode
+.clang-format
+.clangd
+.editorconfig
+.env
+.ufbt

+ 1 - 0
flame_rng/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/OrionW06/Flame-RNG master /

+ 21 - 0
flame_rng/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Comrade Orion
+
+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.

+ 13 - 0
flame_rng/README.md

@@ -0,0 +1,13 @@
+
+# Flame-RNG
+
+Inspired by [this repo](https://github.com/dipdowel/flipper-fire-rng) I have implemented a similar functionality entirely in the flipper zero
+
+
+It uses the IR sensor on the Flipper Zero to gather entropy from IR sources (such as a lighter) to seed a random number generator. The idea is that fire and other similar IR sources are not predictable sources of data, and thus it can be a reliable source of entropy. Obviously the flipper doesn't gather enough data (unless you *really* like flicking a lighter near your nearly $200 toy) to get any sort of decent entropy, this is more of a PoC.
+
+  
+## Disclaimer
+I am NOT responsible for any damage to the flipper or yourself! Be safe with fire, and be safe with your other entropic sources!
+
+I am also NOT responsible if you generate insecure keys using this as a source of entropy!

+ 16 - 0
flame_rng/application.fam

@@ -0,0 +1,16 @@
+# For details & more options, see documentation/AppManifests.md in firmware repo
+
+App(
+    appid="flame_rng",  # Must be unique
+    name="Flipper Flame RNG",  # Displayed in menus
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="flame_rng",
+    stack_size=2 * 1024,
+    fap_category="Infrared",
+    fap_version="0.1",
+    fap_icon="flame_rng.png",  # 10x10 1-bit PNG
+    fap_description="An RNG intended for use with Flames and other IR sources.",
+    fap_author="OrionW06",
+    fap_weburl="https://github.com/OrionW06/Flame-RNG",
+    fap_icon_assets="images",  # Image assets to compile for this application
+)

+ 8 - 0
flame_rng/docs/changelog.md

@@ -0,0 +1,8 @@
+## v0.2:
+added changelog
+added screenshots, manifest
+moved some directories around to comply with FlipperLabs
+Updated README
+
+## v0.1:
+added app, images, and README.md

BIN
flame_rng/flame_rng.png


+ 0 - 0
flame_rng/images/.gitkeep


+ 222 - 0
flame_rng/main.c

@@ -0,0 +1,222 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <infrared.h>
+#include <infrared_worker.h>
+#include <furi_hal_infrared.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <storage/storage.h>
+#include <stdlib.h>
+#include <time.h>
+#include <toolbox/stream/file_stream.h>
+
+#define TAG             "flame_rng"
+#define HISTORY_SIZE    5
+#define MAX_PATH_LENGTH 256
+
+typedef struct {
+    uint32_t rng_value;
+    uint32_t seed;
+    uint32_t history[HISTORY_SIZE];
+    uint8_t history_index;
+    bool new_value;
+    FuriMutex* mutex;
+    uint32_t message_timestamp;
+} FlameRngState;
+
+static uint32_t generate_rng_from_ir(InfraredWorkerSignal* signal) {
+    const uint32_t* timings;
+    size_t timings_cnt;
+    infrared_worker_get_raw_signal(signal, &timings, &timings_cnt);
+
+    uint32_t seed = 0;
+    for(size_t i = 0; i < timings_cnt; i++) {
+        seed ^= timings[i] ^ (seed << 5) ^ (seed >> 3); // mix bits
+    }
+    return seed;
+}
+
+static void update_history(FlameRngState* state, uint32_t value) {
+    state->history[state->history_index] = value;
+    state->history_index = (state->history_index + 1) % HISTORY_SIZE;
+}
+
+static void ir_callback(void* ctx, InfraredWorkerSignal* signal) {
+    FlameRngState* state = ctx;
+    uint32_t seed = generate_rng_from_ir(signal);
+
+    furi_mutex_acquire(state->mutex, FuriWaitForever);
+
+    // Better entropy mixing pipeline
+    uint32_t rng_number = furi_hal_random_get();
+
+    // 1. Combine with seed using non-linear operation
+    rng_number += (seed * 0x9E3779B9); // Golden ratio constant for mixing
+
+    // 2. Improved bit diffusion (xorshift variant)
+    rng_number ^= rng_number >> 15;
+    rng_number *= 0x85EBCA77;
+    rng_number ^= rng_number >> 13;
+    rng_number *= 0xC2B2AE3D;
+    rng_number ^= rng_number >> 16;
+
+    // 3. Safer range reduction (better than simple modulo)
+    state->rng_value = (uint32_t)((rng_number & 0xFFFFFFFF) * 1000000ULL) % 1000000;
+    state->seed = seed;
+    update_history(state, state->rng_value);
+    state->new_value = true;
+    state->message_timestamp = furi_get_tick();
+    furi_mutex_release(state->mutex);
+
+    FURI_LOG_I(TAG, "Generated RNG: %lu (seed: %lu)", state->rng_value, state->seed);
+}
+
+static bool save_random_numbers(FlameRngState* state) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* file = NULL;
+    bool success = false;
+
+    // Create directory if it doesn't exist
+    if(!storage_simply_mkdir(storage, "/ext/random_gen")) {
+        FURI_LOG_E(TAG, "Failed to create directory");
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Generate random filename
+    char filename[MAX_PATH_LENGTH];
+    snprintf(filename, sizeof(filename), "/ext/random_gen/RANDOM-%u.rng", rand());
+
+    file = file_stream_alloc(storage);
+    if(!file_stream_open(file, filename, FSAM_WRITE, FSOM_CREATE_NEW)) {
+        FURI_LOG_E(TAG, "Failed to open file: %s", filename);
+        goto cleanup;
+    }
+
+    // Write all history numbers
+    for(uint8_t i = 0; i < HISTORY_SIZE; i++) {
+        uint8_t idx = (state->history_index + i) % HISTORY_SIZE;
+        if(stream_write_format(file, "%lu\n", state->history[idx]) == 0) {
+            FURI_LOG_E(TAG, "Failed to write to file");
+            goto cleanup;
+        }
+    }
+
+    success = true;
+    FURI_LOG_I(TAG, "Saved random numbers to %s", filename);
+
+cleanup:
+    if(file) {
+        file_stream_close(file);
+        stream_free(file);
+    }
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}
+
+static void render_callback(Canvas* canvas, void* ctx) {
+    FlameRngState* state = ctx;
+    furi_mutex_acquire(state->mutex, FuriWaitForever);
+
+    canvas_clear(canvas);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 2, 10, "Flame RNG");
+
+    // Main random number display (always updates)
+    canvas_set_font(canvas, FontBigNumbers);
+    char value_str[32];
+    snprintf(value_str, sizeof(value_str), "%06lu", state->rng_value);
+    canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, value_str);
+
+    // Status message (disappears after some time)
+    if(state->new_value) {
+        uint32_t current_time = furi_get_tick();
+        uint32_t elapsed_time = current_time - state->message_timestamp;
+
+        if(elapsed_time < 1000) {
+            canvas_set_font(canvas, FontSecondary);
+            canvas_draw_str(canvas, 2, 55, "New value! Press OK to save");
+        } else {
+            state->new_value = false; // Clear the message after delay
+        }
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 2, 55, "Waiting for IR signal...");
+    }
+
+    furi_mutex_release(state->mutex);
+}
+
+static void input_callback(InputEvent* input_event, void* ctx) {
+    FuriMessageQueue* event_queue = ctx;
+    furi_message_queue_put(event_queue, input_event, FuriWaitForever);
+}
+
+int32_t flame_rng(void* p) {
+    UNUSED(p);
+    FURI_LOG_I(TAG, "Starting Flame RNG");
+
+    // Initialize state
+    FlameRngState* state = malloc(sizeof(FlameRngState));
+    state->rng_value = 0;
+    state->seed = 0;
+    state->history_index = 0;
+    state->new_value = false;
+    memset(state->history, 0, sizeof(state->history));
+    state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+
+    // Set up message queue
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+    // Set up ViewPort
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, render_callback, state);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    // Register viewport in GUI
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    // Set up infrared
+    InfraredWorker* worker = infrared_worker_alloc();
+    infrared_worker_rx_enable_signal_decoding(worker, false);
+    infrared_worker_rx_set_received_signal_callback(worker, ir_callback, state);
+    infrared_worker_rx_start(worker);
+
+    // Main loop
+    InputEvent event;
+    bool running = true;
+    while(running) {
+        if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
+            if(event.type == InputTypePress) {
+                if(event.key == InputKeyBack) {
+                    running = false;
+                } else if(event.key == InputKeyOk) {
+                    furi_mutex_acquire(state->mutex, FuriWaitForever);
+                    bool saved = save_random_numbers(state);
+                    furi_mutex_release(state->mutex);
+
+                    if(saved) {
+                        // Show save confirmation
+                        view_port_update(view_port);
+                        furi_delay_ms(500);
+                    }
+                }
+            }
+        }
+        view_port_update(view_port);
+    }
+
+    // Cleanup
+    infrared_worker_rx_stop(worker);
+    infrared_worker_free(worker);
+    gui_remove_view_port(gui, view_port);
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(state->mutex);
+    free(state);
+    furi_record_close(RECORD_GUI);
+
+    FURI_LOG_I(TAG, "Stopping Flame RNG");
+    return 0;
+}

BIN
flame_rng/screenshots/AllZeros.png


BIN
flame_rng/screenshots/RandomNumber.png


BIN
flame_rng/screenshots/SaveMessage.png