mnemonic_external/
lib.rs

1#![no_std]
2#![deny(unused_crate_dependencies)]
3
4#[cfg(not(feature = "std"))]
5extern crate alloc;
6
7#[cfg(feature = "std")]
8#[macro_use]
9extern crate std;
10
11#[cfg(not(feature = "std"))]
12use alloc::{string::String, vec::Vec};
13
14#[cfg(feature = "std")]
15use std::{string::String, vec::Vec};
16
17use sha2::{Digest, Sha256};
18use zeroize::{Zeroize, ZeroizeOnDrop};
19
20pub mod error;
21
22#[cfg(feature = "sufficient-memory")]
23pub mod regular;
24
25#[cfg(test)]
26mod tests;
27
28#[cfg(any(feature = "sufficient-memory", test))]
29pub mod wordlist;
30
31use crate::error::ErrorMnemonic;
32
33pub const TOTAL_WORDS: usize = 2048;
34pub const WORD_MAX_LEN: usize = 8;
35pub const SEPARATOR_LEN: usize = 1;
36
37pub const MAX_SEED_LEN: usize = 24;
38
39#[derive(Clone, Copy, Debug, Zeroize)]
40pub struct Bits11(u16);
41
42impl Bits11 {
43    pub fn bits(self) -> u16 {
44        self.0
45    }
46    pub fn from(i: u16) -> Result<Self, ErrorMnemonic> {
47        if (i as usize) < TOTAL_WORDS {
48            Ok(Self(i))
49        } else {
50            Err(ErrorMnemonic::InvalidWordNumber)
51        }
52    }
53}
54
55#[derive(Clone, Debug)]
56pub struct WordListElement<L: AsWordList + ?Sized> {
57    pub word: L::Word,
58    pub bits11: Bits11,
59}
60
61pub trait AsWordList {
62    type Word: AsRef<str>;
63    fn get_word(&self, bits: Bits11) -> Result<Self::Word, ErrorMnemonic>;
64    fn get_words_by_prefix(
65        &self,
66        prefix: &str,
67    ) -> Result<Vec<WordListElement<Self>>, ErrorMnemonic>;
68    fn bits11_for_word(&self, word: &str) -> Result<Bits11, ErrorMnemonic>;
69}
70
71#[derive(Debug, Copy, Clone)]
72pub enum MnemonicType {
73    Words12,
74    Words15,
75    Words18,
76    Words21,
77    Words24,
78}
79
80impl MnemonicType {
81    fn from(len: usize) -> Result<Self, ErrorMnemonic> {
82        match len {
83            12 => Ok(Self::Words12),
84            15 => Ok(Self::Words15),
85            18 => Ok(Self::Words18),
86            21 => Ok(Self::Words21),
87            24 => Ok(Self::Words24),
88            _ => Err(ErrorMnemonic::WordsNumber),
89        }
90    }
91    fn checksum_bits(&self) -> u8 {
92        match &self {
93            Self::Words12 => 4,
94            Self::Words15 => 5,
95            Self::Words18 => 6,
96            Self::Words21 => 7,
97            Self::Words24 => 8,
98        }
99    }
100    fn entropy_bits(&self) -> usize {
101        match &self {
102            Self::Words12 => 128,
103            Self::Words15 => 160,
104            Self::Words18 => 192,
105            Self::Words21 => 224,
106            Self::Words24 => 256,
107        }
108    }
109    fn total_bits(&self) -> usize {
110        self.entropy_bits() + self.checksum_bits() as usize
111    }
112}
113
114#[derive(Clone, Debug, ZeroizeOnDrop)]
115struct BitsHelper {
116    bits: Vec<bool>,
117}
118
119impl BitsHelper {
120    fn with_capacity(cap: usize) -> Self {
121        Self {
122            bits: Vec::with_capacity(cap),
123        }
124    }
125
126    fn extend_from_byte(&mut self, byte: u8) {
127        for i in (0..BITS_IN_BYTE).rev() {
128            let bit = (byte & (1 << i)) != 0;
129            self.bits.push(bit);
130        }
131    }
132
133    fn extend_from_bits11(&mut self, bits11: &Bits11) {
134        let two_bytes = bits11.0.to_be_bytes();
135
136        // last 3 bits of first byte - others are always zero
137        for i in (0..BITS_IN_U11 % BITS_IN_BYTE).rev() {
138            let bit = (two_bytes[0] & (1 << i)) != 0;
139            self.bits.push(bit);
140        }
141
142        // all bits of second byte
143        self.extend_from_byte(two_bytes[1])
144    }
145}
146
147pub const BITS_IN_BYTE: usize = 8;
148pub const BITS_IN_U11: usize = 11;
149
150#[derive(Clone, Debug, ZeroizeOnDrop)]
151pub struct WordSet {
152    pub bits11_set: Vec<Bits11>,
153}
154
155impl WordSet {
156    pub fn from_entropy(entropy: &[u8]) -> Result<Self, ErrorMnemonic> {
157        if entropy.len() < 16 || entropy.len() > 32 || entropy.len() % 4 != 0 {
158            return Err(ErrorMnemonic::InvalidEntropy);
159        }
160
161        let checksum_byte = sha256_first_byte(entropy);
162
163        let mut entropy_bits = BitsHelper::with_capacity((entropy.len() + 1) * BITS_IN_BYTE);
164        for byte in entropy {
165            entropy_bits.extend_from_byte(*byte);
166        }
167        entropy_bits.extend_from_byte(checksum_byte);
168
169        let mut bits11_set: Vec<Bits11> = Vec::with_capacity(MAX_SEED_LEN);
170        for chunk in entropy_bits.bits.chunks_exact(BITS_IN_U11) {
171            let mut bits11: u16 = 0;
172            for (i, bit) in chunk.iter().rev().enumerate() {
173                if *bit {
174                    bits11 |= 1 << i
175                }
176            }
177            bits11_set.push(Bits11(bits11));
178        }
179        Ok(Self { bits11_set })
180    }
181
182    pub fn new() -> Self {
183        Self {
184            bits11_set: Vec::with_capacity(MAX_SEED_LEN),
185        }
186    }
187
188    pub fn add_word<L: AsWordList>(
189        &mut self,
190        word: &str,
191        wordlist: &L,
192    ) -> Result<(), ErrorMnemonic> {
193        if self.bits11_set.len() < MAX_SEED_LEN {
194            let bits11 = wordlist.bits11_for_word(word)?;
195            self.bits11_set.push(bits11);
196        }
197        Ok(())
198    }
199
200    pub fn is_finalizable(&self) -> bool {
201        MnemonicType::from(self.bits11_set.len()).is_ok()
202    }
203
204    pub fn to_entropy(&self) -> Result<Vec<u8>, ErrorMnemonic> {
205        let mnemonic_type = MnemonicType::from(self.bits11_set.len())?;
206
207        let mut entropy_bits = BitsHelper::with_capacity(mnemonic_type.total_bits());
208
209        for bits11 in self.bits11_set.iter() {
210            entropy_bits.extend_from_bits11(bits11);
211        }
212
213        let mut entropy: Vec<u8> = Vec::with_capacity(mnemonic_type.total_bits() / BITS_IN_BYTE);
214
215        let chunks_exact = entropy_bits.bits.chunks_exact(BITS_IN_BYTE);
216        let remainder = chunks_exact.remainder();
217
218        for chunk in chunks_exact {
219            let mut byte: u8 = 0;
220            for (i, bit) in chunk.iter().rev().enumerate() {
221                if *bit {
222                    byte |= 1 << i
223                }
224            }
225            entropy.push(byte);
226        }
227
228        let mut last_byte: u8 = 0;
229        for (i, bit) in remainder.iter().rev().enumerate() {
230            if *bit {
231                last_byte |= 1 << (BITS_IN_BYTE - remainder.len() + i)
232            }
233        }
234
235        entropy.push(last_byte);
236
237        let entropy_len = mnemonic_type.entropy_bits().div_ceil(BITS_IN_BYTE);
238
239        let actual_checksum = checksum(entropy[entropy_len], mnemonic_type.checksum_bits());
240
241        entropy.truncate(entropy_len);
242
243        let checksum_byte = sha256_first_byte(&entropy);
244
245        let expected_checksum = checksum(checksum_byte, mnemonic_type.checksum_bits());
246
247        if actual_checksum != expected_checksum {
248            Err(ErrorMnemonic::InvalidChecksum)
249        } else {
250            Ok(entropy)
251        }
252    }
253
254    pub fn to_phrase<L: AsWordList>(&self, wordlist: &L) -> Result<String, ErrorMnemonic> {
255        let mut phrase = String::with_capacity(
256            self.bits11_set.len() * (WORD_MAX_LEN + SEPARATOR_LEN) - SEPARATOR_LEN,
257        );
258        for bits11 in self.bits11_set.iter() {
259            if !phrase.is_empty() {
260                phrase.push(' ')
261            }
262            let word = wordlist.get_word(*bits11)?;
263            phrase.push_str(word.as_ref());
264        }
265        Ok(phrase)
266    }
267}
268
269fn checksum(source: u8, bits: u8) -> u8 {
270    assert!(bits <= BITS_IN_BYTE as u8);
271    source >> (BITS_IN_BYTE as u8 - bits)
272}
273
274fn sha256_first_byte(input: &[u8]) -> u8 {
275    Sha256::digest(input)[0]
276}