bsv_wasm/keypair/
extended_private_key.rs

1use crate::{BSVErrors, HARDENED_KEY_OFFSET, KDF, XPRIV_VERSION_BYTE};
2use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
3use getrandom::*;
4use k256::SecretKey;
5use std::{
6    io::{Cursor, Read, Write},
7    ops::Add,
8    vec,
9};
10
11#[cfg(target_arch = "wasm32")]
12use wasm_bindgen::{prelude::*, throw_str};
13
14use crate::{hash::Hash, PrivateKey, PublicKey};
15
16#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
17pub struct ExtendedPrivateKey {
18    private_key: PrivateKey,
19    public_key: PublicKey,
20    chain_code: Vec<u8>,
21    depth: u8,
22    index: u32,
23    parent_fingerprint: Vec<u8>,
24}
25
26impl ExtendedPrivateKey {
27    pub fn new(private_key: &PrivateKey, chain_code: &[u8], depth: &u8, index: &u32, parent_fingerprint: Option<&[u8]>) -> Self {
28        let fingerprint = parent_fingerprint.unwrap_or(&[0, 0, 0, 0]);
29
30        ExtendedPrivateKey {
31            private_key: private_key.clone(),
32            public_key: PublicKey::from_private_key_impl(private_key),
33            chain_code: chain_code.to_vec(),
34            depth: *depth,
35            index: *index,
36            parent_fingerprint: fingerprint.to_vec(),
37        }
38    }
39
40    pub fn to_string_impl(&self) -> Result<String, BSVErrors> {
41        let mut buffer: Vec<u8> = vec![];
42
43        buffer
44            .write_u32::<BigEndian>(XPRIV_VERSION_BYTE)
45            .and_then(|_| buffer.write_u8(self.depth))
46            .and_then(|_| buffer.write(&self.parent_fingerprint))
47            .and_then(|_| buffer.write_u32::<BigEndian>(self.index))
48            .and_then(|_| buffer.write(&self.chain_code))
49            .and_then(|_| buffer.write_u8(0))
50            .and_then(|_| buffer.write(&self.private_key.to_bytes()))?;
51
52        let checksum = &Hash::sha_256d(&buffer).to_bytes()[0..4];
53        buffer.write_all(checksum)?;
54
55        Ok(bs58::encode(buffer).into_string())
56    }
57
58    pub fn from_mnemonic_and_passphrase_impl(mnemonic: &[u8], passphrase: Option<Vec<u8>>) -> Result<Self, BSVErrors> {
59        let fixed_phrase = match passphrase {
60            Some(v) => v,
61            None => b"mnemonic".to_vec(),
62        };
63
64        let seed = KDF::pbkdf2(mnemonic, Some(fixed_phrase.to_vec()), crate::PBKDF2Hashes::SHA512, 2048, 64);
65        let seed_bytes = seed.get_hash().to_bytes();
66        Self::from_seed_impl(&seed_bytes)
67    }
68
69    pub fn from_string_impl(xprv_string: &str) -> Result<Self, BSVErrors> {
70        let mut cursor = Cursor::new(bs58::decode(xprv_string).into_vec()?);
71
72        // Skip the first 4 bytes "xprv"
73        cursor.set_position(4);
74
75        let depth = cursor.read_u8()?;
76        let mut parent_fingerprint = vec![0; 4];
77        cursor.read_exact(&mut parent_fingerprint)?;
78        let index = cursor.read_u32::<BigEndian>()?;
79
80        let mut chain_code = vec![0; 32];
81        cursor.read_exact(&mut chain_code)?;
82
83        // Skip appended 0 byte on private key
84        cursor.set_position(cursor.position() + 1);
85
86        let mut private_key_bytes = vec![0; 32];
87        cursor.read_exact(&mut private_key_bytes)?;
88        let private_key = PrivateKey::from_bytes_impl(&private_key_bytes)?;
89        let public_key = PublicKey::from_private_key_impl(&private_key);
90
91        let mut checksum = vec![0; 4];
92        cursor.read_exact(&mut checksum)?;
93
94        Ok(ExtendedPrivateKey {
95            private_key,
96            public_key,
97            chain_code,
98            depth,
99            index,
100            parent_fingerprint,
101        })
102    }
103
104    pub fn from_random_impl() -> Result<Self, BSVErrors> {
105        let mut seed = vec![0; 64];
106        getrandom(&mut seed)?;
107
108        Self::from_seed_impl(&seed)
109    }
110
111    pub fn from_seed_impl(seed: &[u8]) -> Result<Self, BSVErrors> {
112        let seed_hmac = Hash::sha_512_hmac(seed, b"Bitcoin seed");
113
114        let seed_bytes = seed_hmac.to_bytes();
115        let mut seed_chunks = seed_bytes.chunks_exact(32_usize);
116        let private_key_bytes = match seed_chunks.next() {
117            Some(b) => b,
118            None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for private key".into())),
119        };
120        let chain_code = match seed_chunks.next() {
121            Some(b) => b,
122            None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for chain code".into())),
123        };
124
125        let priv_key = PrivateKey::from_bytes_impl(private_key_bytes)?;
126
127        let pub_key = PublicKey::from_private_key_impl(&priv_key);
128
129        Ok(Self {
130            private_key: priv_key,
131            public_key: pub_key,
132            chain_code: chain_code.to_vec(),
133            depth: 0,
134            index: 0,
135            parent_fingerprint: [0, 0, 0, 0].to_vec(),
136        })
137    }
138
139    pub fn derive_impl(&self, index: u32) -> Result<ExtendedPrivateKey, BSVErrors> {
140        let is_hardened = index >= HARDENED_KEY_OFFSET;
141
142        let key_data = match is_hardened {
143            true => {
144                let mut bytes: Vec<u8> = vec![0x0];
145                bytes.extend_from_slice(&self.private_key.clone().to_bytes());
146                bytes.extend_from_slice(&index.to_be_bytes());
147                bytes
148            }
149            false => {
150                let mut bytes: Vec<u8> = vec![];
151
152                let pub_key_bytes = &self.public_key.clone().to_bytes_impl()?;
153
154                bytes.extend_from_slice(pub_key_bytes);
155                bytes.extend_from_slice(&index.to_be_bytes());
156                bytes
157            }
158        };
159
160        let pub_key_bytes = &self.public_key.clone().to_bytes_impl()?;
161        let hash = Hash::hash_160(pub_key_bytes);
162        let fingerprint = &hash.to_bytes()[0..4];
163
164        let hmac = Hash::sha_512_hmac(&key_data, &self.chain_code.clone());
165        let seed_bytes = hmac.to_bytes();
166
167        let mut seed_chunks = seed_bytes.chunks_exact(32_usize);
168        // let mut seed_chunks = seed_bytes.chunks_exact(32 as usize);
169        let private_key_bytes = match seed_chunks.next() {
170            Some(b) => b,
171            None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for private key".into())),
172        };
173        let child_chain_code = match seed_chunks.next() {
174            Some(b) => b,
175            None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for chain code".into())),
176        };
177
178        let parent_scalar = SecretKey::from_be_bytes(&self.private_key.to_bytes())?.to_nonzero_scalar();
179
180        let il_scalar = *SecretKey::from_be_bytes(private_key_bytes)?.to_nonzero_scalar();
181
182        // child_private_key = il + parent_key % n
183        let derived_private_key = parent_scalar.add(il_scalar);
184
185        let child_private_key = PrivateKey::from_bytes_impl(&derived_private_key.to_bytes())?;
186
187        let child_chain_code_bytes = child_chain_code.to_vec();
188        let child_pub_key = PublicKey::from_private_key_impl(&child_private_key);
189
190        Ok(ExtendedPrivateKey {
191            chain_code: child_chain_code_bytes,
192            private_key: child_private_key,
193            public_key: child_pub_key,
194            depth: self.depth + 1,
195            index,
196            parent_fingerprint: fingerprint.to_vec(),
197        })
198    }
199
200    pub fn derive_from_path_impl(&self, path: &str) -> Result<ExtendedPrivateKey, BSVErrors> {
201        #[allow(clippy::bool_comparison)]
202        if path.to_ascii_lowercase().starts_with('m') == false {
203            return Err(BSVErrors::DerivationError("Path did not begin with 'm'".into()));
204        }
205
206        let children = path[1..].split('/').filter(|x| -> bool { !x.is_empty() });
207        let child_indices = children.map(Self::parse_str_to_idx).collect::<Result<Vec<u32>, BSVErrors>>()?;
208
209        if child_indices.is_empty() {
210            return Err(BSVErrors::DerivationError(format!(
211                "No path was provided. Please provide a string of the form m/0. Given path: {}",
212                path
213            )));
214        }
215
216        let mut xpriv = self.derive_impl(child_indices[0])?;
217        for index in child_indices[1..].iter() {
218            xpriv = xpriv.derive_impl(*index)?;
219        }
220
221        Ok(xpriv)
222    }
223
224    fn parse_str_to_idx(x: &str) -> Result<u32, BSVErrors> {
225        let is_hardened = x.ends_with('\'') || x.to_lowercase().ends_with('h');
226        let index_str = x.trim_end_matches('\'').trim_end_matches('h').trim_end_matches('H');
227
228        let index = index_str.parse::<u32>()?;
229
230        if index >= HARDENED_KEY_OFFSET {
231            return Err(BSVErrors::DerivationError(format!("Indicies may not be greater than {}", HARDENED_KEY_OFFSET - 1)));
232        }
233
234        Ok(match is_hardened {
235            true => index + HARDENED_KEY_OFFSET,
236            false => index,
237        })
238    }
239}
240
241#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
242impl ExtendedPrivateKey {
243    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getPrivateKey))]
244    pub fn get_private_key(&self) -> PrivateKey {
245        self.private_key.clone()
246    }
247
248    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getPublicKey))]
249    pub fn get_public_key(&self) -> PublicKey {
250        self.public_key.clone()
251    }
252
253    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getChainCode))]
254    pub fn get_chain_code(&self) -> Vec<u8> {
255        self.chain_code.clone()
256    }
257
258    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getDepth))]
259    pub fn get_depth(&self) -> u8 {
260        self.depth
261    }
262
263    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getParentFingerprint))]
264    pub fn get_parent_fingerprint(&self) -> Vec<u8> {
265        self.parent_fingerprint.clone()
266    }
267
268    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getIndex))]
269    pub fn get_index(&self) -> u32 {
270        self.index
271    }
272}
273
274#[cfg(target_arch = "wasm32")]
275#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
276impl ExtendedPrivateKey {
277    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = deriveChild))]
278    pub fn derive(&self, index: u32) -> Result<ExtendedPrivateKey, JsValue> {
279        match Self::derive_impl(&self, index) {
280            Ok(v) => Ok(v),
281            Err(e) => Err(JsValue::from_str(&e.to_string())),
282        }
283    }
284
285    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = derive))]
286    pub fn derive_from_path(&self, path: &str) -> Result<ExtendedPrivateKey, JsValue> {
287        match Self::derive_from_path_impl(&self, path) {
288            Ok(v) => Ok(v),
289            Err(e) => Err(JsValue::from_str(&e.to_string())),
290        }
291    }
292
293    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromSeed))]
294    pub fn from_seed(seed: &[u8]) -> Result<ExtendedPrivateKey, JsValue> {
295        match Self::from_seed_impl(seed) {
296            Ok(v) => Ok(v),
297            Err(e) => Err(JsValue::from_str(&e.to_string())),
298        }
299    }
300
301    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromRandom))]
302    pub fn from_random() -> Result<ExtendedPrivateKey, JsValue> {
303        match Self::from_random_impl() {
304            Ok(v) => Ok(v),
305            Err(e) => Err(JsValue::from_str(&e.to_string())),
306        }
307    }
308
309    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromString))]
310    pub fn from_string(xprv_string: &str) -> Result<ExtendedPrivateKey, JsValue> {
311        match Self::from_string_impl(xprv_string) {
312            Ok(v) => Ok(v),
313            Err(e) => Err(JsValue::from_str(&e.to_string())),
314        }
315    }
316
317    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = toString))]
318    pub fn to_string(&self) -> Result<String, JsValue> {
319        match Self::to_string_impl(&self) {
320            Ok(v) => Ok(v),
321            Err(e) => Err(JsValue::from_str(&e.to_string())),
322        }
323    }
324
325    #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromMnemonic))]
326    pub fn from_mnemonic(mnemonic: &[u8], passphrase: Option<Vec<u8>>) -> Result<ExtendedPrivateKey, JsValue> {
327        match Self::from_mnemonic_and_passphrase_impl(mnemonic, passphrase) {
328            Ok(v) => Ok(v),
329            Err(e) => Err(JsValue::from_str(&e.to_string())),
330        }
331    }
332}
333
334#[cfg(not(target_arch = "wasm32"))]
335impl ExtendedPrivateKey {
336    pub fn derive(&self, index: u32) -> Result<ExtendedPrivateKey, BSVErrors> {
337        Self::derive_impl(self, index)
338    }
339
340    pub fn derive_from_path(&self, path: &str) -> Result<ExtendedPrivateKey, BSVErrors> {
341        Self::derive_from_path_impl(self, path)
342    }
343
344    pub fn from_seed(seed: &[u8]) -> Result<ExtendedPrivateKey, BSVErrors> {
345        Self::from_seed_impl(seed)
346    }
347
348    pub fn from_random() -> Result<ExtendedPrivateKey, BSVErrors> {
349        Self::from_random_impl()
350    }
351
352    pub fn from_string(xprv_string: &str) -> Result<ExtendedPrivateKey, BSVErrors> {
353        Self::from_string_impl(xprv_string)
354    }
355
356    pub fn to_string(&self) -> Result<String, BSVErrors> {
357        Self::to_string_impl(self)
358    }
359
360    pub fn from_mnemonic(mnemonic: &[u8], passphrase: Option<Vec<u8>>) -> Result<ExtendedPrivateKey, BSVErrors> {
361        Self::from_mnemonic_and_passphrase_impl(mnemonic, passphrase)
362    }
363}