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 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 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 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 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}