1use crate::error::Error;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct Phrase(
8 pub [String; 12],
10);
11
12impl Phrase {
13 pub fn from_id_bytes(id: &[u8; 16]) -> Result<Self, Error> {
18 let mnemonic = bip39::Mnemonic::from_entropy(id)
19 .expect("128-bit entropy is always a valid BIP-39 input");
20 let mut words: [String; 12] = Default::default();
21 for (slot, word) in words.iter_mut().zip(mnemonic.words()) {
22 *slot = word.to_string();
23 }
24 Ok(Phrase(words))
25 }
26}
27
28impl std::fmt::Display for Phrase {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(f, "{}", self.0.join(" "))
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn phrase_deterministic() {
40 let id = [0xab; 16];
41 let p1 = Phrase::from_id_bytes(&id).unwrap();
42 let p2 = Phrase::from_id_bytes(&id).unwrap();
43 assert_eq!(p1, p2);
44 }
45
46 #[test]
47 fn phrase_has_12_words() {
48 let id = [0u8; 16];
49 let p = Phrase::from_id_bytes(&id).unwrap();
50 assert_eq!(p.0.len(), 12);
51 for word in &p.0 {
52 assert!(!word.is_empty());
53 }
54 }
55
56 #[test]
57 fn phrase_to_string_is_space_separated() {
58 let id = [0u8; 16];
59 let p = Phrase::from_id_bytes(&id).unwrap();
60 let s = p.to_string();
61 assert_eq!(s.split(' ').count(), 12);
62 }
63}