khodpay_bip32/
chain_code.rs

1//! Chain code implementation for BIP32 hierarchical deterministic key derivation.
2//!
3//! Chain codes are 32-byte values that provide additional entropy for key derivation.
4//! They work alongside private or public keys to enable secure hierarchical key generation.
5//!
6//! # What is a Chain Code?
7//!
8//! A chain code is a 32-byte secret value that, combined with a key, allows deriving
9//! child keys. It prevents attackers from deriving parent keys even if they compromise
10//! a child private key and the parent public key.
11//!
12//! # Examples
13//!
14//! ```rust
15//! use khodpay_bip32::ChainCode;
16//!
17//! // Create from bytes
18//! let bytes = [42u8; 32];
19//! let chain_code = ChainCode::from_bytes(&bytes)?;
20//!
21//! // Access the bytes
22//! assert_eq!(chain_code.as_bytes(), &bytes);
23//! # Ok::<(), khodpay_bip32::Error>(())
24//! ```
25
26use crate::{Error, Result};
27use zeroize::ZeroizeOnDrop;
28
29/// A 32-byte chain code used in BIP32 hierarchical deterministic key derivation.
30///
31/// Chain codes provide additional entropy for deriving child keys. Every extended key
32/// (both private and public) has an associated chain code that is used in the
33/// HMAC-SHA512 operation during child key derivation.
34///
35/// # Security
36///
37/// Chain codes must be kept secret, similar to private keys. Exposing a chain code
38/// along with an extended public key can compromise the privacy of all child keys.
39///
40/// **Memory Safety:** This type implements `ZeroizeOnDrop`, which automatically
41/// overwrites the chain code bytes with zeros when the value is dropped, preventing
42/// sensitive data from lingering in memory.
43///
44/// # Size
45///
46/// Chain codes are always exactly 32 bytes (256 bits) in length.
47///
48/// # Examples
49///
50/// ```rust
51/// use khodpay_bip32::ChainCode;
52///
53/// // Create from a 32-byte array
54/// let bytes = [0u8; 32];
55/// let chain_code = ChainCode::from_bytes(&bytes)?;
56///
57/// // Access the underlying bytes
58/// let bytes_ref: &[u8; 32] = chain_code.as_bytes();
59/// assert_eq!(bytes_ref.len(), 32);
60/// # Ok::<(), khodpay_bip32::Error>(())
61/// ```
62#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop)]
63pub struct ChainCode([u8; 32]);
64
65impl ChainCode {
66    /// The length of a chain code in bytes.
67    pub const LENGTH: usize = 32;
68
69    /// Creates a new `ChainCode` from a 32-byte array.
70    ///
71    /// # Arguments
72    ///
73    /// * `bytes` - A 32-byte array containing the chain code data
74    ///
75    /// # Examples
76    ///
77    /// ```rust
78    /// use khodpay_bip32::ChainCode;
79    ///
80    /// let bytes = [0u8; 32];
81    /// let chain_code = ChainCode::new(bytes);
82    /// assert_eq!(chain_code.as_bytes(), &bytes);
83    /// ```
84    pub fn new(bytes: [u8; 32]) -> Self {
85        ChainCode(bytes)
86    }
87
88    /// Creates a `ChainCode` from a byte slice.
89    ///
90    /// # Arguments
91    ///
92    /// * `bytes` - A byte slice that must be exactly 32 bytes long
93    ///
94    /// # Errors
95    ///
96    /// Returns [`Error::InvalidPrivateKey`] if the slice is not exactly 32 bytes.
97    ///
98    /// # Examples
99    ///
100    /// ```rust
101    /// use khodpay_bip32::ChainCode;
102    ///
103    /// // Valid 32-byte slice
104    /// let bytes = vec![0u8; 32];
105    /// let chain_code = ChainCode::from_bytes(&bytes)?;
106    ///
107    /// // Invalid length
108    /// let invalid = vec![0u8; 16];
109    /// assert!(ChainCode::from_bytes(&invalid).is_err());
110    /// # Ok::<(), khodpay_bip32::Error>(())
111    /// ```
112    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
113        if bytes.len() != Self::LENGTH {
114            return Err(Error::InvalidPrivateKey {
115                reason: format!(
116                    "Chain code must be {} bytes, got {}",
117                    Self::LENGTH,
118                    bytes.len()
119                ),
120            });
121        }
122
123        let mut array = [0u8; 32];
124        array.copy_from_slice(bytes);
125        Ok(ChainCode(array))
126    }
127
128    /// Returns a reference to the chain code bytes as a 32-byte array.
129    ///
130    /// # Examples
131    ///
132    /// ```rust
133    /// use khodpay_bip32::ChainCode;
134    ///
135    /// let bytes = [42u8; 32];
136    /// let chain_code = ChainCode::new(bytes);
137    ///
138    /// let bytes_ref: &[u8; 32] = chain_code.as_bytes();
139    /// assert_eq!(bytes_ref, &bytes);
140    /// ```
141    pub fn as_bytes(&self) -> &[u8; 32] {
142        &self.0
143    }
144
145    /// Converts the chain code to a `Vec<u8>`.
146    ///
147    /// # Examples
148    ///
149    /// ```rust
150    /// use khodpay_bip32::ChainCode;
151    ///
152    /// let bytes = [1u8; 32];
153    /// let chain_code = ChainCode::new(bytes);
154    ///
155    /// let vec = chain_code.to_vec();
156    /// assert_eq!(vec.len(), 32);
157    /// assert_eq!(vec, bytes.to_vec());
158    /// ```
159    pub fn to_vec(&self) -> Vec<u8> {
160        self.0.to_vec()
161    }
162
163    /// Returns the length of the chain code (always 32).
164    ///
165    /// # Examples
166    ///
167    /// ```rust
168    /// use khodpay_bip32::ChainCode;
169    ///
170    /// let chain_code = ChainCode::new([0u8; 32]);
171    /// assert_eq!(chain_code.len(), 32);
172    /// ```
173    pub fn len(&self) -> usize {
174        Self::LENGTH
175    }
176
177    /// Always returns `false` since chain codes have a fixed non-zero length.
178    ///
179    /// This method exists for consistency with collection-like types.
180    ///
181    /// # Examples
182    ///
183    /// ```rust
184    /// use khodpay_bip32::ChainCode;
185    ///
186    /// let chain_code = ChainCode::new([0u8; 32]);
187    /// assert!(!chain_code.is_empty());
188    /// ```
189    pub fn is_empty(&self) -> bool {
190        false
191    }
192}
193
194impl std::fmt::Debug for ChainCode {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        write!(f, "ChainCode(")?;
197        for (i, byte) in self.0.iter().enumerate() {
198            if i > 0 {
199                write!(f, " ")?;
200            }
201            write!(f, "{:02x}", byte)?;
202            if i >= 7 {
203                write!(f, "...")?;
204                break;
205            }
206        }
207        write!(f, ")")
208    }
209}
210
211impl AsRef<[u8]> for ChainCode {
212    fn as_ref(&self) -> &[u8] {
213        &self.0
214    }
215}
216
217impl From<[u8; 32]> for ChainCode {
218    fn from(bytes: [u8; 32]) -> Self {
219        ChainCode::new(bytes)
220    }
221}
222
223impl TryFrom<&[u8]> for ChainCode {
224    type Error = Error;
225
226    fn try_from(bytes: &[u8]) -> Result<Self> {
227        ChainCode::from_bytes(bytes)
228    }
229}
230
231impl TryFrom<Vec<u8>> for ChainCode {
232    type Error = Error;
233
234    fn try_from(bytes: Vec<u8>) -> Result<Self> {
235        ChainCode::from_bytes(&bytes)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_chain_code_new() {
245        let bytes = [42u8; 32];
246        let chain_code = ChainCode::new(bytes);
247        assert_eq!(chain_code.as_bytes(), &bytes);
248    }
249
250    #[test]
251    fn test_chain_code_from_bytes_valid() {
252        let bytes = vec![1u8; 32];
253        let chain_code = ChainCode::from_bytes(&bytes).unwrap();
254        assert_eq!(chain_code.as_bytes(), &[1u8; 32]);
255    }
256
257    #[test]
258    fn test_chain_code_from_bytes_too_short() {
259        let bytes = vec![0u8; 16];
260        let result = ChainCode::from_bytes(&bytes);
261        assert!(result.is_err());
262        assert!(result.unwrap_err().to_string().contains("must be 32 bytes"));
263    }
264
265    #[test]
266    fn test_chain_code_from_bytes_too_long() {
267        let bytes = vec![0u8; 64];
268        let result = ChainCode::from_bytes(&bytes);
269        assert!(result.is_err());
270        assert!(result.unwrap_err().to_string().contains("must be 32 bytes"));
271    }
272
273    #[test]
274    fn test_chain_code_from_bytes_empty() {
275        let bytes: Vec<u8> = vec![];
276        let result = ChainCode::from_bytes(&bytes);
277        assert!(result.is_err());
278    }
279
280    #[test]
281    fn test_chain_code_as_bytes() {
282        let bytes = [123u8; 32];
283        let chain_code = ChainCode::new(bytes);
284        let result = chain_code.as_bytes();
285        assert_eq!(result, &bytes);
286        assert_eq!(result.len(), 32);
287    }
288
289    #[test]
290    fn test_chain_code_to_vec() {
291        let bytes = [42u8; 32];
292        let chain_code = ChainCode::new(bytes);
293        let vec = chain_code.to_vec();
294        assert_eq!(vec.len(), 32);
295        assert_eq!(vec, bytes.to_vec());
296    }
297
298    #[test]
299    fn test_chain_code_len() {
300        let chain_code = ChainCode::new([0u8; 32]);
301        assert_eq!(chain_code.len(), 32);
302        assert_eq!(chain_code.len(), ChainCode::LENGTH);
303    }
304
305    #[test]
306    fn test_chain_code_is_empty() {
307        let chain_code = ChainCode::new([0u8; 32]);
308        assert!(!chain_code.is_empty());
309    }
310
311    #[test]
312    fn test_chain_code_clone() {
313        let bytes = [99u8; 32];
314        let chain_code1 = ChainCode::new(bytes);
315        let chain_code2 = chain_code1.clone();
316        assert_eq!(chain_code1, chain_code2);
317        assert_eq!(chain_code1.as_bytes(), chain_code2.as_bytes());
318    }
319
320    #[test]
321    fn test_chain_code_equality() {
322        let chain_code1 = ChainCode::new([1u8; 32]);
323        let chain_code2 = ChainCode::new([1u8; 32]);
324        let chain_code3 = ChainCode::new([2u8; 32]);
325
326        assert_eq!(chain_code1, chain_code2);
327        assert_ne!(chain_code1, chain_code3);
328        assert_ne!(chain_code2, chain_code3);
329    }
330
331    #[test]
332    fn test_chain_code_debug() {
333        let mut bytes = [0u8; 32];
334        bytes[0] = 0xAB;
335        bytes[1] = 0xCD;
336        bytes[2] = 0xEF;
337        bytes[3] = 0x01;
338
339        let chain_code = ChainCode::new(bytes);
340        let debug_str = format!("{:?}", chain_code);
341
342        assert!(debug_str.contains("ChainCode"));
343        assert!(debug_str.contains("ab"));
344        assert!(debug_str.contains("cd"));
345        assert!(debug_str.contains("..."));
346    }
347
348    #[test]
349    fn test_chain_code_as_ref() {
350        let bytes = [77u8; 32];
351        let chain_code = ChainCode::new(bytes);
352        let slice: &[u8] = chain_code.as_ref();
353        assert_eq!(slice, &bytes);
354        assert_eq!(slice.len(), 32);
355    }
356
357    #[test]
358    fn test_chain_code_from_array() {
359        let bytes = [55u8; 32];
360        let chain_code: ChainCode = bytes.into();
361        assert_eq!(chain_code.as_bytes(), &bytes);
362    }
363
364    #[test]
365    fn test_chain_code_try_from_slice_valid() {
366        let bytes: &[u8] = &[88u8; 32];
367        let chain_code = ChainCode::try_from(bytes).unwrap();
368        assert_eq!(chain_code.as_bytes(), &[88u8; 32]);
369    }
370
371    #[test]
372    fn test_chain_code_try_from_slice_invalid() {
373        let bytes: &[u8] = &[0u8; 10];
374        let result = ChainCode::try_from(bytes);
375        assert!(result.is_err());
376    }
377
378    #[test]
379    fn test_chain_code_try_from_vec_valid() {
380        let bytes = vec![66u8; 32];
381        let chain_code = ChainCode::try_from(bytes).unwrap();
382        assert_eq!(chain_code.as_bytes(), &[66u8; 32]);
383    }
384
385    #[test]
386    fn test_chain_code_try_from_vec_invalid() {
387        let bytes = vec![0u8; 20];
388        let _result = ChainCode::try_from(bytes);
389    }
390
391    #[test]
392    fn test_chain_code_different_values() {
393        let code1 = ChainCode::new([1u8; 32]);
394        let code2 = ChainCode::new([2u8; 32]);
395
396        assert_ne!(code1, code2);
397    }
398
399    #[test]
400    fn test_chain_code_drop_zeroizes() {
401        // Create a ChainCode with recognizable pattern
402        let sensitive_data = [0x42u8; 32];
403        let chain_code = ChainCode::new(sensitive_data);
404
405        // Get a raw pointer to the data location
406        let ptr = chain_code.as_bytes().as_ptr();
407
408        // Drop the chain code explicitly
409        drop(chain_code);
410
411        // After drop, the memory should be zeroized by ZeroizeOnDrop
412        // Note: This test demonstrates the drop happens, but we can't
413        // safely read the memory after drop in safe Rust.
414        // The ZeroizeOnDrop derive macro guarantees zeroization.
415
416        // This test mainly serves as documentation that ChainCode
417        // implements ZeroizeOnDrop and will be zeroized on drop.
418        assert!(ptr as usize > 0); // Pointer was valid
419    }
420
421    #[test]
422    fn test_chain_code_scope_drop() {
423        // Test that ChainCode is dropped when going out of scope
424        let outer_value = {
425            let chain_code = ChainCode::new([0xFFu8; 32]);
426            chain_code.as_bytes()[0] // Access before drop
427        };
428
429        assert_eq!(outer_value, 0xFF);
430        // chain_code is dropped here, memory should be zeroized
431    }
432
433    #[test]
434    fn test_chain_code_clone_independence() {
435        // Test that cloning creates independent instances
436        let original = ChainCode::new([0xAAu8; 32]);
437        let cloned = original.clone();
438
439        // Both should be equal
440        assert_eq!(original, cloned);
441
442        // Drop one - the other should still be valid
443        drop(original);
444        assert_eq!(cloned.as_bytes()[0], 0xAA);
445    }
446}