Skip to main content

fci4096/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(missing_docs)]
3#![deny(unsafe_code)]
4
5//! # fci4096
6//!
7//! A BIP39-inspired mnemonic wordlist and seed-derivation library built on 4096
8//! standard Chinese four-character idioms (*chengyu*). Each idiom encodes 12
9//! bits of entropy (2¹² = 4096), delivering higher information density than
10//! BIP39's 2048-word list (11 bits/word). The checksum length is 1/8 of the
11//! raw entropy.
12//!
13//! Designed for **offline, local-only** key derivation — no network requests,
14//! zero configuration, no third-party cloud services. Runs on native desktop,
15//! embedded devices, hardware wallets (`no_std`), and WebAssembly targets. The
16//! idiom-based wordlist improves memorability, readability, and backup
17//! usability for Chinese-language users while maintaining a self-contained,
18//! locally-controlled mnemonic system.
19//!
20//! ## Quick Start
21//!
22//! ```rust,no_run
23//! use fci4096::{generate, IdiomMnemonicSize};
24//!
25//! // Generate a 12-idiom mnemonic (128 bits of entropy)
26//! let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
27//! println!("{}", mnemonic.phrase());
28//!
29//! // Derive a 64-byte seed via PBKDF2-SHA512
30//! let seed = mnemonic.to_seed("my_passphrase");
31//! ```
32//!
33//! ## Core Parameters
34//!
35//! | Parameter | Value |
36//! |-----------|-------|
37//! | Wordlist size | 4096 (2¹²) |
38//! | Bits per idiom | 12 |
39//! | Idiom length | 4 Chinese characters (uniform) |
40//! | Checksum ratio | 1/8 of entropy length |
41//! | Seed derivation | PBKDF2-HMAC-SHA512 |
42//! | Default iterations | 4096 (BIP39 uses 2048) |
43//! | Lookup complexity | O(log N) binary search |
44//! | Index storage | `Vec<u16>` (2 bytes/idiom) |
45//! | Supported entropy | 128 / 160 / 192 / 224 / 256 bits |
46//! | Supported word counts | 12 / 15 / 18 / 21 / 24 idioms |
47//!
48//! ### Entropy & Word Count Mapping
49//!
50//! | Idioms | Entropy | Checksum | Total Bits |
51//! |--------|---------|----------|------------|
52//! | 12 | 128 | 16 | 144 |
53//! | 15 | 160 | 20 | 180 |
54//! | 18 | 192 | 24 | 216 |
55//! | 21 | 224 | 28 | 252 |
56//! | 24 | 256 | 32 | 288 |
57//!
58//! ## Feature Flags
59//!
60//! | Feature | Default | Description |
61//! |---------|---------|-------------|
62//! | `std`   | Yes     | Standard library support |
63//! | `rand`  | Yes     | OS random source for entropy generation (via `getrandom`) |
64//! | `serde` | No      | `Serialize` / `Deserialize` for [`IdiomMnemonic`] |
65//! | `wasm`  | No      | WebAssembly bindings (auto-enables `rand`) |
66//!
67//! ## Modules
68//!
69//! - [`entropy`] — Bit-level encoding/decoding: entropy ↔ idiom indices, SHA-256 checksum.
70//! - [`mnemonic`] — Core [`IdiomMnemonic`] type and [`IdiomMnemonicSize`] enum.
71//! - [`seed`] — PBKDF2-HMAC-SHA512 seed derivation with NFKD normalization.
72//! - [`idiom_list`] — Compile-time validated 4096-idiom wordlist with O(log N) binary search.
73//! - [`error`] — Error enum ([`Error`]) and [`Result`] alias.
74//!
75//! ## Top-level API
76//!
77//! | Function | Description |
78//! |----------|-------------|
79//! | [`generate`] | Generate a random mnemonic from OS entropy source |
80//! | [`from_entropy`] | Build a mnemonic from raw entropy bytes |
81//! | [`from_phrase`] | Parse & validate a space-separated idiom phrase |
82//! | [`validate`] | Check phrase validity (wordlist + checksum) |
83//! | [`idiom_to_index`] | Binary-search an idiom's index (O(log N)) |
84//! | [`index_to_idiom`] | Look up an idiom by index (O(1)) |
85//! | [`get_pinyin`] | Get the pinyin of an idiom by index |
86//! | [`IDIOM_COUNT`] | Constant: total idiom count (4096) |
87//!
88//! ## Runtime Characteristics
89//!
90//! - **Offline**: All operations are local; zero network I/O.
91//! - **Lightweight**: No heavy dependencies; core stack is `sha2` + `pbkdf2` + `hmac`.
92//! - **`no_std` compatible**: Usable on bare-metal embedded devices and hardware wallets.
93//! - **WASM ready**: Enable the `wasm` feature for browser-based key derivation.
94//! - **Low latency**: O(log N) idiom lookup via Unicode-codepoint-sorted binary search array.
95
96#[cfg(feature = "std")]
97extern crate std;
98
99#[cfg(not(feature = "std"))]
100extern crate alloc;
101
102/// Bit-level entropy encoding/decoding: entropy ↔ idiom index conversion,
103/// SHA-256 checksum calculation, and OS random entropy generation.
104pub mod entropy;
105/// Error enum ([`Error`]) and [`Result`] type alias for all fallible operations.
106pub mod error;
107/// Compile-time validated 4096-idiom wordlist with O(log N) Unicode-codepoint
108/// binary search and pinyin lookup.
109pub mod idiom_list;
110/// Core mnemonic types: [`IdiomMnemonic`] and [`IdiomMnemonicSize`].
111pub mod mnemonic;
112/// Seed derivation via PBKDF2-HMAC-SHA512 with NFKD Unicode normalization.
113pub mod seed;
114
115/// WebAssembly bindings exposing `generate`, `from_phrase`, `validate`,
116/// `phrase`, and `to_seed` to JavaScript (requires the `wasm` feature).
117#[cfg(feature = "wasm")]
118pub mod wasm;
119
120pub use crate::error::{Error, Result};
121pub use crate::idiom_list::ChineseIdiomList;
122pub use crate::mnemonic::{IdiomMnemonic, IdiomMnemonicSize};
123pub use crate::seed::PBKDF2_ITERATIONS;
124
125/// Generate a cryptographically secure random mnemonic from the OS entropy source.
126///
127/// Uses the operating system's CSPRNG (via `getrandom`) to produce raw entropy,
128/// then appends a SHA-256 checksum and maps the bitstream to idiom indices.
129/// Suitable for key creation on desktop, server, embedded (`no_std`), and WASM
130/// targets (requires the `rand` feature; on `wasm32` the `getrandom/js` backend
131/// is used automatically).
132///
133/// # Arguments
134///
135/// * `size` — The mnemonic length variant (12/15/18/21/24 idioms).
136///
137/// # Errors
138///
139/// Returns [`Error::InvalidEntropyLength`] if the size maps to an unsupported
140/// entropy length, or [`Error::RandError`] if the OS random source fails.
141///
142/// # Example
143///
144/// ```rust,no_run
145/// use fci4096::{generate, IdiomMnemonicSize};
146///
147/// let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
148/// assert_eq!(mnemonic.idioms().len(), 12);
149/// ```
150#[cfg(feature = "rand")]
151pub fn generate(size: IdiomMnemonicSize) -> Result<IdiomMnemonic> {
152    IdiomMnemonic::generate(size)
153}
154
155/// Build a mnemonic from raw entropy bytes.
156///
157/// Appends a SHA-256 checksum (1/8 of the entropy length) and slices the
158/// combined bitstream into 12-bit idiom indices. Suitable for deterministic
159/// key derivation from a known entropy source (e.g. hardware RNG, BIP85 child
160/// entropy, or pre-seeded test vectors).
161///
162/// # Arguments
163///
164/// * `entropy` — Raw entropy bytes. Must be 16/20/24/28/32 bytes
165///   (128/160/192/224/256 bits).
166///
167/// # Errors
168///
169/// Returns [`Error::InvalidEntropyLength`] if the byte length does not match
170/// one of the five supported sizes.
171///
172/// # Example
173///
174/// ```rust
175/// use fci4096::from_entropy;
176///
177/// let entropy = [0x00u8; 16]; // 128 bits of entropy
178/// let mnemonic = from_entropy(&entropy).unwrap();
179/// assert_eq!(mnemonic.idioms().len(), 12);
180/// ```
181pub fn from_entropy(entropy: &[u8]) -> Result<IdiomMnemonic> {
182    IdiomMnemonic::from_entropy(entropy)
183}
184
185/// Parse and validate a mnemonic from a space-separated idiom phrase.
186///
187/// Splits the input by half-width spaces, full-width spaces (`\u{3000}`), and
188/// tabs, then looks up each idiom via O(log N) binary search. Automatically
189/// validates the SHA-256 checksum after parsing. Suitable for restoring a
190/// mnemonic from user input, clipboard text, or QR-decoded phrases.
191///
192/// # Arguments
193///
194/// * `phrase` — Space-separated idiom string. Full-width spaces and tabs are
195///   accepted as delimiters.
196///
197/// # Errors
198///
199/// Returns [`Error::InvalidLength`] if the idiom count is not 12/15/18/21/24,
200/// [`Error::InvalidIdiom`] if any token is not a four-character idiom in the
201/// wordlist, or [`Error::ChecksumMismatch`] if the checksum does not validate.
202///
203/// # Example
204///
205/// ```rust
206/// use fci4096::{from_entropy, from_phrase};
207///
208/// let entropy = [0x00u8; 16];
209/// let mnemonic = from_entropy(&entropy).unwrap();
210/// let phrase = mnemonic.phrase();
211/// let recovered = from_phrase(&phrase).unwrap();
212/// assert_eq!(mnemonic, recovered);
213/// ```
214pub fn from_phrase(phrase: &str) -> Result<IdiomMnemonic> {
215    IdiomMnemonic::from_phrase(phrase)
216}
217
218/// Validate a mnemonic phrase without constructing an [`IdiomMnemonic`].
219///
220/// Checks whether all tokens exist in the 4096-idiom wordlist and the SHA-256
221/// checksum matches. Suitable for pre-flight input validation before seed
222/// derivation or before prompting the user to confirm a phrase.
223///
224/// # Arguments
225///
226/// * `phrase` — Space-separated idiom string (full-width spaces and tabs
227///   accepted as delimiters).
228///
229/// # Returns
230///
231/// `true` if the phrase is valid (wordlist + checksum), `false` otherwise.
232/// This function never panics.
233///
234/// # Example
235///
236/// ```rust
237/// use fci4096::{generate, validate, IdiomMnemonicSize};
238///
239/// let mnemonic = generate(IdiomMnemonicSize::Idioms12).unwrap();
240/// assert!(validate(&mnemonic.phrase()));
241/// assert!(!validate("invalid phrase"));
242/// ```
243pub fn validate(phrase: &str) -> bool {
244    IdiomMnemonic::validate(phrase)
245}
246
247/// Look up an idiom's 0-based index in the wordlist via binary search.
248///
249/// Uses binary search over a Unicode-codepoint-sorted lookup array compiled at
250/// build time. Time complexity O(log N) where N = 4096 (≈ 12 comparisons).
251/// Returns `None` immediately for non-four-character input (fast rejection path).
252/// Suitable for high-frequency lookup on resource-constrained devices.
253///
254/// # Arguments
255///
256/// * `idiom` — A four-character Chinese idiom string.
257///
258/// # Returns
259///
260/// `Some(index)` if the idiom exists in the wordlist, `None` otherwise.
261/// Index range is `0..4096`.
262///
263/// # Example
264///
265/// ```rust
266/// use fci4096::{idiom_to_index, index_to_idiom};
267///
268/// let idiom = index_to_idiom(0).unwrap();
269/// if let Some(idx) = idiom_to_index(idiom) {
270///     println!("Idiom index: {}", idx);
271/// }
272/// ```
273pub fn idiom_to_index(idiom: &str) -> Option<usize> {
274    ChineseIdiomList::idiom_to_index(idiom)
275}
276
277/// Look up an idiom by its 0-based index (direct array access, O(1)).
278///
279/// Returns a `&'static str` reference into the compile-time embedded wordlist,
280/// with zero heap allocation. Suitable for rendering mnemonic phrases on
281/// embedded displays or serializing to external formats.
282///
283/// # Arguments
284///
285/// * `idx` — Idiom index, expected range `0..4096`.
286///
287/// # Returns
288///
289/// `Some(&'static str)` if the index is valid, `None` if `idx >= 4096`.
290///
291/// # Example
292///
293/// ```rust
294/// use fci4096::index_to_idiom;
295///
296/// if let Some(idiom) = index_to_idiom(0) {
297///     println!("First idiom in wordlist: {}", idiom);
298/// }
299/// ```
300pub fn index_to_idiom(idx: usize) -> Option<&'static str> {
301    ChineseIdiomList::index_to_idiom(idx)
302}
303
304/// Get the pinyin (romanization) of an idiom by its index.
305///
306/// Returns a `&'static str` pinyin string compiled from the bundled CSV data
307/// at build time. Suitable for input-method integration, text-to-speech, or
308/// pronunciation aids in mnemonic backup/recovery UIs.
309///
310/// # Arguments
311///
312/// * `idx` — Idiom index, expected range `0..4096`.
313///
314/// # Returns
315///
316/// `Some(&'static str)` if the index is valid and pinyin data is available,
317/// `None` if `idx >= 4096` or the CSV data file was not present at build time.
318///
319/// # Example
320///
321/// ```rust
322/// use fci4096::get_pinyin;
323///
324/// if let Some(pinyin) = get_pinyin(0) {
325///     println!("Pinyin: {}", pinyin);
326/// }
327/// ```
328pub fn get_pinyin(idx: usize) -> Option<&'static str> {
329    ChineseIdiomList::get_pinyin(idx)
330}
331
332/// Total number of idioms in the wordlist, fixed at 4096 (2¹²).
333/// Each idiom encodes 12 bits of entropy.
334pub const IDIOM_COUNT: usize = 4096;