1use elliptic_curve::sec1::ToEncodedPoint;
2use k256::{ProjectivePoint, PublicKey as K256PublicKey, SecretKey};
3
4use crate::{HARDENED_KEY_OFFSET, XPUB_VERSION_BYTE};
5use std::io::{Cursor, Read, Write};
6
7use crate::{hash::Hash, BSVErrors, ExtendedPrivateKey, PublicKey};
8use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
9use getrandom::*;
10#[cfg(target_arch = "wasm32")]
11use wasm_bindgen::{prelude::*, throw_str};
12
13#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
14pub struct ExtendedPublicKey {
15 public_key: PublicKey,
16 chain_code: Vec<u8>,
17 depth: u8,
18 index: u32,
19 parent_fingerprint: Vec<u8>,
20}
21
22impl ExtendedPublicKey {
23 pub fn new(public_key: &PublicKey, chain_code: &[u8], depth: &u8, index: &u32, parent_fingerprint: Option<&[u8]>) -> Self {
24 let fingerprint = parent_fingerprint.unwrap_or(&[0, 0, 0, 0]);
25
26 ExtendedPublicKey {
27 public_key: public_key.clone(),
28 chain_code: chain_code.to_vec(),
29 depth: *depth,
30 index: *index,
31 parent_fingerprint: fingerprint.to_vec(),
32 }
33 }
34
35 pub fn to_string_impl(&self) -> Result<String, BSVErrors> {
36 let mut cursor: Cursor<Vec<u8>> = Cursor::new(Vec::new());
37
38 cursor
39 .write_u32::<BigEndian>(XPUB_VERSION_BYTE)
40 .and_then(|_| cursor.write_u8(self.depth))
41 .and_then(|_| cursor.write(&self.parent_fingerprint))
42 .and_then(|_| cursor.write_u32::<BigEndian>(self.index))
43 .and_then(|_| cursor.write(&self.chain_code))?;
44
45 let pub_key_bytes = self.public_key.to_bytes_impl()?;
46 cursor.write_all(&pub_key_bytes)?;
47
48 let mut serialised = Vec::new();
49 cursor.set_position(0);
50 cursor.read_to_end(&mut serialised)?;
51
52 let checksum = &Hash::sha_256d(&serialised).to_bytes()[0..4];
53 cursor.write_all(checksum)?;
54
55 serialised = Vec::new();
56 cursor.set_position(0);
57 cursor.read_to_end(&mut serialised)?;
58
59 Ok(bs58::encode(serialised).into_string())
60 }
61
62 pub fn from_string_impl(xpub_string: &str) -> Result<Self, BSVErrors> {
63 let mut cursor = Cursor::new(bs58::decode(xpub_string).into_vec()?);
64
65 cursor.set_position(4);
67
68 let depth = cursor.read_u8()?;
69 let mut parent_fingerprint = vec![0; 4];
70 cursor.read_exact(&mut parent_fingerprint)?;
71 let index = cursor.read_u32::<BigEndian>()?;
72
73 let mut chain_code = vec![0; 32];
74 cursor.read_exact(&mut chain_code)?;
75
76 let mut pub_key_bytes = vec![0; 33];
77 cursor.read_exact(&mut pub_key_bytes)?;
78 let public_key = PublicKey::from_bytes_impl(&pub_key_bytes)?;
79
80 let mut checksum = vec![0; 4];
81 cursor.read_exact(&mut checksum)?;
82
83 Ok(ExtendedPublicKey {
84 public_key,
85 chain_code,
86 depth,
87 index,
88 parent_fingerprint,
89 })
90 }
91
92 pub fn from_random_impl() -> Result<Self, BSVErrors> {
93 let mut seed = vec![0; 64];
94 getrandom(&mut seed)?;
95
96 Self::from_seed_impl(&seed)
97 }
98
99 pub fn from_seed_impl(seed: &[u8]) -> Result<Self, BSVErrors> {
100 let xpriv = ExtendedPrivateKey::from_seed_impl(seed)?;
101
102 Ok(Self::from_xpriv(&xpriv))
103 }
104
105 pub fn derive_impl(&self, index: u32) -> Result<ExtendedPublicKey, BSVErrors> {
106 if index >= HARDENED_KEY_OFFSET {
107 return Err(BSVErrors::DerivationError(format!(
108 "Cannot generate a hardened xpub, choose an index between 0 and {}.",
109 HARDENED_KEY_OFFSET - 1
110 )));
111 }
112
113 let mut key_data: Vec<u8> = vec![];
114 let pub_key_bytes = &self.public_key.clone().to_bytes_impl()?;
115 key_data.extend_from_slice(pub_key_bytes);
116 key_data.extend_from_slice(&index.to_be_bytes());
117
118 let pub_key_bytes = &self.public_key.clone().to_bytes_impl()?;
119 let hash = Hash::hash_160(pub_key_bytes);
120 let fingerprint = &hash.to_bytes()[0..4];
121
122 let hmac = Hash::sha_512_hmac(&key_data, &self.chain_code.clone());
123 let seed_bytes = hmac.to_bytes();
124
125 let mut seed_chunks = seed_bytes.chunks_exact(32_usize);
126 let child_public_key_bytes = match seed_chunks.next() {
128 Some(b) => b,
129 None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for private key".into())),
130 };
131 let child_chain_code = match seed_chunks.next() {
132 Some(b) => b,
133 None => return Err(BSVErrors::InvalidSeedHmacError("Could not get 32 bytes for chain code".into())),
134 };
135
136 let parent_pub_key_bytes = self.public_key.to_bytes_impl()?;
137 let parent_pub_key_point = K256PublicKey::from_sec1_bytes(&parent_pub_key_bytes)?.to_projective();
138
139 let il_scalar = *SecretKey::from_be_bytes(child_public_key_bytes)?.to_nonzero_scalar();
141 let child_pub_key_point = parent_pub_key_point + (ProjectivePoint::GENERATOR * il_scalar);
142
143 let internal_pub_key: K256PublicKey = K256PublicKey::from_affine(child_pub_key_point.to_affine())?;
144 let child_pub_key = PublicKey::from_bytes_impl(internal_pub_key.to_encoded_point(true).as_bytes())?;
145
146 Ok(ExtendedPublicKey {
147 chain_code: child_chain_code.to_vec(),
148 public_key: child_pub_key,
149 depth: self.depth + 1,
150 index,
151 parent_fingerprint: fingerprint.to_vec(),
152 })
153 }
154
155 pub fn derive_from_path_impl(&self, path: &str) -> Result<ExtendedPublicKey, BSVErrors> {
156 if !path.to_ascii_lowercase().starts_with('m') {
157 return Err(BSVErrors::DerivationError("Path did not begin with 'm'".into()));
158 }
159
160 let children = path[1..].split('/').filter(|x| -> bool { !x.is_empty() });
161 let child_indices = children.map(Self::parse_str_to_idx).collect::<Result<Vec<u32>, BSVErrors>>()?;
162
163 if child_indices.is_empty() {
164 return Err(BSVErrors::DerivationError(format!(
165 "No path was provided. Please provide a string of the form m/0. Given path: {}",
166 path
167 )));
168 }
169
170 let mut xpriv = self.derive_impl(child_indices[0])?;
171 for index in child_indices[1..].iter() {
172 xpriv = xpriv.derive_impl(*index)?;
173 }
174
175 Ok(xpriv)
176 }
177
178 fn parse_str_to_idx(x: &str) -> Result<u32, BSVErrors> {
179 let is_hardened = x.ends_with('\'') || x.to_lowercase().ends_with('h');
180 let index_str = x.trim_end_matches('\'').trim_end_matches('h').trim_end_matches('H');
181
182 let index = match index_str.parse::<u32>() {
183 Ok(v) => v,
184 Err(e) => return Err(BSVErrors::DerivationError(e.to_string())),
186 };
187
188 if index >= HARDENED_KEY_OFFSET {
189 return Err(BSVErrors::DerivationError(format!("Indicies may not be greater than {}", HARDENED_KEY_OFFSET - 1)));
190 }
191
192 Ok(match is_hardened {
193 true => index + HARDENED_KEY_OFFSET,
194 false => index,
195 })
196 }
197}
198
199#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
200impl ExtendedPublicKey {
201 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getPublicKey))]
202 pub fn get_public_key(&self) -> PublicKey {
203 self.public_key.clone()
204 }
205
206 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromXPriv))]
207 pub fn from_xpriv(xpriv: &ExtendedPrivateKey) -> Self {
208 Self {
209 public_key: xpriv.get_public_key(),
210 chain_code: xpriv.get_chain_code(),
211 depth: xpriv.get_depth(),
212 index: xpriv.get_index(),
213 parent_fingerprint: xpriv.get_parent_fingerprint(),
214 }
215 }
216
217 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getChainCode))]
218 pub fn get_chain_code(&self) -> Vec<u8> {
219 self.chain_code.clone()
220 }
221
222 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getDepth))]
223 pub fn get_depth(&self) -> u8 {
224 self.depth
225 }
226
227 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getParentFingerprint))]
228 pub fn get_parent_fingerprint(&self) -> Vec<u8> {
229 self.parent_fingerprint.clone()
230 }
231
232 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = getIndex))]
233 pub fn get_index(&self) -> u32 {
234 self.index
235 }
236}
237
238#[cfg(target_arch = "wasm32")]
239#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen)]
240impl ExtendedPublicKey {
241 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = deriveChild))]
242 pub fn derive(&self, index: u32) -> Result<ExtendedPublicKey, JsValue> {
243 match Self::derive_impl(&self, index) {
244 Ok(v) => Ok(v),
245 Err(e) => Err(JsValue::from_str(&e.to_string())),
246 }
247 }
248
249 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = derive))]
250 pub fn derive_from_path(&self, path: &str) -> Result<ExtendedPublicKey, JsValue> {
251 match Self::derive_from_path_impl(&self, path) {
252 Ok(v) => Ok(v),
253 Err(e) => Err(JsValue::from_str(&e.to_string())),
254 }
255 }
256
257 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromSeed))]
258 pub fn from_seed(seed: &[u8]) -> Result<ExtendedPublicKey, JsValue> {
259 match Self::from_seed_impl(seed) {
260 Ok(v) => Ok(v),
261 Err(e) => Err(JsValue::from_str(&e.to_string())),
262 }
263 }
264
265 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromRandom))]
266 pub fn from_random() -> Result<ExtendedPublicKey, JsValue> {
267 match Self::from_random_impl() {
268 Ok(v) => Ok(v),
269 Err(e) => Err(JsValue::from_str(&e.to_string())),
270 }
271 }
272
273 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = fromString))]
274 pub fn from_string(xpub_string: &str) -> Result<ExtendedPublicKey, JsValue> {
275 match Self::from_string_impl(xpub_string) {
276 Ok(v) => Ok(v),
277 Err(e) => Err(JsValue::from_str(&e.to_string())),
278 }
279 }
280
281 #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen-keypair"), wasm_bindgen(js_name = toString))]
282 pub fn to_string(&self) -> Result<String, JsValue> {
283 match Self::to_string_impl(&self) {
284 Ok(v) => Ok(v),
285 Err(e) => Err(JsValue::from_str(&e.to_string())),
286 }
287 }
288}
289
290#[cfg(not(target_arch = "wasm32"))]
291impl ExtendedPublicKey {
292 pub fn derive(&self, index: u32) -> Result<ExtendedPublicKey, BSVErrors> {
293 Self::derive_impl(self, index)
294 }
295
296 pub fn derive_from_path(&self, path: &str) -> Result<ExtendedPublicKey, BSVErrors> {
297 Self::derive_from_path_impl(self, path)
298 }
299
300 pub fn from_seed(seed: &[u8]) -> Result<ExtendedPublicKey, BSVErrors> {
301 Self::from_seed_impl(seed)
302 }
303
304 pub fn from_random() -> Result<ExtendedPublicKey, BSVErrors> {
305 Self::from_random_impl()
306 }
307 pub fn from_string(xpub_string: &str) -> Result<ExtendedPublicKey, BSVErrors> {
308 Self::from_string_impl(xpub_string)
309 }
310 pub fn to_string(&self) -> Result<String, BSVErrors> {
311 Self::to_string_impl(self)
312 }
313}