bip0039 0.14.1

Another Rust implementation of BIP-0039 standard
Documentation
//! Entropy encoding internals.
//!
//! This module contains the logic to encode BIP-0039 entropy bytes into
//! the mnemonic phrase (words joined by single ASCII spaces).
//!
//! It is intentionally crate-private and designed to be the single source of truth
//! for encoding logic used by `Mnemonic::from_entropy` (and any future APIs).
//!
//! Encoding algorithm (BIP-0039):
//! - Given ENT bits of entropy (ENT ∈ {128,160,192,224,256})
//! - Compute checksum = first ENT/32 bits of SHA256(entropy)
//! - Concatenate entropy bits + checksum bits
//! - Split into 11-bit groups -> indices in 0..2048
//! - Map indices to words via the selected `Language` wordlist

#[cfg(not(feature = "std"))]
use alloc::string::String;

use sha2::{Digest, Sha256};

use super::{BITS_PER_BYTE, BITS_PER_WORD, BitAccumulator, Count};
use crate::{
    error::Error,
    language::{AnyLanguage, Language},
};

/// Encode entropy into a mnemonic phrase using `Language` generic type.
///
/// Note:
/// - This function assumes entropy bytes are already in the intended form.
/// - Entropy length is validated against BIP-0039 allowed sizes.
pub fn encode_entropy<L: Language>(entropy: &[u8]) -> Result<String, Error> {
    encode_entropy_with(AnyLanguage::of::<L>(), entropy)
}

/// Encode entropy into a mnemonic phrase using `AnyLanguage` type.
///
/// Note:
/// - This function assumes entropy bytes are already in the intended form.
/// - Entropy length is validated against BIP-0039 allowed sizes.
pub fn encode_entropy_with(language: AnyLanguage, entropy: &[u8]) -> Result<String, Error> {
    let count = Count::from_key_size(entropy.len() * BITS_PER_BYTE)?;

    // An initial entropy of ENT bits is given.
    // A checksum is generated by taking the first `ENT/32` bits of its SHA256 hash,
    // and this checksum is appended to the end of the initial entropy.
    let checksum_byte = Sha256::digest(entropy)[0];
    let checksum_bit_length = count.checksum_bit_length();

    // Estimate output sizes.
    let mut phrase = {
        let words = count.word_count();
        let rough_phrase_cap = words * 8 + (words.saturating_sub(1));
        String::with_capacity(rough_phrase_cap)
    };

    // Stream bytes -> 11-bit indices without allocating a per-bit buffer.
    let mut accumulator = BitAccumulator::new();

    let push_index = |idx: usize, phrase: &mut String| {
        if !phrase.is_empty() {
            phrase.push(' ');
        }
        phrase.push_str(language.word_of(idx));
    };

    // Feed entropy bits (byte-aligned, MSB-first).
    for &b in entropy {
        accumulator.push_bits(b as u64, BITS_PER_BYTE);

        while accumulator.can_take(BITS_PER_WORD) {
            let index = accumulator.take_bits(BITS_PER_WORD) as usize;
            push_index(index, &mut phrase);
        }
    }

    // Feed checksum bits (4..=8, MSB-first) and exhaust remaining indices.
    {
        // Compute checksum bits (MSB-first) in the low bits of a u8.
        let checksum_bits = checksum_byte >> (BITS_PER_BYTE - checksum_bit_length);
        accumulator.push_bits(checksum_bits as u64, checksum_bit_length);

        while accumulator.can_take(BITS_PER_WORD) {
            let index = accumulator.take_bits(BITS_PER_WORD) as usize;
            push_index(index, &mut phrase);
        }
    }

    debug_assert_eq!(
        accumulator.bits(),
        0,
        "entropy + checksum bits not exhausted during encoding"
    );

    Ok(phrase)
}