cpchain_rust_sdk/
hd.rs

1use bip39::{Language, Mnemonic, Seed, MnemonicType};
2use hex_literal::hex;
3use hmac::{Hmac, Mac};
4use num_bigint::BigUint;
5use regex::Regex;
6use secp256k1::{PublicKey, Secp256k1, SecretKey};
7use sha2::Sha512;
8
9// "Bitcoin seed"
10const MASTER_SECRET: &str = "Bitcoin seed";
11
12/// HMAC: Keyed-Hashing for Message Authentication
13type HmacSha512 = Hmac<Sha512>;
14
15// 正常衍生的索引号范围为 [0x0, 0x7FFFFFFF],而硬化衍生的索引号范围为 [0x80000000, 0xFFFFFFFF]
16const HARDENED_BIT: u64 = 0x80000000;
17
18/// Deterministic Wallet
19/// BIP39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
20/// https://stevenocean.github.io/2018/09/23/generate-hd-wallet-by-bip39.html
21#[derive(Debug, Clone)]
22pub struct HDNode {
23    pub private_key: Option<[u8; 32]>,
24    pub public_key: [u8; 33], // beginning with 0x02 or 0x03 to denote the sign of the missing Y component.
25
26    pub mnemonic: Option<Mnemonic>,
27    pub path: String,
28
29    chain_code: [u8; 32],
30
31    pub index: u64,
32    pub depth: u32,
33}
34
35impl HDNode {
36    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
37        // 创建助记词
38        let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English);
39        HDNode::from_mnemonic(&mnemonic)
40    }
41
42    pub fn from_mnemonic(mnemonic: &Mnemonic) -> Result<HDNode, Box<dyn std::error::Error>> {
43        let seed = Seed::new_cpc(&mnemonic, "");
44        let bytes = compute_hmac(MASTER_SECRET.as_bytes(), &seed.as_bytes())?;
45        let mut master_private_key: [u8; 32] = [0; 32];
46        bytes[0..32]
47            .to_vec()
48            .iter()
49            .enumerate()
50            .for_each(|(index, elem)| {
51                master_private_key[index] = elem.clone();
52            });
53        let secp = Secp256k1::new();
54        // Get public key
55        let secret_key = SecretKey::from_slice(&master_private_key)?;
56        let public_key = PublicKey::from_secret_key(&secp, &secret_key);
57        let public_key: [u8; 33] = public_key.serialize();
58
59        let mut master_chain_code: [u8; 32] = [0; 32];
60        bytes[32..64]
61            .to_vec()
62            .iter()
63            .enumerate()
64            .for_each(|(index, elem)| {
65                master_chain_code[index] = elem.clone();
66            });
67        Ok(HDNode::new_hdnode(
68            Some(master_private_key),
69            public_key,
70            master_chain_code,
71            0,
72            0,
73            "m".to_string(),
74            Some(mnemonic.clone()),
75        ))
76    }
77
78    pub fn from_phrase(phrase: &str) -> Result<HDNode, Box<dyn std::error::Error>> {
79        // Create seed
80        let mnemonic = Mnemonic::from_phrase(phrase, Language::English)?;
81        HDNode::from_mnemonic(&mnemonic)
82    }
83
84    fn new_hdnode(
85        private_key: Option<[u8; 32]>,
86        public_key: [u8; 33],
87        chain_code: [u8; 32],
88        index: u64,
89        depth: u32,
90        path: String,
91        mnemonic: Option<Mnemonic>,
92    ) -> Self {
93        Self {
94            private_key,
95            public_key,
96            chain_code,
97            index,
98            depth,
99            mnemonic,
100            path,
101        }
102    }
103    fn _derive(&self, index: u64) -> Result<HDNode, Box<dyn std::error::Error>> {
104        if index > 0xffffffff {
105            return Err(format!("invalid index - {}", index).into());
106        }
107
108        // Base path
109        let mut path = self.path.clone();
110        if path.len() > 0 {
111            path += &format!("/{}", (index & !HARDENED_BIT))
112        }
113
114        let mut data: [u8; 37] = [0; 37];
115
116        if (index & HARDENED_BIT) > 0 {
117            match &self.private_key {
118                Some(pk) => {
119                    // Data = 0x00 || ser_256(k_par)
120                    pk.iter().enumerate().for_each(|(index, e)| {
121                        data[index + 1] = e.clone();
122                    });
123                    // Hardened path
124                    if path.len() > 0 {
125                        path += "'";
126                    }
127                }
128                None => return Err("cannot derive child of neutered node".into()),
129            }
130        } else {
131            // Data = ser_p(point(k_par))
132            self.public_key.iter().enumerate().for_each(|(index, e)| {
133                data[index] = *e;
134            });
135        }
136
137        // Data += ser_32(i)
138        let mut i: i32 = 24;
139        while i >= 0 {
140            data[(33 + (i >> 3)) as usize] = ((index >> (24 - i)) & 0xff) as u8;
141            i -= 8;
142        }
143
144        let hmac_result = compute_hmac(&self.chain_code, &data)?;
145        let hmac_result_left: &[u8; 32] = &hmac_result[..32].try_into().unwrap();
146        let child_chain_code: &[u8; 32] = &hmac_result[32..].try_into().unwrap();
147
148        let secp = Secp256k1::new();
149
150        // 私钥
151        let (private_key, public_key): ([u8; 32], [u8; 33]) = match self.private_key {
152            Some(pk) => {
153                let bn1 = BigUint::from_bytes_be(&pk);
154                let bn2 = BigUint::from_bytes_be(hmac_result_left);
155                let r = (bn1 + bn2)
156                    % BigUint::from_bytes_be(&hex!(
157                        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
158                    ));
159                let mut ki: [u8; 32] = [0; 32];
160                r.to_bytes_be()[..32]
161                    .iter()
162                    .enumerate()
163                    .for_each(|(index, e)| {
164                        ki[index] = e.clone();
165                    });
166                let secret_key = SecretKey::from_slice(&ki)?;
167                let public_key = PublicKey::from_secret_key(&secp, &secret_key);
168                let public_key: [u8; 33] = public_key.serialize();
169                (ki, public_key)
170            }
171            None => {
172                todo!()
173            }
174        };
175
176        let node = HDNode::new_hdnode(
177            Some(private_key),
178            public_key,
179            child_chain_code.clone(),
180            index,
181            self.depth + 1,
182            path,
183            self.mnemonic.clone(),
184        );
185        Ok(node)
186    }
187
188    pub fn derive_path(&self, path: &str) -> Result<HDNode, Box<dyn std::error::Error>> {
189        let mut components = path.split("/").collect::<Vec<_>>();
190        if components.len() == 0 || path == "" || (components[0] == "m" && self.depth != 0) {
191            return Err(format!("invalid path - {}", path).into());
192        }
193
194        if components[0] == "m" {
195            components = components[1..components.len()].to_vec();
196        }
197        let r1 = Regex::new(r"^\d+'$")?;
198        let r2 = Regex::new(r"^\d+$")?;
199        let mut node = self.clone();
200        for elem in components.iter() {
201            if r1.is_match(elem) {
202                let index = elem[..elem.len() - 1].to_string().parse::<u64>()?;
203                if index > HARDENED_BIT {
204                    return Err(format!("invalid path index - {}", index).into());
205                }
206                node = node._derive(HARDENED_BIT + index)?;
207            } else if r2.is_match(elem) {
208                let index = elem.to_string().parse::<u64>()?;
209                if index > HARDENED_BIT {
210                    return Err(format!("invalid path index - {}", index).into());
211                }
212                node = node._derive(index)?;
213            }
214        }
215        Ok(node)
216    }
217}
218
219fn compute_hmac(key: &[u8], message: &[u8]) -> Result<[u8; 64], Box<dyn std::error::Error>> {
220    let mut mac = HmacSha512::new_from_slice(key)?;
221    mac.update(message);
222    let result = mac.finalize();
223    let code_bytes: [u8; 64] = result.into_bytes().into();
224    Ok(code_bytes)
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use hex_literal::hex;
231
232    #[test]
233    fn test_hd() {
234        let a = HDNode::new().unwrap();
235        let node = a.derive_path("m/44'/337'/0'/0/0").unwrap();
236        let pk = hex::encode(&node.private_key.unwrap());
237        println!("{}", node.mnemonic.unwrap().phrase());
238        println!("{}", pk);
239    }
240
241    #[test]
242    fn test_from_phrase() {
243        let node = HDNode::from_phrase("office picture sausage either disease ordinary comic loan unknown entire winner twice").unwrap().derive_path("m/44'/337'/0'/0/0").unwrap();
244        let expected = hex!("31e880225ba9103ed82b0d404518e303210d7fc0ecb6091be440112d59cda185");
245        assert_eq!(node.private_key.unwrap(), expected)
246    }
247
248    #[test]
249    fn test_hmac() {
250        // hmac online: https://1024tools.com/hmac
251        let mut mac = HmacSha512::new_from_slice(b"my secret and secure key")
252            .expect("HMAC can take key of any size");
253        mac.update(b"input message");
254        let result = mac.finalize();
255        // To get underlying array use `into_bytes`, but be careful, since
256        // incorrect use of the code value may permit timing attacks which defeats
257        // the security provided by the `CtOutput`
258        let code_bytes = result.into_bytes();
259        let expected = hex!(
260            "
261            e51c913d44379e50c69201a5d95fb43ec0d
262            c5b1736cd6f2214b506e64bd35c9dc0214c
263            900f62be4b61d507a60299b6bb1625e5e36
264            5a9aa4ed1089b0262fb99a5
265        "
266        );
267        assert_eq!(code_bytes[..], expected[..]);
268    }
269
270    #[test]
271    fn test_compute_hmac() {
272        let code_bytes = compute_hmac(b"my secret and secure key", b"input message").unwrap();
273        let expected = hex!(
274            "
275            e51c913d44379e50c69201a5d95fb43ec0d
276            c5b1736cd6f2214b506e64bd35c9dc0214c
277            900f62be4b61d507a60299b6bb1625e5e36
278            5a9aa4ed1089b0262fb99a5
279        "
280        );
281        assert_eq!(code_bytes[..], expected[..]);
282    }
283}