1use crate::error::DescriptorError;
6use rustywallet_hd::{DerivationPath, ChildNumber, ExtendedPublicKey, ExtendedPrivateKey};
7use rustywallet_keys::public_key::PublicKey;
8use std::fmt;
9
10#[derive(Clone, Debug)]
12pub struct KeyOrigin {
13 pub fingerprint: [u8; 4],
15 pub path: DerivationPath,
17}
18
19impl KeyOrigin {
20 pub fn new(fingerprint: [u8; 4], path: DerivationPath) -> Self {
22 Self { fingerprint, path }
23 }
24
25 pub fn from_str_inner(s: &str) -> Result<Self, DescriptorError> {
27 let parts: Vec<&str> = s.splitn(2, '/').collect();
29
30 let fingerprint = parse_fingerprint(parts[0])?;
31
32 let path = if parts.len() > 1 {
33 DerivationPath::parse(&format!("m/{}", parts[1]))
34 .map_err(|e| DescriptorError::InvalidDerivationPath(e.to_string()))?
35 } else {
36 DerivationPath::master()
37 };
38
39 Ok(Self { fingerprint, path })
40 }
41}
42
43impl fmt::Display for KeyOrigin {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "{}", hex::encode(self.fingerprint))?;
46 for child in self.path.components() {
47 match child {
48 ChildNumber::Normal(i) => write!(f, "/{}", i)?,
49 ChildNumber::Hardened(i) => write!(f, "/{}h", i)?,
50 }
51 }
52 Ok(())
53 }
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
58pub enum Wildcard {
59 #[default]
61 None,
62 Unhardened,
64 Hardened,
66}
67
68impl Wildcard {
69 pub fn is_wildcard(&self) -> bool {
71 !matches!(self, Wildcard::None)
72 }
73}
74
75#[derive(Clone, Debug)]
77pub enum DescriptorKey {
78 Single(PublicKey),
80
81 Xpub {
83 xpub: ExtendedPublicKey,
85 origin: Option<KeyOrigin>,
87 derivation: Vec<ChildNumber>,
89 wildcard: Wildcard,
91 },
92
93 Xprv {
95 xprv: ExtendedPrivateKey,
97 origin: Option<KeyOrigin>,
99 derivation: Vec<ChildNumber>,
101 wildcard: Wildcard,
103 },
104}
105
106impl DescriptorKey {
107 pub fn from_public_key(pubkey: PublicKey) -> Self {
109 Self::Single(pubkey)
110 }
111
112 pub fn from_xpub(xpub: ExtendedPublicKey) -> Self {
114 Self::Xpub {
115 xpub,
116 origin: None,
117 derivation: Vec::new(),
118 wildcard: Wildcard::None,
119 }
120 }
121
122 pub fn from_xprv(xprv: ExtendedPrivateKey) -> Self {
124 Self::Xprv {
125 xprv,
126 origin: None,
127 derivation: Vec::new(),
128 wildcard: Wildcard::None,
129 }
130 }
131
132 pub fn has_wildcard(&self) -> bool {
134 match self {
135 Self::Single(_) => false,
136 Self::Xpub { wildcard, .. } | Self::Xprv { wildcard, .. } => wildcard.is_wildcard(),
137 }
138 }
139
140 pub fn derive_public_key(&self, index: u32) -> Result<PublicKey, DescriptorError> {
142 match self {
143 Self::Single(pk) => Ok(pk.clone()),
144 Self::Xpub { xpub, derivation, wildcard, .. } => {
145 let mut key = xpub.clone();
146
147 for child in derivation {
149 let idx = child.raw_index();
150 key = key.derive_child(idx)
151 .map_err(|e| DescriptorError::DerivationError(e.to_string()))?;
152 }
153
154 if wildcard.is_wildcard() {
156 let idx = match wildcard {
157 Wildcard::Unhardened => index,
158 Wildcard::Hardened => index | 0x80000000,
159 Wildcard::None => unreachable!(),
160 };
161 key = key.derive_child(idx)
162 .map_err(|e| DescriptorError::DerivationError(e.to_string()))?;
163 }
164
165 Ok(key.public_key().clone())
166 }
167 Self::Xprv { xprv, derivation, wildcard, .. } => {
168 let mut key = xprv.clone();
169
170 for child in derivation {
172 let idx = child.raw_index();
173 key = key.derive_child(idx)
174 .map_err(|e| DescriptorError::DerivationError(e.to_string()))?;
175 }
176
177 if wildcard.is_wildcard() {
179 let idx = match wildcard {
180 Wildcard::Unhardened => index,
181 Wildcard::Hardened => index | 0x80000000,
182 Wildcard::None => unreachable!(),
183 };
184 key = key.derive_child(idx)
185 .map_err(|e| DescriptorError::DerivationError(e.to_string()))?;
186 }
187
188 Ok(key.public_key().clone())
189 }
190 }
191 }
192
193 pub fn public_key(&self) -> Result<PublicKey, DescriptorError> {
195 if self.has_wildcard() {
196 return Err(DescriptorError::WildcardNotAllowed);
197 }
198 self.derive_public_key(0)
199 }
200}
201
202impl fmt::Display for DescriptorKey {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 Self::Single(pk) => {
206 write!(f, "{}", pk.to_hex(rustywallet_keys::public_key::PublicKeyFormat::Compressed))
207 }
208 Self::Xpub { xpub, origin, derivation, wildcard } => {
209 if let Some(orig) = origin {
210 write!(f, "[{}]", orig)?;
211 }
212 write!(f, "{}", xpub.to_xpub())?;
213 for child in derivation {
214 match child {
215 ChildNumber::Normal(i) => write!(f, "/{}", i)?,
216 ChildNumber::Hardened(i) => write!(f, "/{}h", i)?,
217 }
218 }
219 match wildcard {
220 Wildcard::None => {}
221 Wildcard::Unhardened => write!(f, "/*")?,
222 Wildcard::Hardened => write!(f, "/*h")?,
223 }
224 Ok(())
225 }
226 Self::Xprv { xprv, origin, derivation, wildcard } => {
227 if let Some(orig) = origin {
228 write!(f, "[{}]", orig)?;
229 }
230 write!(f, "{}", xprv.to_xprv())?;
231 for child in derivation {
232 match child {
233 ChildNumber::Normal(i) => write!(f, "/{}", i)?,
234 ChildNumber::Hardened(i) => write!(f, "/{}h", i)?,
235 }
236 }
237 match wildcard {
238 Wildcard::None => {}
239 Wildcard::Unhardened => write!(f, "/*")?,
240 Wildcard::Hardened => write!(f, "/*h")?,
241 }
242 Ok(())
243 }
244 }
245 }
246}
247
248fn parse_fingerprint(s: &str) -> Result<[u8; 4], DescriptorError> {
250 let bytes = hex::decode(s)
251 .map_err(|_| DescriptorError::InvalidFingerprint(s.to_string()))?;
252
253 if bytes.len() != 4 {
254 return Err(DescriptorError::InvalidFingerprint(format!(
255 "Expected 4 bytes, got {}",
256 bytes.len()
257 )));
258 }
259
260 let mut fingerprint = [0u8; 4];
261 fingerprint.copy_from_slice(&bytes);
262 Ok(fingerprint)
263}
264
265pub fn parse_key(s: &str) -> Result<DescriptorKey, DescriptorError> {
267 let s = s.trim();
268
269 if s.is_empty() {
270 return Err(DescriptorError::InvalidKey("Empty key".into()));
271 }
272
273 let (origin, rest) = if s.starts_with('[') {
275 let end = s.find(']')
276 .ok_or_else(|| DescriptorError::InvalidKey("Unclosed origin bracket".into()))?;
277 let origin_str = &s[1..end];
278 let origin = KeyOrigin::from_str_inner(origin_str)?;
279 (Some(origin), &s[end + 1..])
280 } else {
281 (None, s)
282 };
283
284 if rest.starts_with("xpub") || rest.starts_with("tpub") {
286 return parse_xpub_key(rest, origin);
287 }
288
289 if rest.starts_with("xprv") || rest.starts_with("tprv") {
290 return parse_xprv_key(rest, origin);
291 }
292
293 if rest.len() == 66 || rest.len() == 130 {
295 let pubkey = PublicKey::from_hex(rest)
297 .map_err(|e| DescriptorError::InvalidPublicKey(e.to_string()))?;
298 return Ok(DescriptorKey::Single(pubkey));
299 }
300
301 Err(DescriptorError::InvalidKey(format!("Unknown key format: {}", rest)))
302}
303
304fn parse_xpub_key(s: &str, origin: Option<KeyOrigin>) -> Result<DescriptorKey, DescriptorError> {
305 let xpub_end = s.find('/').unwrap_or(s.len());
307 let xpub_str = &s[..xpub_end];
308
309 let xpub = ExtendedPublicKey::from_xpub(xpub_str)
310 .map_err(|e| DescriptorError::InvalidExtendedKey(e.to_string()))?;
311
312 let (derivation, wildcard) = if xpub_end < s.len() {
314 parse_derivation_suffix(&s[xpub_end..])?
315 } else {
316 (Vec::new(), Wildcard::None)
317 };
318
319 Ok(DescriptorKey::Xpub {
320 xpub,
321 origin,
322 derivation,
323 wildcard,
324 })
325}
326
327fn parse_xprv_key(s: &str, origin: Option<KeyOrigin>) -> Result<DescriptorKey, DescriptorError> {
328 let xprv_end = s.find('/').unwrap_or(s.len());
330 let xprv_str = &s[..xprv_end];
331
332 let xprv = ExtendedPrivateKey::from_xprv(xprv_str)
333 .map_err(|e| DescriptorError::InvalidExtendedKey(e.to_string()))?;
334
335 let (derivation, wildcard) = if xprv_end < s.len() {
337 parse_derivation_suffix(&s[xprv_end..])?
338 } else {
339 (Vec::new(), Wildcard::None)
340 };
341
342 Ok(DescriptorKey::Xprv {
343 xprv,
344 origin,
345 derivation,
346 wildcard,
347 })
348}
349
350fn parse_derivation_suffix(s: &str) -> Result<(Vec<ChildNumber>, Wildcard), DescriptorError> {
351 let mut path_parts = Vec::new();
353 let mut wildcard = Wildcard::None;
354
355 for part in s.split('/').skip(1) {
356 if part.is_empty() {
357 continue;
358 }
359
360 if part == "*" {
361 wildcard = Wildcard::Unhardened;
362 break;
363 } else if part == "*'" || part == "*h" {
364 wildcard = Wildcard::Hardened;
365 break;
366 } else {
367 let (num_str, hardened) = if part.ends_with('\'') || part.ends_with('h') {
369 (&part[..part.len() - 1], true)
370 } else {
371 (part, false)
372 };
373
374 let num: u32 = num_str.parse()
375 .map_err(|_| DescriptorError::InvalidDerivationPath(part.to_string()))?;
376
377 let child = if hardened {
378 ChildNumber::Hardened(num)
379 } else {
380 ChildNumber::Normal(num)
381 };
382
383 path_parts.push(child);
384 }
385 }
386
387 Ok((path_parts, wildcard))
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_parse_raw_pubkey() {
396 let hex = "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5";
397 let key = parse_key(hex).unwrap();
398
399 match key {
400 DescriptorKey::Single(_) => {}
401 _ => panic!("Expected Single key"),
402 }
403 }
404
405 #[test]
406 fn test_parse_xpub() {
407 let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
408 let key = parse_key(xpub).unwrap();
409
410 match key {
411 DescriptorKey::Xpub { wildcard, .. } => {
412 assert_eq!(wildcard, Wildcard::None);
413 }
414 _ => panic!("Expected Xpub key"),
415 }
416 }
417
418 #[test]
419 fn test_parse_xpub_with_derivation() {
420 let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/1";
421 let key = parse_key(xpub).unwrap();
422
423 match key {
424 DescriptorKey::Xpub { derivation, wildcard, .. } => {
425 assert_eq!(derivation.len(), 2);
426 assert_eq!(wildcard, Wildcard::None);
427 }
428 _ => panic!("Expected Xpub key"),
429 }
430 }
431
432 #[test]
433 fn test_parse_xpub_with_wildcard() {
434 let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/*";
435 let key = parse_key(xpub).unwrap();
436
437 match key {
438 DescriptorKey::Xpub { derivation, wildcard, .. } => {
439 assert_eq!(derivation.len(), 1);
440 assert_eq!(wildcard, Wildcard::Unhardened);
441 }
442 _ => panic!("Expected Xpub key"),
443 }
444 }
445
446 #[test]
447 fn test_parse_key_with_origin() {
448 let key_str = "[deadbeef/44h/0h/0h]xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/0/*";
449 let key = parse_key(key_str).unwrap();
450
451 match key {
452 DescriptorKey::Xpub { origin, wildcard, .. } => {
453 assert!(origin.is_some());
454 let orig = origin.unwrap();
455 assert_eq!(orig.fingerprint, [0xde, 0xad, 0xbe, 0xef]);
456 assert_eq!(wildcard, Wildcard::Unhardened);
457 }
458 _ => panic!("Expected Xpub key"),
459 }
460 }
461}