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 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 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}