1use arrayvec::ArrayVec;
2use hmac::{Hmac, Mac};
3use iris_ztd::crypto::cheetah::{ch_add, ch_scal_big, A_GEN, G_ORDER};
4use iris_ztd::U256;
5use sha2::Sha512;
6
7use crate::cheetah::{PrivateKey, PublicKey};
8
9fn hmac_sha512(key: &[u8], data: &[u8]) -> [u8; 64] {
10 let mut mac = Hmac::<Sha512>::new_from_slice(key).unwrap();
11 mac.update(data);
12 mac.finalize().into_bytes().into()
13}
14
15#[derive(Debug, Clone)]
17pub struct ExtendedKey {
18 pub private_key: Option<PrivateKey>,
19 pub public_key: PublicKey,
20 pub chain_code: [u8; 32],
21}
22
23impl ExtendedKey {
24 pub fn derive_child(&self, index: u32) -> ExtendedKey {
26 let hardened = index >= (1 << 31);
27
28 let mut data = ArrayVec::<_, { 1 + 96 + 4 }>::new();
29 if hardened {
30 let private_key = self
31 .private_key
32 .as_ref()
33 .expect("Cannot derive hardened child without private key");
34 data.push(0x00);
35 data.try_extend_from_slice(&private_key.to_be_bytes())
36 .unwrap();
37 data.try_extend_from_slice(&index.to_be_bytes()).unwrap();
38 } else {
39 data.push(0x01);
40 data.try_extend_from_slice(&self.public_key.as_slip10_bytes())
41 .unwrap();
42 data.try_extend_from_slice(&index.to_be_bytes()).unwrap();
43 }
44 let mut result = hmac_sha512(&self.chain_code, &data);
45
46 loop {
47 let left = U256::from_be_slice(&result[..32]);
48 let mut chain_code = [0u8; 32];
49 chain_code.copy_from_slice(&result[32..]);
50
51 if left < G_ORDER {
52 match self.private_key.as_ref() {
53 Some(pk) => {
54 let s = left.add_mod(&pk.0, &G_ORDER);
55 if s != U256::ZERO {
56 let private_key = PrivateKey(s);
57 let public_key = private_key.public_key();
58 return ExtendedKey {
59 private_key: Some(private_key),
60 public_key,
61 chain_code,
62 };
63 }
64 }
65 None => {
66 let mut point = ch_scal_big(&left, &A_GEN).unwrap();
67 point = ch_add(&point, &self.public_key.0).unwrap();
68 if !point.inf {
69 return ExtendedKey {
70 private_key: None,
71 public_key: PublicKey(point),
72 chain_code,
73 };
74 }
75 }
76 }
77 }
78 let mut data = ArrayVec::<_, { 1 + 32 + 4 }>::new();
80 data.push(0x01);
81 data.try_extend_from_slice(&chain_code).unwrap();
82 data.try_extend_from_slice(&index.to_be_bytes()).unwrap();
83 result = hmac_sha512(&self.chain_code, &data);
84 }
85 }
86}
87
88pub fn derive_master_key(seed: &[u8]) -> ExtendedKey {
89 const DOMAIN_SEPARATOR: &[u8] = b"Nockchain seed";
90 let mut result = hmac_sha512(DOMAIN_SEPARATOR, seed);
91 loop {
92 let s = U256::from_be_slice(&result[..32]);
93 let mut chain_code = [0u8; 32];
94 chain_code.copy_from_slice(&result[32..]);
95 if s < G_ORDER && s != U256::ZERO {
96 let private_key = PrivateKey(s);
97 let public_key = private_key.public_key();
98 return ExtendedKey {
99 private_key: Some(private_key),
100 public_key,
101 chain_code,
102 };
103 }
104 result = hmac_sha512(DOMAIN_SEPARATOR, &result[..]);
105 }
106}
107
108#[cfg(feature = "alloc")]
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use alloc::vec::Vec;
113 use bip39::Mnemonic;
114 use iris_ztd::{Belt, Hashable, NounEncode};
115
116 #[cfg(feature = "alloc")]
117 fn from_b58(s: &str) -> Vec<u8> {
118 bs58::decode(s).into_vec().unwrap()
119 }
120
121 #[test]
122 fn test_nockchain_wallet_vector() {
123 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();
129 let key = derive_master_key(&mnemonic.to_seed(""));
130 assert_eq!(
131 key.private_key.as_ref().unwrap().to_be_bytes().to_vec(),
132 from_b58("3MoHxVXWAr9qny12Sw8ZZtrgEBFcZegQQVkwYyePb9LZ")
133 );
134 assert_eq!(
135 key.chain_code[..],
136 from_b58("3NhBRdy7vRw8vKQ5RnR3CNcD43WDn5Ky7mhhotqUcaiR")
137 );
138
139 let child_key = key.derive_child(0);
140 assert_eq!(
141 child_key.private_key.unwrap().to_be_bytes().to_vec(),
142 from_b58("6AifHLAuT1MxnFsoCwjKNFaBze91DXFDV1rRLefkzPEK")
143 );
144 assert_eq!(
145 child_key.chain_code[..],
146 from_b58("8NL75o1uwMpGFcLRrnFt9adTyExwK9MP6RL8h2jAKEVD")
147 );
148
149 let hardened_child_key = key.derive_child(1 << 31);
150 assert_eq!(
151 hardened_child_key
152 .private_key
153 .unwrap()
154 .to_be_bytes()
155 .to_vec(),
156 from_b58("CpMAmcgN1V6Majtx2HC7ULLXD9psA3Gg3nMye3JpKpH")
157 );
158 assert_eq!(
159 hardened_child_key.chain_code[..],
160 from_b58("8x7zh5LQA7tsFQQ3qsPfYGgFzQkoizGhLqLK7iKTGj3R")
161 );
162 }
163
164 #[test]
165 fn test_nockchain_message_vector() {
166 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();
168 let sig = &derive_master_key(&mnemonic.to_seed(""))
169 .private_key
170 .unwrap()
171 .sign(&Belt::from_bytes(b"hello").to_noun().hash());
172 assert_eq!(
173 sig.to_noun().hash().to_bytes().to_vec(),
174 from_b58("AJMwSLmz1k9YnDa1iQTVbpz4Jr4hZojxeCHiqLY7TnjYLxXjZtmbskw")
175 );
176 }
177}