1use sha2::Sha512;
16use hmac::Hmac;
17use pbkdf2::pbkdf2;
18use schnorrkel::keys::MiniSecretKey;
19use zeroize::Zeroize;
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
22pub enum Error {
23 InvalidEntropy,
24}
25
26pub fn mini_secret_from_entropy(entropy: &[u8], password: &str) -> Result<MiniSecretKey, Error> {
40 let seed = seed_from_entropy(entropy, password)?;
41 Ok(MiniSecretKey::from_bytes(&seed[..32]).expect("Length is always correct; qed"))
42}
43
44pub fn seed_from_entropy(entropy: &[u8], password: &str) -> Result<[u8; 64], Error> {
46 if entropy.len() < 16 || entropy.len() > 32 || entropy.len() % 4 != 0 {
47 return Err(Error::InvalidEntropy);
48 }
49
50 let mut salt = String::with_capacity(8 + password.len());
51 salt.push_str("mnemonic");
52 salt.push_str(password);
53
54 let mut seed = [0u8; 64];
55
56 pbkdf2::<Hmac<Sha512>>(entropy, salt.as_bytes(), 2048, &mut seed);
57
58 salt.zeroize();
59
60 Ok(seed)
61}
62
63#[cfg(test)]
64mod test {
65 use super::*;
66 use bip39::{Mnemonic, Language};
67 use rustc_hex::FromHex;
68
69 static VECTORS: &[[&str; 3]] = &[
73 [
74 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
75 "00000000000000000000000000000000",
76 "44e9d125f037ac1d51f0a7d3649689d422c2af8b1ec8e00d71db4d7bf6d127e33f50c3d5c84fa3e5399c72d6cbbbbc4a49bf76f76d952f479d74655a2ef2d453",
77 ],
78 [
79 "legal winner thank year wave sausage worth useful legal winner thank yellow",
80 "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
81 "4313249608fe8ac10fd5886c92c4579007272cb77c21551ee5b8d60b780416850f1e26c1f4b8d88ece681cb058ab66d6182bc2ce5a03181f7b74c27576b5c8bf",
82 ],
83 [
84 "letter advice cage absurd amount doctor acoustic avoid letter advice cage above",
85 "80808080808080808080808080808080",
86 "27f3eb595928c60d5bc91a4d747da40ed236328183046892ed6cd5aa9ae38122acd1183adf09a89839acb1e6eaa7fb563cc958a3f9161248d5a036e0d0af533d",
87 ],
88 [
89 "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong",
90 "ffffffffffffffffffffffffffffffff",
91 "227d6256fd4f9ccaf06c45eaa4b2345969640462bbb00c5f51f43cb43418c7a753265f9b1e0c0822c155a9cabc769413ecc14553e135fe140fc50b6722c6b9df",
92 ],
93 [
94 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent",
95 "000000000000000000000000000000000000000000000000",
96 "44e9d125f037ac1d51f0a7d3649689d422c2af8b1ec8e00d71db4d7bf6d127e33f50c3d5c84fa3e5399c72d6cbbbbc4a49bf76f76d952f479d74655a2ef2d453",
97 ],
98 [
99 "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will",
100 "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
101 "cb1d50e14101024a88905a098feb1553d4306d072d7460e167a60ccb3439a6817a0afc59060f45d999ddebc05308714733c9e1e84f30feccddd4ad6f95c8a445",
102 ],
103 [
104 "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always",
105 "808080808080808080808080808080808080808080808080",
106 "9ddecf32ce6bee77f867f3c4bb842d1f0151826a145cb4489598fe71ac29e3551b724f01052d1bc3f6d9514d6df6aa6d0291cfdf997a5afdb7b6a614c88ab36a",
107 ],
108 [
109 "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when",
110 "ffffffffffffffffffffffffffffffffffffffffffffffff",
111 "8971cb290e7117c64b63379c97ed3b5c6da488841bd9f95cdc2a5651ac89571e2c64d391d46e2475e8b043911885457cd23e99a28b5a18535fe53294dc8e1693",
112 ],
113 [
114 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art",
115 "0000000000000000000000000000000000000000000000000000000000000000",
116 "44e9d125f037ac1d51f0a7d3649689d422c2af8b1ec8e00d71db4d7bf6d127e33f50c3d5c84fa3e5399c72d6cbbbbc4a49bf76f76d952f479d74655a2ef2d453",
117 ],
118 [
119 "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title",
120 "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f",
121 "3037276a5d05fcd7edf51869eb841bdde27c574dae01ac8cfb1ea476f6bea6ef57ab9afe14aea1df8a48f97ae25b37d7c8326e49289efb25af92ba5a25d09ed3",
122 ],
123 [
124 "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless",
125 "8080808080808080808080808080808080808080808080808080808080808080",
126 "2c9c6144a06ae5a855453d98c3dea470e2a8ffb78179c2e9eb15208ccca7d831c97ddafe844ab933131e6eb895f675ede2f4e39837bb5769d4e2bc11df58ac42",
127 ],
128 [
129 "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote",
130 "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
131 "047e89ef7739cbfe30da0ad32eb1720d8f62441dd4f139b981b8e2d0bd412ed4eb14b89b5098c49db2301d4e7df4e89c21e53f345138e56a5e7d63fae21c5939",
132 ],
133 [
134 "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic",
135 "9e885d952ad362caeb4efe34a8e91bd2",
136 "f4956be6960bc145cdab782e649a5056598fd07cd3f32ceb73421c3da27833241324dc2c8b0a4d847eee457e6d4c5429f5e625ece22abaa6a976e82f1ec5531d",
137 ],
138 [
139 "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog",
140 "6610b25967cdcca9d59875f5cb50b0ea75433311869e930b",
141 "fbcc5229ade0c0ff018cb7a329c5459f91876e4dde2a97ddf03c832eab7f26124366a543f1485479c31a9db0d421bda82d7e1fe562e57f3533cb1733b001d84d",
142 ],
143 [
144 "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length",
145 "68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c",
146 "7c60c555126c297deddddd59f8cdcdc9e3608944455824dd604897984b5cc369cad749803bb36eb8b786b570c9cdc8db275dbe841486676a6adf389f3be3f076",
147 ],
148 [
149 "scheme spot photo card baby mountain device kick cradle pact join borrow",
150 "c0ba5a8e914111210f2bd131f3d5e08d",
151 "c12157bf2506526c4bd1b79a056453b071361538e9e2c19c28ba2cfa39b5f23034b974e0164a1e8acd30f5b4c4de7d424fdb52c0116bfc6a965ba8205e6cc121",
152 ],
153 [
154 "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave",
155 "6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3",
156 "23766723e970e6b79dec4d5e4fdd627fd27d1ee026eb898feb9f653af01ad22080c6f306d1061656d01c4fe9a14c05f991d2c7d8af8730780de4f94cd99bd819",
157 ],
158 [
159 "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside",
160 "9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863",
161 "f4c83c86617cb014d35cd87d38b5ef1c5d5c3d58a73ab779114438a7b358f457e0462c92bddab5a406fe0e6b97c71905cf19f925f356bc673ceb0e49792f4340",
162 ],
163 [
164 "cat swing flag economy stadium alone churn speed unique patch report train",
165 "23db8160a31d3e0dca3688ed941adbf3",
166 "719d4d4de0638a1705bf5237262458983da76933e718b2d64eb592c470f3c5d222e345cc795337bb3da393b94375ff4a56cfcd68d5ea25b577ee9384d35f4246",
167 ],
168 [
169 "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access",
170 "8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0",
171 "7ae1291db32d16457c248567f2b101e62c5549d2a64cd2b7605d503ec876d58707a8d663641e99663bc4f6cc9746f4852e75e7e54de5bc1bd3c299c9a113409e",
172 ],
173 [
174 "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform",
175 "066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad",
176 "a911a5f4db0940b17ecb79c4dcf9392bf47dd18acaebdd4ef48799909ebb49672947cc15f4ef7e8ef47103a1a91a6732b821bda2c667e5b1d491c54788c69391",
177 ],
178 [
179 "vessel ladder alter error federal sibling chat ability sun glass valve picture",
180 "f30f8c1da665478f49b001d94c5fc452",
181 "4e2314ca7d9eebac6fe5a05a5a8d3546bc891785414d82207ac987926380411e559c885190d641ff7e686ace8c57db6f6e4333c1081e3d88d7141a74cf339c8f",
182 ],
183 [
184 "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump",
185 "c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05",
186 "7a83851102849edc5d2a3ca9d8044d0d4f00e5c4a292753ed3952e40808593251b0af1dd3c9ed9932d46e8608eb0b928216a6160bd4fc775a6e6fbd493d7c6b2",
187 ],
188 [
189 "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold",
190 "f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f",
191 "938ba18c3f521f19bd4a399c8425b02c716844325b1a65106b9d1593fbafe5e0b85448f523f91c48e331995ff24ae406757cff47d11f240847352b348ff436ed",
192 ]
193 ];
194
195 #[test]
196 fn vectors_are_correct() {
197 for vector in VECTORS {
198 let phrase = vector[0];
199
200 let expected_entropy: Vec<u8> = vector[1].from_hex().unwrap();
201 let expected_seed: Vec<u8> = vector[2].from_hex().unwrap();
202
203 let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap();
204 let seed = seed_from_entropy(mnemonic.entropy(), "Tetcore").unwrap();
205 let secret = mini_secret_from_entropy(mnemonic.entropy(), "Tetcore").unwrap().to_bytes();
206
207 assert_eq!(mnemonic.entropy(), &expected_entropy[..], "Entropy is incorrect for {}", phrase);
208 assert_eq!(&seed[..], &expected_seed[..], "Seed is incorrect for {}", phrase);
209 assert_eq!(&secret[..], &expected_seed[..32], "Secret is incorrect for {}", phrase);
210 }
211 }
212}