moonbit-securegen

    Portable typed password, passphrase, and PIN generation for MoonBit

    credential
    password
    passphrase
    pin
    security
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    4 hours ago
    Downloads
    2

    #SecureGen for MoonBit

    English | 简体中文

    SecureGen is a reusable MoonBit credential-generation engine with typed policies, caller-injected entropy, portable application APIs, and allocation-controlled embedded APIs. This repository includes independent CLI, browser, external-module, and FoloToy firmware consumers.

    The firmware is a reference application of the MoonBit package, not the package boundary itself. Consumers can import zhangsan2000w-art/moonbit-securegen without ESP-IDF, FoloToy, LVGL, BLE, a filesystem, or network access.

    For a short evaluation path, see the reviewer guide, then run:

    moon test -p zhangsan2000w-art/moonbit-securegen \ --target wasm-gc --release --deny-warn moon run src/cmd/securegen --target js --release -- --profile strict --length 24

    #FoloToy reference application

    The firmware starts directly in the generator. It does not connect to a network, store password history, or redefine the system power button. A generated value leaves the device only after the user explicitly selects Send, through the paired encrypted BLE HID keyboard connection.

    The embedded application is an offline three-button password generator built for FoloToy AI Passport. It starts directly in the generator, does not connect to a network or store password history, and sends a generated value only after the user explicitly selects Send over an encrypted BLE HID connection.

    #Firmware features

    • Random: 6–30 printable ASCII characters, default length 10, letters always enabled, optional digits and symbols. Every enabled optional class is guaranteed to appear.
    • Memorable: 3–6 offline words, default 4, optional capitalization, complete or four-character abbreviated words, and -, ., or _ separators.
    • PIN: 4–12 decimal digits, default length 6.
    • Input: UP, DOWN, and OK only. OK enters or confirms editing, toggles Boolean values, or generates. Holding UP / DOWN continuously changes a numeric value while editing. Long-pressing OK cancels an edit; outside editing it switches the theme. Long-pressing DOWN outside editing opens Settings.
    • Feedback: MoonBit generates the success chime's frequencies, durations, envelope, and PCM samples; the C audio task only performs non-blocking playback. The chime can be disabled in Settings.
    • BLE keyboard: pair the device named FoloPassKey with no passkey, focus a field on the host, and select Send to type the current password as a US-layout keyboard. The bonded link is encrypted, but Just Works pairing does not provide MITM authentication.
    • Display: switchable 240×320 cyberpunk and blue-sky themes with a 17px, 4bpp, strongly hinted CJK subset. MoonBit view models own parameter slots, focus, layout, theme and settings state, strength color, and battery presentation policy.
    • Core: generation, unbiased indexes, output postconditions, entropy and strength, state transitions, settings input policy, view models, battery policy, and sound synthesis are implemented in MoonBit.

    #Reusable engine and applications

    The project now has a library-first boundary instead of exposing generation only as firmware internals:

    • src is the root, platform-neutral MoonBit package; its API guide covers the typed PasswordPolicy, String-returning application APIs, allocation-controlled embedded APIs, and injected random source.
    • src/cmd/securegen is an independent MoonBit application using host cryptographic entropy through moonbitlang/core/env; it supports profiles, custom lengths, PINs, and batch output.
    • examples/consumer is a separate MoonBit module with a versioned dependency on the root library and cross-module tests.
    • src/cmd/web and examples/web form a browser consumer: MoonBit owns the credential workflow while the adapter supplies DOM access and browser cryptographic randomness.
    • The AI Passport firmware is a second, real application. Its stable C ABI now delegates password, PIN, and passphrase generation to securegen while C continues to provide hardware entropy and device I/O.

    This separation lets Native, Wasm, browser, command-line, and embedded consumers share the same engine without depending on ESP-IDF, LVGL, BLE, or FoloToy code.

    #Settings and persistence

    • Long-press OK on the main screen while not editing to switch between cyberpunk and blue-sky themes. Long-press DOWN to open Settings.
    • Use UP / DOWN to select Theme or Sound, press OK to change the selected value, and long-press OK to return.
    • Theme switches between the dark cyberpunk interface and the blue-sky, clouds, and grass interface.
    • Sound enables or disables the success chime without affecting password generation.
    • Theme and sound preferences and up to three BLE bond records are stored in ESP-IDF NVS. Generated passwords and password history are never persisted.
    • If NVS is unavailable, the selected values still apply for the current session and the firmware logs a warning. The application does not erase the NVS partition automatically.

    #MoonBit-first implementation

    The repository now contains 4,335 physical production .mbt lines and 2,330 MoonBit test/CLI lines, 6,665 in total. Excluding tests, applications, blank lines, and comments leaves 3,619 effective production MoonBit lines. tools/check_repo.py scans both the root library and firmware adapter and independently enforces at least 1,000 effective production lines; tests and applications cannot satisfy that gate.

    The production MoonBit modules are compiled into and called by the ESP-IDF firmware. They own:

    • Random password, PIN, and passphrase algorithms plus enabled-class guarantees;
    • the RandomSource abstraction, rejection sampling, and generated-output postconditions;
    • parameter policies, entropy estimates, and strength classification;
    • NAVIGATION / EDITING transitions and configuration change detection;
    • settings focus, button-gesture mapping, theme selection, and sound policy;
    • parameter slot, coordinate, focus, editing, and value view models;
    • pure policy for resolving raw CW2017 readings into a display value;
    • BLE keyboard state, send eligibility, and the complete printable-ASCII to USB HID report mapping;
    • success-note sequencing, attack/release envelopes, and PCM sample generation.

    C is restricted to ESP-IDF/BSP initialization, LVGL widget calls, raw I2C readings, FreeRTOS scheduling, the NVS persistence adapter, NimBLE HID transport, codec writes, the secure-random source, and Flash dictionary access.

    #Upstream and attribution

    This project is built on the open-source FoloToy AI Passport firmware and hardware support stack.

    The upstream FoloToy AI Passport project is licensed under the MIT License. Its original copyright and license notices are retained in this repository.

    This repository adds the MoonBit-based password generator, product logic, interaction design, UI, tests, documentation, and related firmware modifications for MoonBit Hackathon 2026.

    #Toolchains

    • ESP-IDF 5.5.3, target esp32c3
    • MoonBit with the native/C backend (moon and moonc on PATH)
    • Python 3

    The verified development snapshot used moon 0.1.20260904 and moonc v0.10.12+1634b282e. The ESP-IDF build invokes tools/generate_moonbit.py, which compiles the reusable securegen package and the firmware adapter package to portable C, then links them into the moonbit_password ESP-IDF component. Generated C is a build artifact and is not committed.

    GitHub Actions installs MoonBit's latest stable channel because dated CLI bundles are not guaranteed to remain downloadable from the official CDN. The exact tool versions printed by each CI run are therefore part of that run's verification record; the version above remains the locally verified snapshot.

    #Test

    Run the full static and host-test gate:

    ./tools/validate.sh --static

    Run the MoonBit core directly:

    moon check --target wasm-gc --deny-warn MOONBIT_NEW_NATIVE=0 moon -C moonbit check --target native --deny-warn MOONBIT_NEW_NATIVE=0 moon -C moonbit test --target native --release

    The reusable package also has a backend-independent gate that runs on WasmGC:

    moon test -p zhangsan2000w-art/moonbit-securegen \ --target wasm-gc --release --deny-warn moon -C examples/consumer test --target js --release --deny-warn moon run src/cmd/securegen --target js --release -- --profile strict --length 24

    Deterministic sources are used only by tests. The CLI uses host cryptographic entropy; firmware randomness crosses the C FFI boundary into the ESP32 adapter.

    #Build

    Activate ESP-IDF 5.5.3, ensure MoonBit is on PATH, then run:

    ./tools/validate.sh --firmware

    To run every gate:

    ./tools/validate.sh

    The firmware gate uses a fresh temporary build, runs idf.py build, creates a merged flash image with idf.py merge-bin, verifies its component offsets and partition bounds, and copies only the checked image to:

    build/FoloToy-AI-Passport-full.bin

    build/FoloToy-AI-Passport.bin is only the application image and is not a substitute for the merged firmware.

    On Windows, use an ESP-IDF PowerShell followed by Git Bash, or pass the active ESP-IDF Python explicitly if the Microsoft Store python3 alias is unusable:

    PYTHON='D:/path/to/idf-python/Scripts/python.exe' ./tools/validate.sh --static

    If a constrained Windows environment cannot write to the configured ccache directory, set IDF_NO_CCACHE=1 for the firmware gate. CI keeps ccache enabled by default.

    #Flash

    The merged image is written at offset 0x0:

    python -m esptool --chip esp32c3 --baud 460800 \ --before default-reset --after hard-reset \ write-flash 0x0 build/FoloToy-AI-Passport-full.bin

    Alternatively, use the official AI Passport web flasher and select the same -full.bin file. Successful compilation or flashing is not evidence that the UI, buttons, fonts, power behavior, and RNG adapter have passed physical-device acceptance.

    Flashing the merged image at 0x0 can reset the NVS region. After initial provisioning, use segmented idf.py flash during development when existing theme and sound preferences must be preserved.

    #Offline word list and fonts

    • The memorable mode bundles the 1,296-entry EFF Short Wordlist for Passphrases #1, attributed to the Electronic Frontier Foundation under CC BY 3.0 US. The tracked source SHA-256 is 8f5ca830b8bffb6fe39c9736c024a00a6a6411adb3f83a9be8bfeeb6e067ae69.
    • Build-time code generation packs all words into one NUL-separated constant byte blob with 16-bit offsets. The table remains in Flash and is not loaded wholesale into RAM at startup.
    • The Chinese LVGL glyph subset was generated from Noto Sans SC. Its OFL 1.1 notice is tracked at assets/fonts/NotoSansSC-OFL.txt. Only ASCII and V1 UI glyphs are compiled into the firmware; the current subset uses 17px, 4bpp, and strong autohinting for heavier small-screen strokes.
    • The subset uses LVGL's compressed font format, so examples/folotoy-ai-passport/sdkconfig.defaults enables CONFIG_LV_USE_FONT_COMPRESSED=y. If panels and the generated password render but title, mode, and button labels are blank, rebuild from clean defaults and confirm this option is present in the generated sdkconfig.
    • The vendored MoonBit runtime files retain their Apache-2.0 notice in examples/folotoy-ai-passport/components/moonbit_password/RUNTIME_LICENSE.txt. Project-authored code remains under the repository MIT license.

    #API, versioning, design, and security

    #Verification status

    The current tree defines 118 MoonBit tests. Root-library WasmGC tests, CLI JavaScript tests, Native C code generation, the 3,619-line effective-production gate, repository checks, and 18 Python tests pass locally. The ESP-IDF toolchain is not active in the current shell, so the relocated example has not yet received a fresh full firmware build; a pre-migration build is not counted as validation of the new path. BLE pairing and typing, the dual-theme Settings screen, preference persistence across reboot, sound toggle, fonts, buttons, battery behavior, and RNG adapter still require physical-device validation.

    PassphrasePolicy

    pub struct PassphrasePolicy {
    word_count : Int
    capitalize : Bool
    complete_word : Bool
    separator : PassphraseSeparator
    }

    A portable passphrase policy. complete_word=false emits at most the first four characters of each word, which is useful on constrained displays but can reduce the number of unique outputs in a dictionary.

    PassphrasePolicy::is_valid

    fn PassphrasePolicy::is_valid(self : PassphrasePolicy) -> Bool

    PassphrasePolicy::new

    fn PassphrasePolicy::new(word_count : Int, capitalize? : Bool, complete_word? : Bool, separator? : PassphraseSeparator) -> PassphrasePolicy

    PassphrasePolicy::standard

    PassphrasePolicy::word_count

    fn PassphrasePolicy::word_count(self : PassphrasePolicy) -> Int

    PassphraseSeparator

    pub enum PassphraseSeparator {
    Hyphen
    Period
    Underscore
    } derive(Eq,
    Debug
    )

    Separators supported by the portable passphrase generator.

    PassphraseSeparator::hyphen

    PassphraseSeparator::period

    PassphraseSeparator::underscore

    PasswordPolicy

    pub struct PasswordPolicy {
    length : Int
    lowercase : Bool
    uppercase : Bool
    digits : Bool
    symbols : Bool
    exclude_ambiguous : Bool
    safe_symbols_only : Bool
    }

    A portable password policy. The policy has no dependency on ESP-IDF, FoloToy, LVGL, a filesystem, or a particular random-number provider.

    PasswordPolicy::compatible

    fn PasswordPolicy::compatible() -> PasswordPolicy

    A broadly compatible policy for sites that reject punctuation.

    PasswordPolicy::estimated_entropy_bits_x10

    fn PasswordPolicy::estimated_entropy_bits_x10(self : PasswordPolicy) -> Int

    Estimate password entropy in tenths of a bit from length and pool size. The value is a configuration estimate, not a claim about RNG quality.

    PasswordPolicy::is_valid

    fn PasswordPolicy::is_valid(self : PasswordPolicy) -> Bool

    PasswordPolicy::length

    fn PasswordPolicy::length(self : PasswordPolicy) -> Int

    PasswordPolicy::new

    fn PasswordPolicy::new(length : Int, lowercase? : Bool, uppercase? : Bool, digits? : Bool, symbols? : Bool, exclude_ambiguous? : Bool, safe_symbols_only? : Bool) -> PasswordPolicy

    Construct a password policy for a library, command-line, web, or embedded consumer. Use PasswordPolicy::is_valid before presenting custom values.

    PasswordPolicy::standard

    fn PasswordPolicy::standard() -> PasswordPolicy

    The recommended default for general-purpose credentials.

    PasswordPolicy::strict

    A longer policy that keeps the complete supported character set.

    PasswordPolicy::with_length

    fn PasswordPolicy::with_length(self : PasswordPolicy, length : Int) -> PasswordPolicy

    Return the same policy with a different output length. This preserves the selected profile's character-set rules.

    RandomSource

    pub struct RandomSource {
    next : () -> UInt
    }

    A typed random-source boundary. The library deliberately does not choose a platform RNG: native, browser, test, and embedded consumers inject one.

    RandomSource::new

    fn RandomSource::new(next : () -> UInt) -> RandomSource

    RandomSource::next_u32

    fn RandomSource::next_u32(self : RandomSource) -> UInt

    Strength

    pub enum Strength {
    Unknown
    Weak
    Fair
    Strong
    VeryStrong
    } derive(Eq,
    Debug
    )

    Password-strength bands based on an entropy estimate measured in tenths of a bit. The thresholds are presentation aids, not an online-attack promise.

    Strength::fair

    fn Strength::fair() -> Strength

    Strength::strong

    fn Strength::strong() -> Strength

    Strength::unknown

    fn Strength::unknown() -> Strength

    Strength::very_strong

    fn Strength::very_strong() -> Strength

    Strength::weak

    fn Strength::weak() -> Strength

    estimate_passphrase_entropy_bits_x10

    fn estimate_passphrase_entropy_bits_x10(policy : PassphrasePolicy, unique_output_count : Int) -> Int

    Estimate passphrase entropy in tenths of a bit. unique_output_count must be the number of distinct emitted words or prefixes after transformations.

    estimate_pin_entropy_bits_x10

    fn estimate_pin_entropy_bits_x10(length : Int) -> Int

    Estimate decimal PIN entropy in tenths of a bit.

    generate_passphrase

    fn generate_passphrase(policy : PassphrasePolicy, dictionary : Array[String], next_u32 : () -> UInt) -> Result[String, String]

    Generate a passphrase from an in-memory dictionary. Embedded consumers can use generate_passphrase_into directly to read words from Flash.

    generate_passphrase_into

    fn generate_passphrase_into(next_u32 : () -> UInt, emit : (Int) -> Bool, dictionary_count : Int, dictionary_length : (Int) -> Int, dictionary_char : (Int, Int) -> Int, word_count : Int, capitalize : Bool, complete_word : Bool, separator_index : Int) -> Int

    Generate a passphrase into a caller-owned sink. Dictionary access remains callback-based so callers can use arrays, memory-mapped data, or Flash.

    generate_passphrase_with_source

    fn generate_passphrase_with_source(policy : PassphrasePolicy, dictionary : Array[String], source : RandomSource) -> Result[String, String]

    generate_password

    fn generate_password(policy : PasswordPolicy, next_u32 : () -> UInt) -> Result[String, String]

    Generate a password as a MoonBit String. Randomness is injected so a native application, browser, test, or microcontroller can provide the entropy source appropriate to its platform.

    generate_password_compat

    fn generate_password_compat(next_u32 : () -> UInt, emit : (Int) -> Bool, length : Int, lowercase : Bool, uppercase : Bool, digits : Bool, symbols : Bool, exclude_ambiguous : Bool, safe_symbols_only : Bool) -> Int

    Compatibility API for C and existing callback-based consumers. New MoonBit applications should prefer PasswordPolicy and generate_password or generate_password_into.

    generate_password_into

    fn generate_password_into(policy : PasswordPolicy, next_u32 : () -> UInt, emit : (Int) -> Bool) -> Int

    Generate a password into a caller-owned sink. This allocation-controlled API is the integration point used by embedded firmware.

    generate_password_with_source

    fn generate_password_with_source(policy : PasswordPolicy, source : RandomSource) -> Result[String, String]

    Generate a password using a typed random source.

    generate_pin

    fn generate_pin(length : Int, next_u32 : () -> UInt) -> Result[String, String]

    Generate a decimal PIN as a MoonBit String.

    generate_pin_into

    fn generate_pin_into(next_u32 : () -> UInt, emit : (Int) -> Bool, length : Int) -> Int

    Generate a decimal PIN into a caller-owned sink.

    generate_pin_with_source

    fn generate_pin_with_source(length : Int, source : RandomSource) -> Result[String, String]

    Generate a PIN using a typed random source.

    strength_from_entropy_x10

    fn strength_from_entropy_x10(entropy_x10 : Int) -> Strength

    unbiased_index

    fn unbiased_index(next_u32 : () -> UInt, bound : Int) -> Int

    Map a uniform 32-bit sample to [0, bound) without modulo bias.

    validate_password

    fn validate_password(policy : PasswordPolicy, candidate : String) -> Bool

    Check that a candidate exactly satisfies the selected password policy.

    validate_pin

    fn validate_pin(candidate : String) -> Bool

    Check that a candidate is a decimal PIN in the supported 4..32 range.