sare_core/seed/
mod.rs

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            // NOTE: Because the chunck sizes are known and in the valid range there will be no errors here
42            // and it can be unwraped without having to handle errors
43            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}