rustywallet_descriptor/
key.rs

1//! Key expressions for descriptors
2//!
3//! Handles parsing and representation of keys in descriptors.
4
5use crate::error::DescriptorError;
6use rustywallet_hd::{DerivationPath, ChildNumber, ExtendedPublicKey, ExtendedPrivateKey};
7use rustywallet_keys::public_key::PublicKey;
8use std::fmt;
9
10/// Key origin information [fingerprint/path]
11#[derive(Clone, Debug)]
12pub struct KeyOrigin {
13    /// Master key fingerprint (4 bytes)
14    pub fingerprint: [u8; 4],
15    /// Derivation path from master
16    pub path: DerivationPath,
17}
18
19impl KeyOrigin {
20    /// Create a new key origin
21    pub fn new(fingerprint: [u8; 4], path: DerivationPath) -> Self {
22        Self { fingerprint, path }
23    }
24
25    /// Parse from string like "fingerprint/path"
26    pub fn from_str_inner(s: &str) -> Result<Self, DescriptorError> {
27        // Format: fingerprint/path or just fingerprint
28        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/// Wildcard type for ranged descriptors
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
58pub enum Wildcard {
59    /// No wildcard - single key
60    #[default]
61    None,
62    /// Unhardened wildcard /*
63    Unhardened,
64    /// Hardened wildcard /*' or /*h
65    Hardened,
66}
67
68impl Wildcard {
69    /// Check if this is a wildcard
70    pub fn is_wildcard(&self) -> bool {
71        !matches!(self, Wildcard::None)
72    }
73}
74
75/// A key expression in a descriptor
76#[derive(Clone, Debug)]
77pub enum DescriptorKey {
78    /// Raw public key (compressed, 33 bytes)
79    Single(PublicKey),
80    
81    /// Extended public key with optional origin and derivation
82    Xpub {
83        /// The extended public key
84        xpub: ExtendedPublicKey,
85        /// Optional origin information
86        origin: Option<KeyOrigin>,
87        /// Additional derivation path after xpub
88        derivation: Vec<ChildNumber>,
89        /// Wildcard for range derivation
90        wildcard: Wildcard,
91    },
92    
93    /// Extended private key with optional origin and derivation
94    Xprv {
95        /// The extended private key
96        xprv: ExtendedPrivateKey,
97        /// Optional origin information
98        origin: Option<KeyOrigin>,
99        /// Additional derivation path after xprv
100        derivation: Vec<ChildNumber>,
101        /// Wildcard for range derivation
102        wildcard: Wildcard,
103    },
104}
105
106impl DescriptorKey {
107    /// Create from a single public key
108    pub fn from_public_key(pubkey: PublicKey) -> Self {
109        Self::Single(pubkey)
110    }
111
112    /// Create from an extended public key
113    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    /// Create from an extended private key
123    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    /// Check if this key has a wildcard
133    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    /// Derive the public key at a specific index (for wildcards)
141    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                // Apply derivation path
148                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                // Apply wildcard index
155                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                // Apply derivation path
171                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                // Apply wildcard index
178                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    /// Get the public key (for non-wildcard keys)
194    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
248/// Parse a fingerprint from hex string
249fn 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
265/// Parse a key expression from string
266pub 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    // Check for origin [fingerprint/path]
274    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    // Check for xpub/xprv
285    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    // Try to parse as raw public key (hex)
294    if rest.len() == 66 || rest.len() == 130 {
295        // Compressed (33 bytes = 66 hex) or uncompressed (65 bytes = 130 hex)
296        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    // Find the end of the xpub (111 chars for mainnet, 111 for testnet)
306    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    // Parse derivation path and wildcard
313    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    // Find the end of the xprv
329    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    // Parse derivation path and wildcard
336    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    // s starts with /
352    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            // Parse as child number
368            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}