use crate::error::{Error, Result};
use sha2::{Digest, Sha256};
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
const BITS_PER_WORD: usize = 12;
pub const ENTROPY_BITS_VALID: &[usize] = &[128, 160, 192, 224, 256];
pub const MNEMONIC_WORDS_VALID: &[usize] = &[12, 15, 18, 21, 24];
pub fn generate_entropy(bits: usize) -> Result<Vec<u8>> {
if !ENTROPY_BITS_VALID.contains(&bits) {
return Err(Error::InvalidEntropyLength(bits));
}
#[cfg(feature = "rand")]
{
let bytes = bits / 8;
let mut entropy = vec![0u8; bytes];
getrandom::getrandom(&mut entropy)?;
Ok(entropy)
}
#[cfg(not(feature = "rand"))]
{
Err(Error::RandUnavailable)
}
}
fn checksum_bits_for_entropy(entropy_bits: usize) -> usize {
let word_count = (entropy_bits + entropy_bits / 8) / BITS_PER_WORD;
word_count * BITS_PER_WORD - entropy_bits
}
const WORD_COUNT_TO_ENTROPY_BITS: [(usize, usize); 5] =
[(12, 128), (15, 160), (18, 192), (21, 224), (24, 256)];
fn entropy_bits_for_word_count(word_count: usize) -> usize {
for &(wc, bits) in &WORD_COUNT_TO_ENTROPY_BITS {
if wc == word_count {
return bits;
}
}
word_count * BITS_PER_WORD * 8 / 9
}
pub fn calculate_checksum(entropy: &[u8], checksum_bits: usize) -> u32 {
let hash = Sha256::digest(entropy);
let full = u32::from_be_bytes(hash[..4].try_into().unwrap());
full >> (32 - checksum_bits)
}
pub fn entropy_to_indices(entropy: &[u8]) -> Vec<usize> {
let entropy_bits = entropy.len() * 8;
let checksum_bits = checksum_bits_for_entropy(entropy_bits);
let checksum = calculate_checksum(entropy, checksum_bits);
let total_bits = entropy_bits + checksum_bits;
let word_count = total_bits / BITS_PER_WORD;
let mut indices = Vec::with_capacity(word_count);
for w in 0..word_count {
let mut idx = 0usize;
for b in 0..BITS_PER_WORD {
let bit_pos = w * BITS_PER_WORD + b;
let bit_val = if bit_pos < entropy_bits {
let byte_idx = bit_pos / 8;
let bit_idx = 7 - (bit_pos % 8);
(entropy[byte_idx] >> bit_idx) & 1
} else {
let cs_bit_pos = bit_pos - entropy_bits;
((checksum >> (checksum_bits - 1 - cs_bit_pos)) & 1) as u8
};
if bit_val == 1 {
idx |= 1 << (BITS_PER_WORD - 1 - b);
}
}
indices.push(idx);
}
indices
}
pub fn indices_to_entropy(indices: &[usize]) -> Result<Vec<u8>> {
let word_count = indices.len();
if !MNEMONIC_WORDS_VALID.contains(&word_count) {
return Err(Error::InvalidLength(word_count));
}
for &idx in indices {
if idx >= 4096 {
return Err(Error::InvalidIndex(idx));
}
}
indices_to_entropy_inner(indices)
}
pub(crate) fn indices_to_entropy_inner(indices: &[usize]) -> Result<Vec<u8>> {
let word_count = indices.len();
let total_bits = word_count * BITS_PER_WORD;
let mut bits = vec![0u8; total_bits.div_ceil(8)];
for (w, &idx) in indices.iter().enumerate() {
for b in 0..BITS_PER_WORD {
if (idx >> (BITS_PER_WORD - 1 - b)) & 1 == 1 {
let bit_pos = w * BITS_PER_WORD + b;
let byte_idx = bit_pos / 8;
let bit_idx = 7 - (bit_pos % 8);
bits[byte_idx] |= 1 << bit_idx;
}
}
}
let entropy_bits = entropy_bits_for_word_count(word_count);
let checksum_bits = total_bits - entropy_bits;
let entropy_bytes = entropy_bits / 8;
let mut entropy = vec![0u8; entropy_bytes];
entropy.copy_from_slice(&bits[..entropy_bytes]);
let expected_checksum = calculate_checksum(&entropy, checksum_bits);
let mut received_checksum = 0u32;
for i in 0..checksum_bits {
let bit_pos = entropy_bits + i;
let byte_idx = bit_pos / 8;
let bit_idx = 7 - (bit_pos % 8);
if (bits[byte_idx] >> bit_idx) & 1 == 1 {
received_checksum |= 1 << (checksum_bits - 1 - i);
}
}
if expected_checksum != received_checksum {
return Err(Error::ChecksumMismatch);
}
Ok(entropy)
}
pub fn validate_entropy_length(len: usize) -> bool {
ENTROPY_BITS_VALID.contains(&(len * 8))
}
pub fn validate_mnemonic_length(len: usize) -> bool {
MNEMONIC_WORDS_VALID.contains(&len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_checksum_formula() {
for &bits in ENTROPY_BITS_VALID {
assert_eq!(checksum_bits_for_entropy(bits), bits / 8);
}
}
#[test]
fn test_checksum() {
let entropy = [0x00u8; 16];
let checksum_bits = checksum_bits_for_entropy(128);
let checksum = calculate_checksum(&entropy, checksum_bits);
assert!(checksum < (1 << checksum_bits));
}
#[test]
fn test_entropy_to_indices() {
let entropy = [0x00u8; 16];
let indices = entropy_to_indices(&entropy);
assert_eq!(indices.len(), 12);
for &idx in &indices {
assert!(idx < 4096);
}
}
#[test]
fn test_indices_to_entropy_roundtrip() {
let entropy = [0x00u8; 16];
let indices = entropy_to_indices(&entropy);
let recovered = indices_to_entropy(&indices).unwrap();
assert_eq!(entropy, recovered.as_slice());
}
#[test]
fn test_roundtrip_various_entropy() {
for entropy_bits in ENTROPY_BITS_VALID {
let entropy = vec![0xABu8; entropy_bits / 8];
let indices = entropy_to_indices(&entropy);
let recovered = indices_to_entropy(&indices).unwrap();
assert_eq!(entropy, recovered);
}
}
#[test]
fn test_roundtrip_random_patterns() {
let test_entropies: Vec<Vec<u8>> = vec![
vec![0xFF; 16],
vec![0x55; 16],
vec![0xAA; 16],
vec![
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF,
],
vec![
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
0x32, 0x10,
],
];
for entropy in &test_entropies {
let indices = entropy_to_indices(entropy);
let recovered = indices_to_entropy(&indices).unwrap();
assert_eq!(entropy.as_slice(), recovered.as_slice());
}
}
#[test]
fn test_roundtrip_256bit() {
let entropy = vec![0xDEu8; 32];
let indices = entropy_to_indices(&entropy);
assert_eq!(indices.len(), 24);
let recovered = indices_to_entropy(&indices).unwrap();
assert_eq!(entropy, recovered);
}
#[test]
fn test_unchecked_roundtrip() {
let entropy = [0x00u8; 16];
let indices = entropy_to_indices(&entropy);
let recovered = indices_to_entropy_inner(&indices).unwrap();
assert_eq!(entropy, recovered.as_slice());
}
#[test]
fn test_generate_entropy_zero_bits() {
assert!(generate_entropy(0).is_err());
}
#[test]
fn test_invalid_entropy_length() {
assert!(generate_entropy(127).is_err());
assert!(generate_entropy(257).is_err());
}
#[test]
fn test_invalid_mnemonic_length() {
let indices = vec![0; 13];
assert!(indices_to_entropy(&indices).is_err());
}
#[test]
fn test_invalid_index() {
let indices = vec![4096; 12];
assert!(indices_to_entropy(&indices).is_err());
}
#[test]
fn test_validate_functions() {
assert!(validate_entropy_length(16));
assert!(validate_entropy_length(32));
assert!(!validate_entropy_length(10));
assert!(validate_mnemonic_length(12));
assert!(validate_mnemonic_length(24));
assert!(!validate_mnemonic_length(13));
}
}