1use hmac::{Hmac, Mac};
2use ibig::UBig;
3use iris_ztd::crypto::cheetah::{ch_add, ch_scal_big, A_GEN, G_ORDER};
4use sha2::Sha512;
5
6use crate::cheetah::{PrivateKey, PublicKey};
7
8fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] {
9 let mut mac = Hmac::<Sha512>::new_from_slice(key).unwrap();
10 mac.update(data);
11 mac.finalize().into_bytes().into()
12}
13
14#[derive(Debug, Clone)]
16pub struct ExtendedKey {
17 pub private_key: Option<PrivateKey>,
18 pub public_key: PublicKey,
19 pub chain_code: [u8; 32],
20}
21
22impl ExtendedKey {
23 pub fn derive_child(&self, index: u32) -> ExtendedKey {
25 let hardened = index >= (1 << 31);
26
27 let mut data = Vec::new();
28 if hardened {
29 let private_key = self
30 .private_key
31 .as_ref()
32 .expect("Cannot derive hardened child without private key");
33 data.push(0x00);
34 data.extend_from_slice(&private_key.to_be_bytes());
35 data.extend_from_slice(&index.to_be_bytes());
36 } else {
37 data.push(0x01);
38 data.extend_from_slice(&self.public_key.to_slip10_bytes());
39 data.extend_from_slice(&index.to_be_bytes());
40 }
41 let mut result = hmac_sha512(&self.chain_code, &data);
42
43 loop {
44 let left = UBig::from_be_bytes(&result[..32]);
45 let mut chain_code = [0u8; 32];
46 chain_code.copy_from_slice(&result[32..]);
47
48 if left < *G_ORDER {
49 match self.private_key.as_ref() {
50 Some(pk) => {
51 let s = (&left + &pk.0) % &*G_ORDER;
52 if s != UBig::from(0u64) {
53 let private_key = PrivateKey(s);
54 let public_key = private_key.public_key();
55 return ExtendedKey {
56 private_key: Some(private_key),
57 public_key,
58 chain_code,
59 };
60 }
61 }
62 None => {
63 let mut point = ch_scal_big(&left, &A_GEN).unwrap();
64 point = ch_add(&point, &self.public_key.0).unwrap();
65 if !point.inf {
66 return ExtendedKey {
67 private_key: None,
68 public_key: PublicKey(point),
69 chain_code,
70 };
71 }
72 }
73 }
74 }
75 let mut data = Vec::new();
77 data.push(0x01);
78 data.extend_from_slice(&chain_code);
79 data.extend_from_slice(&index.to_be_bytes());
80 result = hmac_sha512(&self.chain_code, &data);
81 }
82 }
83}
84
85pub fn derive_master_key(seed: &[u8]) -> ExtendedKey {
86 const DOMAIN_SEPARATOR: &[u8] = b"Nockchain seed";
87 let mut result = hmac_sha512(DOMAIN_SEPARATOR, seed);
88 loop {
89 let s = UBig::from_be_bytes(&result[..32]);
90 let mut chain_code = [0u8; 32];
91 chain_code.copy_from_slice(&result[32..]);
92 if s < *G_ORDER && s != UBig::from(0u64) {
93 let private_key = PrivateKey(s);
94 let public_key = private_key.public_key();
95 return ExtendedKey {
96 private_key: Some(private_key),
97 public_key,
98 chain_code,
99 };
100 }
101 result = hmac_sha512(DOMAIN_SEPARATOR, &result[..]);
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use bip39::Mnemonic;
109 use iris_ztd::{Belt, Hashable, NounEncode};
110
111 fn from_b58(s: &str) -> Vec<u8> {
112 bs58::decode(s).into_vec().unwrap()
113 }
114
115 #[test]
116 fn test_nockchain_wallet_vector() {
117 let mnemonic = Mnemonic::parse("clutch inmate mango seek attract credit illegal popular term loyal fiber output trumpet lucky garbage merge menu certain dynamic aim trip fantasy master unveil").unwrap();
123 let key = derive_master_key(&mnemonic.to_seed(""));
124 assert_eq!(
125 key.private_key.as_ref().unwrap().to_be_bytes().to_vec(),
126 from_b58("3MoHxVXWAr9qny12Sw8ZZtrgEBFcZegQQVkwYyePb9LZ")
127 );
128 assert_eq!(
129 key.chain_code[..],
130 from_b58("3NhBRdy7vRw8vKQ5RnR3CNcD43WDn5Ky7mhhotqUcaiR")
131 );
132
133 let child_key = key.derive_child(0);
134 assert_eq!(
135 child_key.private_key.unwrap().to_be_bytes().to_vec(),
136 from_b58("6AifHLAuT1MxnFsoCwjKNFaBze91DXFDV1rRLefkzPEK")
137 );
138 assert_eq!(
139 child_key.chain_code[..],
140 from_b58("8NL75o1uwMpGFcLRrnFt9adTyExwK9MP6RL8h2jAKEVD")
141 );
142
143 let hardened_child_key = key.derive_child(1 << 31);
144 assert_eq!(
145 hardened_child_key
146 .private_key
147 .unwrap()
148 .to_be_bytes()
149 .to_vec(),
150 from_b58("CpMAmcgN1V6Majtx2HC7ULLXD9psA3Gg3nMye3JpKpH")
151 );
152 assert_eq!(
153 hardened_child_key.chain_code[..],
154 from_b58("8x7zh5LQA7tsFQQ3qsPfYGgFzQkoizGhLqLK7iKTGj3R")
155 );
156 }
157
158 #[test]
159 fn test_nockchain_message_vector() {
160 let mnemonic = Mnemonic::parse("kangaroo gap pair wonder grid version winter burden garment resemble object trap survey custom mask fiber anger hospital conduct draft page hello embark core").unwrap();
162 let sig = &derive_master_key(&mnemonic.to_seed(""))
163 .private_key
164 .unwrap()
165 .sign(&Belt::from_bytes(b"hello").to_noun().hash());
166 assert_eq!(
167 sig.to_noun().hash().to_bytes().to_vec(),
168 from_b58("AJMwSLmz1k9YnDa1iQTVbpz4Jr4hZojxeCHiqLY7TnjYLxXjZtmbskw")
169 );
170 }
171}