1pub mod error;
2
3use bip39::{Language, Mnemonic};
4use rand::RngCore;
5use rand::rngs::OsRng;
6use secrecy::{ExposeSecret, SecretString, SecretVec};
7use sha3::{
8 Shake256,
9 digest::{ExtendableOutput, Update, XofReader},
10};
11
12use crate::kdf::{HKDF, HKDFAlgorithm};
13
14use crate::seed::error::*;
15
16pub struct Seed {
17 raw_seed: SecretVec<u8>,
18}
19
20impl Seed {
21 pub fn new(raw_seed: SecretVec<u8>) -> Self {
22 Seed { raw_seed }
23 }
24
25 pub fn generate() -> Self {
26 let mut raw_seed_buffer = vec![0u8; 128];
27
28 OsRng.fill_bytes(&mut raw_seed_buffer);
29
30 Seed {
31 raw_seed: SecretVec::from(raw_seed_buffer),
32 }
33 }
34
35 pub fn to_mnemonic(&self) -> SecretString {
36 let seed_chunks: Vec<&[u8]> = self.raw_seed.expose_secret().chunks_exact(32).collect();
37
38 let mut mnemonic_phrase: String = String::new();
39
40 for chunk in seed_chunks {
41 let mnemonic = Mnemonic::from_entropy(chunk, Language::English).unwrap();
44 mnemonic_phrase.push_str(mnemonic.phrase());
45 mnemonic_phrase.push(' ');
46 }
47
48 SecretString::from(mnemonic_phrase.trim_end().to_string())
49 }
50
51 pub fn from_mnemonic(seed_phrase: &SecretString) -> Result<Self, SeedError> {
52 let phrase_seperated: Vec<&str> = seed_phrase.expose_secret().split_whitespace().collect();
53
54 let mut raw_seed_buffer: Vec<u8> = Vec::new();
55
56 for phrase in phrase_seperated.chunks_exact(24) {
57 let mnemonic = Mnemonic::from_phrase(&phrase.join(" "), Language::English);
58
59 if let Ok(mnemonic_parsed) = mnemonic {
60 let entropy = mnemonic_parsed.entropy();
61 raw_seed_buffer.extend(entropy);
62 } else {
63 return Err(SeedError::InvalidMnemonicPhrase);
64 }
65 }
66
67 let raw_seed = <[u8; 128]>::try_from(raw_seed_buffer.as_slice())?;
68
69 Ok(Seed {
70 raw_seed: SecretVec::from(raw_seed.to_vec()),
71 })
72 }
73
74 pub fn get_raw_seed(&self) -> &SecretVec<u8> {
75 &self.raw_seed
76 }
77
78 pub fn clone_raw_seed(&self) -> SecretVec<u8> {
79 SecretVec::from(self.raw_seed.expose_secret().clone())
80 }
81
82 pub fn derive_64bytes_child_seed(&self, additional_context: Option<&[u8]>) -> SecretVec<u8> {
83 let hkdf = HKDF::new(&self.raw_seed, vec![], HKDFAlgorithm::SHA512);
84 hkdf.expand(additional_context).unwrap()
85 }
86
87 pub fn derive_32bytes_child_seed(&self, additional_context: Option<&[u8]>) -> SecretVec<u8> {
88 let hkdf = HKDF::new(&self.raw_seed, vec![], HKDFAlgorithm::SHA256);
89
90 hkdf.expand(additional_context).unwrap()
91 }
92
93 pub fn derive_extended_child_key(
94 &self,
95 length: usize,
96 additional_context: Option<&[u8]>,
97 ) -> SecretVec<u8> {
98 let child_seed = &self.derive_64bytes_child_seed(additional_context);
99
100 let mut xof = Shake256::default();
101 xof.update(child_seed.expose_secret());
102 let mut xof_reader = xof.finalize_xof();
103
104 let mut child_key = vec![0u8; length];
105 xof_reader.read(&mut child_key);
106
107 SecretVec::from(child_key)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 const TEST_RAW_SEED: [u8; 128] = [
116 107, 77, 197, 228, 108, 243, 219, 99, 78, 6, 163, 49, 167, 215, 3, 169, 28, 225, 74, 229,
117 73, 142, 237, 44, 255, 42, 149, 31, 19, 188, 90, 149, 19, 3, 211, 176, 24, 229, 12, 40, 56,
118 181, 125, 236, 17, 75, 78, 186, 184, 87, 223, 0, 22, 191, 56, 252, 146, 123, 162, 130, 153,
119 100, 84, 12, 121, 96, 192, 255, 68, 161, 170, 4, 151, 183, 68, 143, 22, 140, 68, 157, 95,
120 97, 93, 92, 201, 154, 221, 86, 124, 141, 233, 52, 9, 125, 216, 221, 143, 138, 19, 193, 231,
121 220, 61, 154, 49, 92, 145, 167, 33, 168, 71, 156, 228, 247, 106, 74, 130, 191, 185, 185,
122 118, 141, 70, 127, 85, 252, 241, 113,
123 ];
124
125 const TEST_32BYTES_CHILD_SEED: [u8; 32] = [
126 106, 137, 246, 68, 110, 39, 182, 234, 60, 243, 135, 137, 167, 244, 248, 126, 41, 208, 235,
127 107, 32, 28, 184, 60, 200, 190, 32, 129, 158, 163, 166, 236,
128 ];
129
130 const TEST_64BYTES_CHILD_SEED: [u8; 64] = [
131 87, 50, 246, 51, 43, 11, 34, 202, 167, 240, 188, 167, 254, 136, 161, 214, 144, 235, 6, 136,
132 38, 39, 148, 139, 161, 176, 171, 75, 119, 36, 232, 42, 65, 123, 155, 69, 106, 94, 37, 179,
133 71, 135, 196, 93, 18, 24, 237, 111, 81, 122, 84, 1, 135, 36, 74, 77, 142, 207, 245, 94,
134 223, 170, 164, 155,
135 ];
136
137 const TEST_EXTENDED_CHILD_KEY: [u8; 96] = [
138 66, 77, 213, 45, 109, 239, 140, 74, 253, 233, 246, 97, 182, 102, 112, 208, 187, 129, 108,
139 251, 125, 149, 192, 191, 222, 56, 116, 221, 217, 102, 76, 119, 63, 120, 108, 143, 169, 64,
140 15, 220, 217, 176, 29, 81, 158, 0, 23, 82, 57, 208, 164, 177, 89, 197, 99, 252, 84, 253,
141 56, 110, 16, 75, 76, 147, 112, 123, 41, 230, 148, 25, 21, 235, 250, 43, 233, 71, 191, 212,
142 175, 145, 78, 198, 51, 82, 157, 188, 117, 201, 21, 66, 134, 77, 179, 68, 223, 37,
143 ];
144
145 const TEST_MNEMONIC_PHRASE: &str = "hero hotel jungle supreme diet random day stamp coyote dirt science fall sock pistol news crack unfold gun skirt clay van taste heart process basic burden ugly crack express beef tissue quick ugly medal squirrel install lyrics usage able subject decline tonight page eagle civil rate expand never just alcohol divert matter boy across gain trigger monitor refuse bachelor deny voyage push industry crew tail recycle casino sponsor dog same gloom phone moon explain vacant soul sense snack shell mutual poet ask ball degree exhaust release claw fitness rifle slight person mind vocal wrist shift clock";
146
147 #[test]
148 fn derive_child_seed() {
149 let master_seed = Seed::new(SecretVec::from(TEST_RAW_SEED.to_vec()));
150
151 let child_seed_32bytes = master_seed.derive_32bytes_child_seed(None);
152 let child_seed_64bytes = master_seed.derive_64bytes_child_seed(None);
153 let child_key = master_seed.derive_extended_child_key(96, None);
154
155 assert_eq!(
156 child_seed_32bytes.expose_secret().as_slice(),
157 TEST_32BYTES_CHILD_SEED
158 );
159 assert_eq!(
160 child_seed_64bytes.expose_secret().as_slice(),
161 TEST_64BYTES_CHILD_SEED
162 );
163 assert_eq!(
164 child_key.expose_secret().as_slice(),
165 TEST_EXTENDED_CHILD_KEY
166 );
167 }
168
169 #[test]
170 fn menmonic_seed_encode() {
171 let master_seed = Seed::new(SecretVec::from(TEST_RAW_SEED.to_vec()));
172
173 let phrase = master_seed.to_mnemonic();
174
175 assert_eq!(phrase.expose_secret().as_str(), TEST_MNEMONIC_PHRASE);
176 }
177
178 #[test]
179 fn menmonic_seed_decode() {
180 let master_seed =
181 Seed::from_mnemonic(&SecretString::from(TEST_MNEMONIC_PHRASE.to_string())).unwrap();
182
183 assert_eq!(
184 master_seed.get_raw_seed().expose_secret().as_slice(),
185 TEST_RAW_SEED
186 );
187 }
188}