bip39_dict/
entropy.rs

1use super::bits;
2use super::index::*;
3use super::mnemonics::*;
4use cryptoxide::hashing::sha2::Sha256;
5
6#[cfg(not(feature = "std"))]
7use core::fmt;
8#[cfg(feature = "std")]
9use {std::error::Error, std::fmt};
10
11/// Entropy is a random piece of data
12///
13/// See module documentation for mode details about how to use
14/// `Entropy`.
15#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
16pub struct Entropy<const N: usize>(pub [u8; N]);
17
18/// Possible error when trying to create entropy from the mnemonics
19#[derive(Debug, Clone)]
20pub enum EntropyError {
21    /// Invalid parameters when the function is called with mismatching bits
22    InvalidParameters {
23        /// number of checksum bits asked
24        checksum_bits: usize,
25        /// number of total bits
26        total_bits: usize,
27        /// number of words in mnemonics
28        words: usize,
29    },
30    /// Mismatch in checksum
31    ChecksumInvalid,
32}
33
34impl fmt::Display for EntropyError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Self::InvalidParameters {
38                checksum_bits,
39                total_bits,
40                words,
41            } => {
42                write!(
43                    f,
44                    "Invalid Parameters checksum-bits={}, total-bits={}, words={}",
45                    checksum_bits, total_bits, words
46                )
47            }
48            Self::ChecksumInvalid => write!(f, "Invalid Checksum"),
49        }
50    }
51}
52
53#[cfg(feature = "std")]
54impl Error for EntropyError {}
55
56impl<const N: usize> Entropy<N> {
57    /// generate entropy using the given random generator.
58    pub fn generate<G>(gen: G) -> Self
59    where
60        G: Fn() -> u8,
61    {
62        let mut bytes = [0u8; N];
63        for e in bytes.iter_mut() {
64            *e = gen();
65        }
66        Self(bytes)
67    }
68
69    fn full_checksum_data(&self) -> [u8; 32] {
70        Sha256::new().update(&self.0).finalize()
71    }
72
73    /// Try to create an entropy object from the slice
74    ///
75    /// if the slice is not the right size, None is returned
76    pub fn from_slice(slice: &[u8]) -> Option<Self> {
77        if slice.len() == N {
78            let mut out = [0u8; N];
79            out.copy_from_slice(slice);
80            Some(Self(out))
81        } else {
82            None
83        }
84    }
85
86    /// retrieve the `Entropy` from the given [`Mnemonics`](./struct.Mnemonics.html).
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// # use bip39_dict::{ENGLISH, Mnemonics, Entropy};
92    ///
93    /// const MNEMONICS : &'static str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
94    /// let mnemonics = Mnemonics::from_string(&ENGLISH, MNEMONICS)
95    ///     .expect("validating the given mnemonics phrase");
96    ///
97    /// let entropy = Entropy::<16>::from_mnemonics::<12, 4>(&mnemonics)
98    ///     .expect("retrieving the entropy from the mnemonics");
99    /// ```
100    ///
101    /// # Error
102    ///
103    /// This function may fail if the Mnemonic has an invalid checksum. As part of the
104    /// BIP39, the checksum must be embedded in the mnemonic phrase. This allow to check
105    /// the mnemonics have been correctly entered by the user.
106    ///
107    pub fn from_mnemonics<const W: usize, const CS: usize>(
108        mnemonics: &Mnemonics<W>,
109    ) -> Result<Self, EntropyError> {
110        assert!(CS <= 256);
111
112        let total_bits = N * 8 + CS;
113        if total_bits != Mnemonics::<W>::BITS {
114            return Err(EntropyError::InvalidParameters {
115                checksum_bits: CS,
116                total_bits,
117                words: W,
118            });
119        }
120        use bits::BitWriterBy11;
121
122        let mut entropy = [0u8; N];
123        let mut entropy_writer_pos = 0;
124        let mut checksum_data = [0u8; 256];
125
126        // emit the byte to entropy for the N first byte, then to the checksum_data
127        let emit = |b: u8| {
128            if entropy_writer_pos >= N {
129                checksum_data[entropy_writer_pos - N] = b;
130            } else {
131                entropy[entropy_writer_pos] = b;
132            }
133            entropy_writer_pos += 1;
134        };
135        let mut to_validate = BitWriterBy11::new(emit);
136        for mnemonic in mnemonics.indices() {
137            to_validate.write(mnemonic.0);
138        }
139        to_validate.finalize();
140
141        let ret = Self(entropy);
142
143        // check the checksum got from the mnemonics, from the one calculated
144        // from the entropy generated
145        let expected_checksum = ret.full_checksum_data();
146        if CS > 0 {
147            let checksum_data = &checksum_data[0..(entropy_writer_pos - N)];
148            let mut rem = CS;
149            let mut ofs = 0;
150            while rem > 0 {
151                if rem >= 8 {
152                    if checksum_data[ofs] != expected_checksum[ofs] {
153                        return Err(EntropyError::ChecksumInvalid);
154                    }
155                    rem -= 8;
156                } else {
157                    // process up to 7 bits
158                    let mask = ((1 << rem) - 1) << (8 - rem);
159                    if (checksum_data[ofs] & mask) != (expected_checksum[ofs] & mask) {
160                        return Err(EntropyError::ChecksumInvalid);
161                    }
162                    rem = 0;
163                }
164                ofs += 1;
165            }
166        }
167
168        Ok(ret)
169    }
170
171    /// convert the given `Entropy` into a mnemonic phrase of W words.
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// # use bip39_dict::{ENGLISH, Entropy};
177    ///
178    /// let entropy = Entropy::<16>([0;16]);
179    ///
180    /// // convert the 16 bytes entropy into 12 words with 4 bits of checksum
181    /// let mnemonics = entropy.to_mnemonics::<12, 4>()
182    /// 	.expect("correct value of words/checksum for 16 bytes entropy")
183    ///     .to_string(&ENGLISH);
184    /// assert_eq!(mnemonics, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
185    /// ```
186    ///
187    pub fn to_mnemonics<const W: usize, const CS: usize>(
188        &self,
189    ) -> Result<Mnemonics<W>, EntropyError> {
190        assert!(CS <= 256);
191        let total_bits = N * 8 + CS;
192        if total_bits != Mnemonics::<W>::BITS {
193            return Err(EntropyError::InvalidParameters {
194                checksum_bits: CS,
195                total_bits,
196                words: W,
197            });
198        }
199        use bits::{NextRead, ReadState};
200
201        let checksum = self.full_checksum_data();
202
203        let mut state = ReadState::default();
204        let mut read_pos = 0;
205        let mut write_pos = 0;
206
207        let mut words = [MnemonicIndex(0); W];
208        while write_pos < W {
209            let next_byte = if read_pos >= N {
210                checksum[read_pos - N]
211            } else {
212                self.0[read_pos]
213            };
214            read_pos += 1;
215            match state.read8(next_byte) {
216                NextRead::Zero(next_state) => {
217                    state = next_state;
218                }
219                NextRead::One(n, next_state) => {
220                    words[write_pos] = MnemonicIndex::new(n).unwrap();
221                    write_pos += 1;
222                    state = next_state;
223                }
224            }
225        }
226
227        Ok(Mnemonics::<W>::from(words))
228    }
229}
230
231impl<const N: usize> AsRef<[u8]> for Entropy<N> {
232    fn as_ref(&self) -> &[u8] {
233        &self.0
234    }
235}