fci4096 0.1.5

BIP39-inspired mnemonic wordlist using 4096 standard Chinese four-character idioms (chengyu). 12 bits/word, SHA-256 checksum, PBKDF2-SHA512 seed derivation, no_std + WASM support, offline local-only key generation.
Documentation
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![deny(unsafe_code)]

//! # fci4096
//!
//! A BIP39-inspired mnemonic wordlist and seed-derivation library built on 4096
//! standard Chinese four-character idioms (*chengyu*). Each idiom encodes 12
//! bits of entropy (2¹² = 4096), delivering higher information density than
//! BIP39's 2048-word list (11 bits/word). The checksum length is 1/8 of the
//! raw entropy.
//!
//! Designed for **offline, local-only** key derivation — no network requests,
//! zero configuration, no third-party cloud services. Runs on native desktop,
//! embedded devices, hardware wallets (`no_std`), and WebAssembly targets. The
//! idiom-based wordlist improves memorability, readability, and backup
//! usability for Chinese-language users while maintaining a self-contained,
//! locally-controlled mnemonic system.
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use fci4096::{generate, IdiomMnemonicSize};
//!
//! // Generate a 12-idiom mnemonic (128 bits of entropy)
//! let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
//! println!("{}", mnemonic.phrase());
//!
//! // Derive a 64-byte seed via PBKDF2-SHA512
//! let seed = mnemonic.to_seed("my_passphrase");
//! ```
//!
//! ## Core Parameters
//!
//! | Parameter | Value |
//! |-----------|-------|
//! | Wordlist size | 4096 (2¹²) |
//! | Bits per idiom | 12 |
//! | Idiom length | 4 Chinese characters (uniform) |
//! | Checksum ratio | 1/8 of entropy length |
//! | Seed derivation | PBKDF2-HMAC-SHA512 |
//! | Default iterations | 4096 (BIP39 uses 2048) |
//! | Lookup complexity | O(log N) binary search |
//! | Index storage | `Vec<u16>` (2 bytes/idiom) |
//! | Supported entropy | 128 / 160 / 192 / 224 / 256 bits |
//! | Supported word counts | 12 / 15 / 18 / 21 / 24 idioms |
//!
//! ### Entropy & Word Count Mapping
//!
//! | Idioms | Entropy | Checksum | Total Bits |
//! |--------|---------|----------|------------|
//! | 12 | 128 | 16 | 144 |
//! | 15 | 160 | 20 | 180 |
//! | 18 | 192 | 24 | 216 |
//! | 21 | 224 | 28 | 252 |
//! | 24 | 256 | 32 | 288 |
//!
//! ## Feature Flags
//!
//! | Feature | Default | Description |
//! |---------|---------|-------------|
//! | `std`   | Yes     | Standard library support |
//! | `rand`  | Yes     | OS random source for entropy generation (via `getrandom`) |
//! | `serde` | No      | `Serialize` / `Deserialize` for [`IdiomMnemonic`] |
//! | `wasm`  | No      | WebAssembly bindings (auto-enables `rand`) |
//!
//! ## Modules
//!
//! - [`entropy`] — Bit-level encoding/decoding: entropy ↔ idiom indices, SHA-256 checksum.
//! - [`mnemonic`] — Core [`IdiomMnemonic`] type and [`IdiomMnemonicSize`] enum.
//! - [`seed`] — PBKDF2-HMAC-SHA512 seed derivation with NFKD normalization.
//! - [`idiom_list`] — Compile-time validated 4096-idiom wordlist with O(log N) binary search.
//! - [`error`] — Error enum ([`Error`]) and [`Result`] alias.
//!
//! ## Top-level API
//!
//! | Function | Description |
//! |----------|-------------|
//! | [`generate`] | Generate a random mnemonic from OS entropy source |
//! | [`from_entropy`] | Build a mnemonic from raw entropy bytes |
//! | [`from_phrase`] | Parse & validate a space-separated idiom phrase |
//! | [`validate`] | Check phrase validity (wordlist + checksum) |
//! | [`idiom_to_index`] | Binary-search an idiom's index (O(log N)) |
//! | [`index_to_idiom`] | Look up an idiom by index (O(1)) |
//! | [`get_pinyin`] | Get the pinyin of an idiom by index |
//! | [`IDIOM_COUNT`] | Constant: total idiom count (4096) |
//!
//! ## Runtime Characteristics
//!
//! - **Offline**: All operations are local; zero network I/O.
//! - **Lightweight**: No heavy dependencies; core stack is `sha2` + `pbkdf2` + `hmac`.
//! - **`no_std` compatible**: Usable on bare-metal embedded devices and hardware wallets.
//! - **WASM ready**: Enable the `wasm` feature for browser-based key derivation.
//! - **Low latency**: O(log N) idiom lookup via Unicode-codepoint-sorted binary search array.

#[cfg(feature = "std")]
extern crate std;

#[cfg(not(feature = "std"))]
extern crate alloc;

/// Bit-level entropy encoding/decoding: entropy ↔ idiom index conversion,
/// SHA-256 checksum calculation, and OS random entropy generation.
pub mod entropy;
/// Error enum ([`Error`]) and [`Result`] type alias for all fallible operations.
pub mod error;
/// Compile-time validated 4096-idiom wordlist with O(log N) Unicode-codepoint
/// binary search and pinyin lookup.
pub mod idiom_list;
/// Core mnemonic types: [`IdiomMnemonic`] and [`IdiomMnemonicSize`].
pub mod mnemonic;
/// Seed derivation via PBKDF2-HMAC-SHA512 with NFKD Unicode normalization.
pub mod seed;

/// WebAssembly bindings exposing `generate`, `from_phrase`, `validate`,
/// `phrase`, and `to_seed` to JavaScript (requires the `wasm` feature).
#[cfg(feature = "wasm")]
pub mod wasm;

pub use crate::error::{Error, Result};
pub use crate::idiom_list::ChineseIdiomList;
pub use crate::mnemonic::{IdiomMnemonic, IdiomMnemonicSize};
pub use crate::seed::PBKDF2_ITERATIONS;

/// Generate a cryptographically secure random mnemonic from the OS entropy source.
///
/// Uses the operating system's CSPRNG (via `getrandom`) to produce raw entropy,
/// then appends a SHA-256 checksum and maps the bitstream to idiom indices.
/// Suitable for key creation on desktop, server, embedded (`no_std`), and WASM
/// targets (requires the `rand` feature; on `wasm32` the `getrandom/js` backend
/// is used automatically).
///
/// # Arguments
///
/// * `size` — The mnemonic length variant (12/15/18/21/24 idioms).
///
/// # Errors
///
/// Returns [`Error::InvalidEntropyLength`] if the size maps to an unsupported
/// entropy length, or [`Error::RandError`] if the OS random source fails.
///
/// # Example
///
/// ```rust,no_run
/// use fci4096::{generate, IdiomMnemonicSize};
///
/// let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
/// assert_eq!(mnemonic.idioms().len(), 12);
/// ```
#[cfg(feature = "rand")]
pub fn generate(size: IdiomMnemonicSize) -> Result<IdiomMnemonic> {
    IdiomMnemonic::generate(size)
}

/// Build a mnemonic from raw entropy bytes.
///
/// Appends a SHA-256 checksum (1/8 of the entropy length) and slices the
/// combined bitstream into 12-bit idiom indices. Suitable for deterministic
/// key derivation from a known entropy source (e.g. hardware RNG, BIP85 child
/// entropy, or pre-seeded test vectors).
///
/// # Arguments
///
/// * `entropy` — Raw entropy bytes. Must be 16/20/24/28/32 bytes
///   (128/160/192/224/256 bits).
///
/// # Errors
///
/// Returns [`Error::InvalidEntropyLength`] if the byte length does not match
/// one of the five supported sizes.
///
/// # Example
///
/// ```rust
/// use fci4096::from_entropy;
///
/// let entropy = [0x00u8; 16]; // 128 bits of entropy
/// let mnemonic = from_entropy(&entropy).unwrap();
/// assert_eq!(mnemonic.idioms().len(), 12);
/// ```
pub fn from_entropy(entropy: &[u8]) -> Result<IdiomMnemonic> {
    IdiomMnemonic::from_entropy(entropy)
}

/// Parse and validate a mnemonic from a space-separated idiom phrase.
///
/// Splits the input by half-width spaces, full-width spaces (`\u{3000}`), and
/// tabs, then looks up each idiom via O(log N) binary search. Automatically
/// validates the SHA-256 checksum after parsing. Suitable for restoring a
/// mnemonic from user input, clipboard text, or QR-decoded phrases.
///
/// # Arguments
///
/// * `phrase` — Space-separated idiom string. Full-width spaces and tabs are
///   accepted as delimiters.
///
/// # Errors
///
/// Returns [`Error::InvalidLength`] if the idiom count is not 12/15/18/21/24,
/// [`Error::InvalidIdiom`] if any token is not a four-character idiom in the
/// wordlist, or [`Error::ChecksumMismatch`] if the checksum does not validate.
///
/// # Example
///
/// ```rust
/// use fci4096::{from_entropy, from_phrase};
///
/// let entropy = [0x00u8; 16];
/// let mnemonic = from_entropy(&entropy).unwrap();
/// let phrase = mnemonic.phrase();
/// let recovered = from_phrase(&phrase).unwrap();
/// assert_eq!(mnemonic, recovered);
/// ```
pub fn from_phrase(phrase: &str) -> Result<IdiomMnemonic> {
    IdiomMnemonic::from_phrase(phrase)
}

/// Validate a mnemonic phrase without constructing an [`IdiomMnemonic`].
///
/// Checks whether all tokens exist in the 4096-idiom wordlist and the SHA-256
/// checksum matches. Suitable for pre-flight input validation before seed
/// derivation or before prompting the user to confirm a phrase.
///
/// # Arguments
///
/// * `phrase` — Space-separated idiom string (full-width spaces and tabs
///   accepted as delimiters).
///
/// # Returns
///
/// `true` if the phrase is valid (wordlist + checksum), `false` otherwise.
/// This function never panics.
///
/// # Example
///
/// ```rust
/// use fci4096::{generate, validate, IdiomMnemonicSize};
///
/// let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
/// assert!(validate(&mnemonic.phrase()));
/// assert!(!validate("invalid phrase"));
/// ```
pub fn validate(phrase: &str) -> bool {
    IdiomMnemonic::validate(phrase)
}

/// Look up an idiom's 0-based index in the wordlist via binary search.
///
/// Uses binary search over a Unicode-codepoint-sorted lookup array compiled at
/// build time. Time complexity O(log N) where N = 4096 (≈ 12 comparisons).
/// Returns `None` immediately for non-four-character input (fast rejection path).
/// Suitable for high-frequency lookup on resource-constrained devices.
///
/// # Arguments
///
/// * `idiom` — A four-character Chinese idiom string.
///
/// # Returns
///
/// `Some(index)` if the idiom exists in the wordlist, `None` otherwise.
/// Index range is `0..4096`.
///
/// # Example
///
/// ```rust
/// use fci4096::{idiom_to_index, index_to_idiom};
///
/// let idiom = index_to_idiom(0).unwrap();
/// if let Some(idx) = idiom_to_index(idiom) {
///     println!("Idiom index: {}", idx);
/// }
/// ```
pub fn idiom_to_index(idiom: &str) -> Option<usize> {
    ChineseIdiomList::idiom_to_index(idiom)
}

/// Look up an idiom by its 0-based index (direct array access, O(1)).
///
/// Returns a `&'static str` reference into the compile-time embedded wordlist,
/// with zero heap allocation. Suitable for rendering mnemonic phrases on
/// embedded displays or serializing to external formats.
///
/// # Arguments
///
/// * `idx` — Idiom index, expected range `0..4096`.
///
/// # Returns
///
/// `Some(&'static str)` if the index is valid, `None` if `idx >= 4096`.
///
/// # Example
///
/// ```rust
/// use fci4096::index_to_idiom;
///
/// if let Some(idiom) = index_to_idiom(0) {
///     println!("First idiom in wordlist: {}", idiom);
/// }
/// ```
pub fn index_to_idiom(idx: usize) -> Option<&'static str> {
    ChineseIdiomList::index_to_idiom(idx)
}

/// Get the pinyin (romanization) of an idiom by its index.
///
/// Returns a `&'static str` pinyin string compiled from the bundled CSV data
/// at build time. Suitable for input-method integration, text-to-speech, or
/// pronunciation aids in mnemonic backup/recovery UIs.
///
/// # Arguments
///
/// * `idx` — Idiom index, expected range `0..4096`.
///
/// # Returns
///
/// `Some(&'static str)` if the index is valid and pinyin data is available,
/// `None` if `idx >= 4096` or the CSV data file was not present at build time.
///
/// # Example
///
/// ```rust
/// use fci4096::get_pinyin;
///
/// if let Some(pinyin) = get_pinyin(0) {
///     println!("Pinyin: {}", pinyin);
/// }
/// ```
pub fn get_pinyin(idx: usize) -> Option<&'static str> {
    ChineseIdiomList::get_pinyin(idx)
}

/// Total number of idioms in the wordlist, fixed at 4096 (2¹²).
/// Each idiom encodes 12 bits of entropy.
pub const IDIOM_COUNT: usize = 4096;