hkd32/
path.rs

1//! Key derivation paths: locations within the hierarchical derivation tree.
2//!
3//! Paths are lists of "binary safe" (a.k.a. "8-bit clean") components that
4//! specify a location within a tree of keys. Every path within the hierarchy
5//! describes a unique, unrelated key.
6//!
7//! For convenience, HKD32 also supports a string notation inspired by BIP32:
8//! <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree>
9//!
10//! This notation is similar to Unix paths:
11//!
12//! `/first-component/second-component/.../nth-component`
13//!
14//! This corresponds to the following bytestring segments:
15//!
16//! `[b"first-component", b"second-component", ..., b"nth-component"]`
17//!
18//! Paths also support a binary serialization, in which each component is
19//! prefixed by its length. Note that this requires the maximum component
20//! length is 256, a limitation generally enforced by the protocol.
21
22use super::Error;
23#[cfg(feature = "alloc")]
24use crate::{pathbuf::PathBuf, DELIMITER};
25#[cfg(feature = "alloc")]
26use alloc::{str, string::String, vec::Vec};
27#[cfg(feature = "alloc")]
28use core::fmt::{self, Debug};
29
30/// Maximum length of a derivation component
31pub const MAX_COMPONENT_LENGTH: usize = 256;
32
33/// Key derivation paths: location within a key hierarchy which
34/// names/identifies a specific key.
35///
36/// This is the reference type. The corresponding owned type is `hkd32::Path`
37/// (ala the corresponding types in `std`).
38#[derive(Eq, Hash, PartialEq, PartialOrd, Ord)]
39#[repr(transparent)]
40pub struct Path([u8]);
41
42impl Path {
43    /// Create a path from a byte slice.
44    ///
45    /// Returns `Error` if the path is malformed.
46    // TODO(tarcieri): use a safe transparent wrapper type
47    // Pre-RFC: <https://internals.rust-lang.org/t/pre-rfc-patterns-allowing-transparent-wrapper-types/10229>
48    #[allow(unsafe_code)]
49    pub fn new<P>(path: &P) -> Result<&Self, Error>
50    where
51        P: AsRef<[u8]> + ?Sized,
52    {
53        if Components::new(path.as_ref()).validate() {
54            Ok(unsafe { &*(path.as_ref() as *const [u8] as *const Self) })
55        } else {
56            Err(Error)
57        }
58    }
59
60    /// Obtain a reference to this path's bytestring serialization.
61    pub fn as_bytes(&self) -> &[u8] {
62        self.0.as_ref()
63    }
64
65    /// Obtain a component iterator for this path.
66    pub fn components(&self) -> Components<'_> {
67        Components::new(&self.0)
68    }
69
70    /// Is this path the root path?
71    pub fn is_root(&self) -> bool {
72        self.0.is_empty()
73    }
74
75    /// Create a `PathBuf` with `path` joined to `self`.
76    ///
77    /// Requires the `alloc` feature is enabled.
78    #[cfg(feature = "alloc")]
79    pub fn join<P>(&self, path: P) -> PathBuf
80    where
81        P: AsRef<Path>,
82    {
83        let mut result = PathBuf::new();
84        result.extend(self.components());
85        result.extend(path.as_ref().components());
86        result
87    }
88
89    /// Get the parent path for this path
90    pub fn parent(&self) -> Option<&Path> {
91        let mut tail = None;
92
93        for component in self.components() {
94            tail = Some(component)
95        }
96
97        tail.map(|t| {
98            let tail_len = self.0.len() - t.len() - 1;
99            Path::new(&self.0[..tail_len]).unwrap()
100        })
101    }
102
103    /// Attempt to convert this path to an `/x/y/z` string.
104    ///
105    /// This will only succeed if the path components are all ASCII.
106    ///
107    /// Requires the `alloc` feature is enabled.
108    #[cfg(feature = "alloc")]
109    pub fn stringify(&self) -> Result<String, Error> {
110        let mut result = String::new();
111
112        if self.is_root() {
113            result.push(DELIMITER);
114            return Ok(result);
115        }
116
117        for component in self.components() {
118            result.push(DELIMITER);
119            result.push_str(component.stringify()?.as_ref());
120        }
121
122        Ok(result)
123    }
124
125    /// Serialize this `Path` as a byte vector
126    #[cfg(feature = "alloc")]
127    pub fn to_vec(&self) -> Vec<u8> {
128        self.0.to_vec()
129    }
130
131    /// Internal function for debug printing just the path components
132    #[cfg(feature = "alloc")]
133    pub(crate) fn debug_components(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(f, "(")?;
135
136        if let Ok(s) = self.stringify() {
137            s.fmt(f)?;
138        } else {
139            let component_count = self.components().count();
140            for (i, component) in self.components().enumerate() {
141                write!(f, "{:?}", component)?;
142                if i < component_count - 1 {
143                    write!(f, ", ")?;
144                }
145            }
146        }
147
148        write!(f, ")")
149    }
150}
151
152impl AsRef<Path> for Path {
153    fn as_ref(&self) -> &Self {
154        self
155    }
156}
157
158#[cfg(feature = "alloc")]
159impl Debug for Path {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        write!(f, "hkd32::Path")?;
162        self.debug_components(f)
163    }
164}
165
166/// Component of a derivation path
167#[derive(Copy, Clone, Eq, Hash, PartialEq)]
168#[repr(transparent)]
169pub struct Component<'a>(&'a [u8]);
170
171// Components are not allowed to be empty
172#[allow(clippy::len_without_is_empty)]
173impl<'a> Component<'a> {
174    /// Create a new component.
175    ///
176    /// Returns `Error` if the component is empty or is longer than
177    /// `MAX_COMPONENT_LENGTH`.
178    pub fn new(bytes: &'a [u8]) -> Result<Self, Error> {
179        if !bytes.is_empty() && bytes.len() <= MAX_COMPONENT_LENGTH {
180            Ok(Component(bytes))
181        } else {
182            Err(Error)
183        }
184    }
185
186    /// Borrow the contents of this component as a byte slice.
187    pub fn as_bytes(&self) -> &'a [u8] {
188        self.0
189    }
190
191    /// Get the length of this component in bytes.
192    pub fn len(&self) -> usize {
193        self.0.len()
194    }
195
196    /// Attempt to convert component to an ASCII string.
197    ///
198    /// Requires the `alloc` feature is enabled.
199    #[cfg(feature = "alloc")]
200    pub fn stringify(&self) -> Result<String, Error> {
201        let s = str::from_utf8(self.as_bytes())
202            .map(String::from)
203            .map_err(|_| Error)?;
204
205        if s.is_ascii() {
206            Ok(s)
207        } else {
208            Err(Error)
209        }
210    }
211
212    /// Serialize this component as a length-prefixed bytestring.
213    #[cfg(feature = "alloc")]
214    pub fn to_bytes(self) -> Vec<u8> {
215        let mut serialized = Vec::with_capacity(1 + self.len());
216        serialized.push((self.len() - 1) as u8);
217        serialized.extend_from_slice(self.0);
218        serialized
219    }
220}
221
222impl<'a> AsRef<Component<'a>> for Component<'a> {
223    fn as_ref(&self) -> &Component<'a> {
224        self
225    }
226}
227
228#[cfg(feature = "alloc")]
229impl<'a> Debug for Component<'a> {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        write!(f, "hkd32::Component")?;
232
233        if let Ok(s) = self.stringify() {
234            write!(f, "({:?})", s)
235        } else {
236            write!(f, "({:?})", self.as_bytes())
237        }
238    }
239}
240
241/// Iterator over the components of a path
242#[derive(Clone)]
243pub struct Components<'a> {
244    /// Remaining data in the path
245    path: &'a [u8],
246
247    /// Flag set to false if the path is malformed/truncated
248    valid: bool,
249}
250
251impl<'a> Components<'a> {
252    /// Create a new component iterator from the given byte slice
253    fn new(path: &'a [u8]) -> Self {
254        Self { path, valid: true }
255    }
256
257    /// Iterate over the components until the end, checking that the path
258    /// is valid. This consumes the path in the process, and is only used
259    /// internally by `Path::new` to ensure the path is well-formed.
260    fn validate(mut self) -> bool {
261        while self.next().is_some() {}
262        self.valid
263    }
264}
265
266impl<'a> Iterator for Components<'a> {
267    type Item = Component<'a>;
268
269    fn next(&mut self) -> Option<Component<'a>> {
270        // Read the length prefix for the next component
271        let component_len = *self.path.first()? as usize + 1;
272        self.path = &self.path[1..];
273
274        if self.path.len() < component_len {
275            // If the path appears truncated, mark it internally as such
276            // This is checked in `Path::new` and an error returned to the user
277            self.valid = false;
278            return None;
279        }
280
281        let (component_bytes, remaining) = self.path.split_at(component_len);
282        self.path = remaining;
283
284        if let Ok(component) = Component::new(component_bytes) {
285            Some(component)
286        } else {
287            self.valid = false;
288            None
289        }
290    }
291}
292
293#[cfg(all(test, feature = "alloc"))]
294mod tests {
295    use super::*;
296
297    #[test]
298    fn test_root() {
299        let root_path = Path::new(&[]).unwrap();
300        assert_eq!(root_path.components().count(), 0);
301        assert_eq!(root_path.stringify().unwrap(), "/");
302        assert_eq!(&format!("{:?}", root_path), "hkd32::Path(\"/\")");
303    }
304}