1use std::{
34 fmt,
35 ops::{BitXor, BitXorAssign},
36 str::FromStr,
37};
38
39pub trait SeedXor {
41 fn xor(&self, rhs: &Self) -> Self;
43}
44
45impl SeedXor for bip39::Mnemonic {
46 fn xor(&self, rhs: &Self) -> Self {
48 let mut entropy = self.to_entropy();
49 let xor_values = rhs.to_entropy();
50
51 entropy
53 .iter_mut()
54 .zip(xor_values.iter())
55 .for_each(|(a, b)| *a ^= b);
56
57 if entropy.len() < xor_values.len() {
59 entropy.extend(xor_values.iter().skip(entropy.len()))
60 }
61
62 bip39::Mnemonic::from_entropy(&entropy).unwrap()
65 }
66}
67
68#[derive(Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
70pub struct Mnemonic {
71 pub inner: bip39::Mnemonic,
73}
74
75impl Mnemonic {
76 fn new(inner: bip39::Mnemonic) -> Self {
78 Mnemonic { inner }
79 }
80
81 pub fn from_entropy(entropy: &[u8]) -> Result<Self, bip39::Error> {
83 match bip39::Mnemonic::from_entropy(entropy) {
84 Ok(inner) => Ok(Mnemonic::new(inner)),
85 Err(err) => Err(err),
86 }
87 }
88
89 fn xor(&self, rhs: &Self) -> Self {
92 Mnemonic::new(self.inner.xor(&rhs.inner))
93 }
94}
95
96impl FromStr for Mnemonic {
97 type Err = bip39::Error;
98
99 fn from_str(mnemonic: &str) -> Result<Self, <Self as FromStr>::Err> {
100 match bip39::Mnemonic::from_str(mnemonic) {
101 Ok(inner) => Ok(Mnemonic::new(inner)),
102 Err(err) => Err(err),
103 }
104 }
105}
106
107impl fmt::Display for Mnemonic {
108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
109 for (i, word) in self.inner.word_iter().enumerate() {
110 if i > 0 {
111 f.write_str(" ")?;
112 }
113 f.write_str(word)?;
114 }
115 Ok(())
116 }
117}
118
119impl BitXor for Mnemonic {
120 type Output = Self;
121
122 fn bitxor(self, rhs: Self) -> Self::Output {
123 self.xor(&rhs)
124 }
125}
126
127impl BitXorAssign for Mnemonic {
128 fn bitxor_assign(&mut self, rhs: Self) {
129 *self = self.xor(&rhs)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::Mnemonic;
136 use std::str::FromStr;
137
138 #[test]
139 fn seed_xor_works() {
140 let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
142 let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
143 let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
144 let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
145
146 let a = Mnemonic::from_str(a_str).unwrap();
147 let b = Mnemonic::from_str(b_str).unwrap();
148 let c = Mnemonic::from_str(c_str).unwrap();
149 let result = Mnemonic::from_str(result_str).unwrap();
150
151 assert_eq!(result, a.clone() ^ b.clone() ^ c.clone());
152 assert_eq!(result, b ^ c ^ a); }
154
155 #[test]
156 fn seed_xor_assignment_works() {
157 let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
159 let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
160 let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
161 let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
162
163 let a = Mnemonic::from_str(a_str).unwrap();
164 let b = Mnemonic::from_str(b_str).unwrap();
165 let c = Mnemonic::from_str(c_str).unwrap();
166 let result = Mnemonic::from_str(result_str).unwrap();
167
168 let mut assigned = a.xor(&b); assigned ^= c;
170
171 assert_eq!(result, assigned);
172 }
173
174 #[test]
175 fn seed_xor_with_different_lengths_works() {
176 let str_24 = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
179 let str_16 = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series number";
180 let str_12 = "vault nominee cradle silk own frown throw leg cactus recall talent wisdom";
181 let result_str = "silent toe meat possible chair blossom wait occur this worth option aware since milk mother grace rocket cement recall obey exchange recycle dragon rocket";
182
183 let w_24 = Mnemonic::from_str(str_24).unwrap();
184 let w_16 = Mnemonic::from_str(str_16).unwrap();
185 let w_12 = Mnemonic::from_str(str_12).unwrap();
186 let result = Mnemonic::from_str(result_str).unwrap();
187
188 assert_eq!(result, w_24.clone() ^ w_16.clone() ^ w_12.clone());
189 assert_eq!(result, w_12 ^ w_24 ^ w_16); }
191}