bip39_rusty/
lib.rs

1use rand::rngs::OsRng;
2use rand::RngCore;
3use sha256::digest;
4
5mod language;
6mod types;
7mod utils;
8
9pub use language::Language;
10pub use crate::types::MnemonicType;
11
12const MIN_WORDS: usize = 12;
13const MAX_WORDS: usize = 24;
14const DEFAULT_MNEMONIC_TYPE: MnemonicType = MnemonicType::Bits256; // Default Mnemonic Type when error occurs
15
16#[derive(Debug)]
17pub enum MnemonicError {
18    InvalidChecksum,
19    InvalidEntropy,
20    GeneratorError,
21}
22
23impl std::fmt::Display for MnemonicError {
24    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
25        match self {
26            MnemonicError::InvalidChecksum => write!(f, "Invalid checksum."),
27            MnemonicError::InvalidEntropy => write!(f, "Invalid entropy."),
28            MnemonicError::GeneratorError => write!(f, "Error when creating Mnemonic instance!")
29        }
30    }
31}
32
33pub struct EntropyInfo {
34    pub bytes: usize,
35    pub bits: usize,
36}
37
38pub struct Mnemonic {
39    lang: Language,
40    mnemonic_type: MnemonicType,
41    entropy: Vec<u8>,
42    checksum: u8,
43    mnemonic_phrase: Vec<String>,
44}
45
46
47impl Mnemonic {
48
49    /// Wrapper for .generator() function, created to handle errors
50    pub fn new(lang: Language, mnemonic_type: MnemonicType) -> Mnemonic {
51        match Self::generator(lang, mnemonic_type) {
52            Ok(mut mnemonic) => {
53                mnemonic.mnemonic_phrase_generation();
54                mnemonic
55            }
56            Err(e) => {
57                eprintln!("Error creating mnemonic: {}, using default fallback", e);
58                // Provide default Mnemonic as fallback if error happen
59                Mnemonic::default()
60            }
61        }
62    }
63
64    /// Main function for creating an instance of Mnemonic Struct
65    fn generator(lang: Language, mnemonic_type: MnemonicType) -> Result<Mnemonic, MnemonicError> {
66        /*
67        This is responsible to create Mnemonic instance and set initial values for entropy and checksum
68        */
69        let (mut raw_entropy, checksum_decimal) = utils::prepare_data_for_mnemonic_struct_initialization(mnemonic_type); // Derive entropy and checksum
70        raw_entropy.push(checksum_decimal);
71
72        Ok(Mnemonic {
73            lang,
74            mnemonic_type,
75            entropy: raw_entropy, // I store it entropy + checksum
76            checksum: checksum_decimal,
77            mnemonic_phrase: Vec::new(),
78        })
79    }
80
81    /// Handler if an error occurs inside .new() wrapper of .generator() to return default Mnemonic instance
82    fn default() -> Mnemonic {
83            let (mut raw_entropy, checksum_decimal) = utils::prepare_data_for_mnemonic_struct_initialization(DEFAULT_MNEMONIC_TYPE);
84            raw_entropy.push(checksum_decimal);
85
86            let mut mnemonic = Mnemonic {
87                lang: Language::English,
88                mnemonic_type: DEFAULT_MNEMONIC_TYPE,
89                entropy: raw_entropy,
90                checksum: checksum_decimal,
91                mnemonic_phrase: Vec::new(),
92            };
93
94            mnemonic.mnemonic_phrase_generation();
95            mnemonic
96    }
97
98    pub fn validate_checksum(&self) -> Result<bool, MnemonicError> {
99        /*
100            I use binary representation of entropy since i store entropy + checksum
101            Then i calculate how many bits is my checksum and i retrieve it
102            I convert it to decimal and compare it with my self.checksum to see if it is the same
103        */
104        let binary_entropy = self.convert_entropy_to_binary();
105        let checksum_bits = self.mnemonic_type.bits() / 32;
106
107        if binary_entropy.len() < checksum_bits {
108            return Err(MnemonicError::InvalidChecksum);
109        }
110
111        let checksum_binary = &binary_entropy[binary_entropy.len() - checksum_bits..];
112        let checksum_decimal = u8::from_str_radix(&checksum_binary, 2)
113            .map_err(|_| MnemonicError::InvalidChecksum)?;
114
115        Ok(checksum_decimal == self.checksum)
116    }
117
118    /// Getter for the mnemonic phrase.
119    pub fn mnemonic_phrase(&self) -> &Vec<String> {
120        &self.mnemonic_phrase
121    }
122
123    /// Bellow are functions that implement my bip39 cryptography
124    fn generate_entropy(mnemonic_type: MnemonicType) -> Vec<u8> {
125        let mut rng = OsRng {};
126        let entropy_bytes_count = mnemonic_type.bytes();
127
128        let mut entropy = vec![0u8; entropy_bytes_count]; // empty vector [0, 0, 0, 0, 0...] with length of 16 or 32 depends of mnemonic_type
129
130        // Fill the vector with random bytes
131        rng.fill_bytes(&mut entropy); // [123, 23, 123, 23, 123...]
132        entropy
133    }
134
135    fn generate_checksum(entropy: &Vec<u8>, mnemonic_type: MnemonicType) -> u8 {
136        let hash = digest(entropy); // Hash the entropy using sha256 which returns it in hexadecimal
137
138        if hash.len() < 2 {
139            panic!("Hash must be at least 2 characters.");
140        }
141
142        let checksum_bits = mnemonic_type.bits() / 32;
143        let checksum_index = if checksum_bits == 4 {1} else if checksum_bits == 8 {2} else {0}; // i take 4 bits or 8 bits
144
145        let checksum = &hash[..checksum_index]; // checksum in hexadecimal
146        u8::from_str_radix(&checksum, 16).expect("Failed to parse checksum as u8") // I convert hexadecimal to decimal in order to append in my raw entropy
147    }
148
149    fn convert_entropy_to_binary(&self) -> String {
150        // [123, 231 ,123 ,123 ,43 ,123, 231(checksum)] => 0011100111011001110011
151        let mut binary_entropy = String::new();
152
153        for el in &self.entropy {
154            // Ensure each byte is represented by exactly 8 bits
155            let binary_repr = format!("{:08b}", el);
156            binary_entropy += &binary_repr
157        }
158        binary_entropy // => 011011001110111
159    }
160
161    fn mnemonic_phrase_generation(&mut self) {
162        // Convert my raw entropy + checksum into binary, divide it into chunks of 11-bit each with length of 24 (words) or 12 (words)
163        let binary_entropy = self.convert_entropy_to_binary(); // Convert entropy to binary
164
165        let mut start_idx = 0;
166        let mut chunks = Vec::new(); // ["01000110110", "11100010110" ...] each chunk of 11bits for 24 len if Bit256
167
168        // Loop through the binary string and extract 11-bit chunks
169        while start_idx + 11 <= binary_entropy.len() {
170            // Extract the chunk of 11 bits starting at `start_idx`
171            chunks.push(binary_entropy.get(start_idx..start_idx + 11).unwrap());
172            start_idx += 11; // Move to the next chunk
173        }
174
175        let wordlist = Language::get_predefined_word_list(&self.lang); // I take wordlist from language based on chosen one
176
177        for chunk in chunks {
178            // I have some number calculated from my 11-bit binary from 0 to 2047 and i have wordlist with 2048
179            // I use this decimal representation as index to take word from my predefined list
180            let decimal = usize::from_str_radix(chunk, 2).unwrap(); // Convert binary to decimal
181            let phrase = wordlist[decimal];
182            self.add_mnemonic_phrase(String::from(phrase));
183        }
184    }
185
186    fn add_mnemonic_phrase(&mut self, word: String) {
187        // Function to push words in mnemonic field in my Struct => Util function
188        self.mnemonic_phrase.push(word);
189    }
190}