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}