1use alloc::vec::Vec;
6use k256::{ProjectivePoint, Scalar};
7use sha2::{Digest, Sha512};
8
9use crate::{
10 curve::{decode_scalar, encode_point},
11 error::{Error, Result},
12};
13
14pub const MAX_PATH_DEPTH: usize = 16;
16
17const HARDENED: u32 = 1 << 31;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HdPath {
22 chain_code: [u8; 32],
23 path: Vec<u32>,
24}
25
26pub fn derive_child_public_key(
28 x_pub: &[u8],
29 chain_code: [u8; 32],
30 path: Vec<u32>,
31) -> Result<[u8; 33]> {
32 let x = crate::curve::decode_point(x_pub)?;
33 let delta = HdPath::new(chain_code, path)?.delta(&x)?;
34 Ok(encode_point(&(x + ProjectivePoint::GENERATOR * delta)))
35}
36
37impl HdPath {
38 pub fn new(chain_code: [u8; 32], path: Vec<u32>) -> Result<Self> {
40 if path.is_empty() || path.len() > MAX_PATH_DEPTH || path.iter().any(|i| i & HARDENED != 0)
41 {
42 return Err(Error::OutOfRange);
43 }
44 Ok(Self { chain_code, path })
45 }
46
47 pub fn chain_code(&self) -> &[u8; 32] {
48 &self.chain_code
49 }
50
51 pub fn path(&self) -> &[u32] {
52 &self.path
53 }
54
55 pub fn delta(&self, x_pub: &ProjectivePoint) -> Result<Scalar> {
57 let mut chain = self.chain_code;
58 let mut parent = *x_pub;
59 let mut delta = Scalar::ZERO;
60 for index in &self.path {
61 let mut data = [0u8; 37];
62 data[..33].copy_from_slice(&encode_point(&parent));
63 data[33..].copy_from_slice(&index.to_be_bytes());
64 let i = hmac_sha512(&chain, &data);
65 let tweak = decode_scalar(&i[..32])?; let child = parent + ProjectivePoint::GENERATOR * tweak;
67 if child == ProjectivePoint::IDENTITY {
68 return Err(Error::OutOfRange);
69 }
70 delta += tweak;
71 chain.copy_from_slice(&i[32..]);
72 parent = child;
73 }
74 Ok(delta)
75 }
76}
77
78fn hmac_sha512(key: &[u8; 32], data: &[u8]) -> [u8; 64] {
79 let mut ipad = [0x36u8; 128];
80 let mut opad = [0x5cu8; 128];
81 for (i, k) in key.iter().enumerate() {
82 ipad[i] ^= k;
83 opad[i] ^= k;
84 }
85 let mut inner = Sha512::new();
86 inner.update(ipad);
87 inner.update(data);
88 let inner = inner.finalize();
89 let mut outer = Sha512::new();
90 outer.update(opad);
91 outer.update(inner);
92 outer.finalize().into()
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::curve::decode_point;
99
100 fn hex(s: &str) -> Vec<u8> {
101 (0..s.len())
102 .step_by(2)
103 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
104 .collect()
105 }
106
107 #[test]
108 fn child_public_key_matches_delta() {
109 let x = ProjectivePoint::GENERATOR * Scalar::from(5u64);
110 let chain = [3u8; 32];
111 let child = derive_child_public_key(&encode_point(&x), chain, alloc::vec![7, 9]).unwrap();
112 let delta = HdPath::new(chain, alloc::vec![7, 9])
113 .unwrap()
114 .delta(&x)
115 .unwrap();
116 assert_eq!(
117 child,
118 encode_point(&(x + ProjectivePoint::GENERATOR * delta))
119 );
120 assert!(derive_child_public_key(&encode_point(&x), chain, alloc::vec![1 << 31]).is_err());
121 assert!(derive_child_public_key(&[2u8; 5], chain, alloc::vec![1]).is_err());
122 }
123
124 #[test]
126 fn bip32_vector_2_first_level() {
127 let master = decode_point(&hex(
128 "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
129 ))
130 .unwrap();
131 let chain: [u8; 32] =
132 hex("60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689")
133 .try_into()
134 .unwrap();
135 let hd = HdPath::new(chain, alloc::vec![0]).unwrap();
136 let child = master + ProjectivePoint::GENERATOR * hd.delta(&master).unwrap();
137 assert_eq!(
138 encode_point(&child).to_vec(),
139 hex("02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea")
140 );
141 }
142
143 #[test]
144 fn rejects_hardened_empty_and_deep_paths() {
145 assert!(HdPath::new([0; 32], alloc::vec![]).is_err());
146 assert!(HdPath::new([0; 32], alloc::vec![HARDENED]).is_err());
147 assert!(HdPath::new([0; 32], alloc::vec![1, HARDENED + 5]).is_err());
148 assert!(HdPath::new([0; 32], alloc::vec![0; MAX_PATH_DEPTH + 1]).is_err());
149 assert!(HdPath::new([0; 32], alloc::vec![44, 0, 0, 0, 7]).is_ok());
150 }
151
152 #[test]
153 fn path_order_and_chain_code_matter() {
154 let x = ProjectivePoint::GENERATOR * Scalar::from(12345u64);
155 let a = HdPath::new([1; 32], alloc::vec![1, 2])
156 .unwrap()
157 .delta(&x)
158 .unwrap();
159 let b = HdPath::new([1; 32], alloc::vec![2, 1])
160 .unwrap()
161 .delta(&x)
162 .unwrap();
163 let c = HdPath::new([2; 32], alloc::vec![1, 2])
164 .unwrap()
165 .delta(&x)
166 .unwrap();
167 assert!(a != b && a != c);
168 }
169}