bsv_wasm/keypair/
extended_public_key.rs

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        // Skip the first 4 bytes "xprv"
66        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 mut seed_chunks = seed_bytes.chunks_exact(32 as usize);
127        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        // Pass child_public_key_bytes to secretkey because both Private + Public use same scalar, just need to multiply by it and add the new point
140        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            // TODO: Make this error handling nicer
185            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}