1use aztec_core::constants::domain_separator;
8use aztec_core::grumpkin;
9use aztec_core::hash::poseidon2_hash_with_separator;
10use aztec_core::types::{AztecAddress, Fr, GrumpkinScalar, Point, PublicKeys};
11
12use crate::sha512::sha512_to_grumpkin_scalar;
13
14pub fn derive_master_nullifier_hiding_key(secret_key: &Fr) -> GrumpkinScalar {
20 sha512_to_grumpkin_scalar(secret_key, domain_separator::NHK_M)
21}
22
23pub fn derive_master_incoming_viewing_secret_key(secret_key: &Fr) -> GrumpkinScalar {
25 sha512_to_grumpkin_scalar(secret_key, domain_separator::IVSK_M)
26}
27
28pub fn derive_master_outgoing_viewing_secret_key(secret_key: &Fr) -> GrumpkinScalar {
30 sha512_to_grumpkin_scalar(secret_key, domain_separator::OVSK_M)
31}
32
33pub fn derive_master_tagging_secret_key(secret_key: &Fr) -> GrumpkinScalar {
35 sha512_to_grumpkin_scalar(secret_key, domain_separator::TSK_M)
36}
37
38pub fn derive_signing_key(secret_key: &Fr) -> GrumpkinScalar {
42 sha512_to_grumpkin_scalar(secret_key, domain_separator::IVSK_M)
43}
44
45pub fn derive_public_key_from_secret_key(secret_key: &GrumpkinScalar) -> Point {
53 let g = grumpkin::generator();
54 grumpkin::scalar_mul(secret_key, &g)
55}
56
57pub struct DerivedKeys {
63 pub master_nullifier_hiding_key: GrumpkinScalar,
65 pub master_incoming_viewing_secret_key: GrumpkinScalar,
67 pub master_outgoing_viewing_secret_key: GrumpkinScalar,
69 pub master_tagging_secret_key: GrumpkinScalar,
71 pub public_keys: PublicKeys,
73}
74
75pub fn derive_keys(secret_key: &Fr) -> DerivedKeys {
77 let nhk_m = derive_master_nullifier_hiding_key(secret_key);
79 let ivsk_m = derive_master_incoming_viewing_secret_key(secret_key);
80 let ovsk_m = derive_master_outgoing_viewing_secret_key(secret_key);
81 let tsk_m = derive_master_tagging_secret_key(secret_key);
82
83 let npk_m = derive_public_key_from_secret_key(&nhk_m);
85 let ivpk_m = derive_public_key_from_secret_key(&ivsk_m);
86 let ovpk_m = derive_public_key_from_secret_key(&ovsk_m);
87 let tpk_m = derive_public_key_from_secret_key(&tsk_m);
88
89 DerivedKeys {
90 master_nullifier_hiding_key: nhk_m,
91 master_incoming_viewing_secret_key: ivsk_m,
92 master_outgoing_viewing_secret_key: ovsk_m,
93 master_tagging_secret_key: tsk_m,
94 public_keys: PublicKeys {
95 master_nullifier_public_key: npk_m,
96 master_incoming_viewing_public_key: ivpk_m,
97 master_outgoing_viewing_public_key: ovpk_m,
98 master_tagging_public_key: tpk_m,
99 },
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109pub enum KeyType {
110 Nullifier,
111 IncomingViewing,
112 OutgoingViewing,
113 Tagging,
114}
115
116impl KeyType {
117 pub fn domain_separator(&self) -> u32 {
118 match self {
119 KeyType::Nullifier => domain_separator::NHK_M,
120 KeyType::IncomingViewing => domain_separator::IVSK_M,
121 KeyType::OutgoingViewing => domain_separator::OVSK_M,
122 KeyType::Tagging => domain_separator::TSK_M,
123 }
124 }
125}
126
127pub fn compute_app_secret_key(
131 master_key: &GrumpkinScalar,
132 app: &AztecAddress,
133 key_type: KeyType,
134) -> Fr {
135 let hi = master_key.hi();
136 let lo = master_key.lo();
137 let separator = key_type.domain_separator();
138 poseidon2_hash_with_separator(&[hi, lo, Fr::from(*app)], separator)
139}
140
141pub fn compute_app_nullifier_hiding_key(
143 master_nullifier_hiding_key: &GrumpkinScalar,
144 app: &AztecAddress,
145) -> Fr {
146 compute_app_secret_key(master_nullifier_hiding_key, app, KeyType::Nullifier)
147}
148
149pub fn compute_ovsk_app(
154 master_outgoing_viewing_key: &GrumpkinScalar,
155 app: &AztecAddress,
156) -> GrumpkinScalar {
157 let fr_result =
158 compute_app_secret_key(master_outgoing_viewing_key, app, KeyType::OutgoingViewing);
159 GrumpkinScalar::from_be_bytes_mod_order(&fr_result.to_be_bytes())
162}
163
164#[cfg(test)]
165#[allow(clippy::expect_used)]
166mod tests {
167 use super::*;
168
169 #[test]
172 fn master_key_derivation_nhk_m() {
173 let sk = Fr::from(8923u64);
174 let nhk_m = derive_master_nullifier_hiding_key(&sk);
175 let expected = GrumpkinScalar::from_hex(
176 "0x26dd6f83a99b5b1cea47692f40b7aece47756a1a5e93138c5b8f7e7afd36ed1a",
177 )
178 .expect("valid hex");
179 assert_eq!(nhk_m, expected);
180 }
181
182 #[test]
183 fn master_key_derivation_ivsk_m() {
184 let sk = Fr::from(8923u64);
185 let ivsk_m = derive_master_incoming_viewing_secret_key(&sk);
186 let expected = GrumpkinScalar::from_hex(
187 "0x0d3e4402946f2f712d942e1a3962b12fc521effc39fe93777f91285f1ad414cb",
188 )
189 .expect("valid hex");
190 assert_eq!(ivsk_m, expected);
191 }
192
193 #[test]
194 fn public_key_derivation_npk_m() {
195 let sk = Fr::from(8923u64);
196 let nhk_m = derive_master_nullifier_hiding_key(&sk);
197 let npk_m = derive_public_key_from_secret_key(&nhk_m);
198 let expected_x =
199 Fr::from_hex("0x0d86b380f66ec74d32bb04d98f5b2dcef6d92f344e65604a21640f87fb6d078e")
200 .expect("valid hex");
201 let expected_y =
202 Fr::from_hex("0x2b68df4d20985b71c252746a3f2cc5af32b5f0c32739b94f166dfa230f50397b")
203 .expect("valid hex");
204 assert_eq!(npk_m.x, expected_x);
205 assert_eq!(npk_m.y, expected_y);
206 assert!(!npk_m.is_infinite);
207 }
208
209 #[test]
210 fn public_key_derivation_ivpk_m() {
211 let sk = Fr::from(8923u64);
212 let ivsk_m = derive_master_incoming_viewing_secret_key(&sk);
213 let ivpk_m = derive_public_key_from_secret_key(&ivsk_m);
214 let expected_x =
215 Fr::from_hex("0x0e0eb5bc3eb9959d6e05cbc0e37b2fa4cfb113c1db651c384907547f1f867010")
216 .expect("valid hex");
217 let expected_y =
218 Fr::from_hex("0x1db2e49c6845619ba432a951d86de2d41680157b0f54556246916900c0fcdcf2")
219 .expect("valid hex");
220 assert_eq!(ivpk_m.x, expected_x);
221 assert_eq!(ivpk_m.y, expected_y);
222 }
223
224 #[test]
225 fn public_key_derivation_ovpk_m() {
226 let sk = Fr::from(8923u64);
227 let ovsk_m = derive_master_outgoing_viewing_secret_key(&sk);
228 let ovpk_m = derive_public_key_from_secret_key(&ovsk_m);
229 let expected_x =
230 Fr::from_hex("0x2721eaed30c0c9fae14c2ca4af7668a46278762d4a6066ab7a5defcc242f559c")
231 .expect("valid hex");
232 let expected_y =
233 Fr::from_hex("0x0bd0c4b0ec90ebafe511f20e818fb359a1322ab0f02fe3ebec95af5df502015d")
234 .expect("valid hex");
235 assert_eq!(ovpk_m.x, expected_x);
236 assert_eq!(ovpk_m.y, expected_y);
237 }
238
239 #[test]
240 fn public_key_derivation_tpk_m() {
241 let sk = Fr::from(8923u64);
242 let tsk_m = derive_master_tagging_secret_key(&sk);
243 let tpk_m = derive_public_key_from_secret_key(&tsk_m);
244 let expected_x =
245 Fr::from_hex("0x0fabb6adca7c2bf7f6202c65fe2785096efb317897bc545c427635a61d536955")
246 .expect("valid hex");
247 let expected_y =
248 Fr::from_hex("0x2cc356e6e5b68fd64d33c96fad7bb1394956c53930fefdf0bb536812ec604459")
249 .expect("valid hex");
250 assert_eq!(tpk_m.x, expected_x);
251 assert_eq!(tpk_m.y, expected_y);
252 }
253
254 #[test]
255 fn derive_keys_full_round_trip() {
256 let sk = Fr::from(8923u64);
257 let derived = derive_keys(&sk);
258
259 let expected_nhk_m = GrumpkinScalar::from_hex(
261 "0x26dd6f83a99b5b1cea47692f40b7aece47756a1a5e93138c5b8f7e7afd36ed1a",
262 )
263 .expect("valid hex");
264 assert_eq!(derived.master_nullifier_hiding_key, expected_nhk_m);
265
266 let expected_npk_x =
268 Fr::from_hex("0x0d86b380f66ec74d32bb04d98f5b2dcef6d92f344e65604a21640f87fb6d078e")
269 .expect("valid hex");
270 assert_eq!(
271 derived.public_keys.master_nullifier_public_key.x,
272 expected_npk_x
273 );
274
275 let expected_ivpk_x =
277 Fr::from_hex("0x0e0eb5bc3eb9959d6e05cbc0e37b2fa4cfb113c1db651c384907547f1f867010")
278 .expect("valid hex");
279 assert_eq!(
280 derived.public_keys.master_incoming_viewing_public_key.x,
281 expected_ivpk_x
282 );
283
284 let pk_hash = derived.public_keys.hash();
286 assert!(!pk_hash.is_zero());
287 }
288
289 #[test]
290 fn app_nullifier_hiding_key() {
291 let sk = Fr::from(8923u64);
292 let nhk_m = derive_master_nullifier_hiding_key(&sk);
293 let app = AztecAddress::from(624u64);
294 let app_nhk = compute_app_nullifier_hiding_key(&nhk_m, &app);
295 let expected =
296 Fr::from_hex("0x165cc265d187ed42f0e3f5adbb5a0055a77e205daeb68dd1735796ee402e502f")
297 .expect("valid hex");
298 assert_eq!(app_nhk, expected);
299 }
300
301 #[test]
302 fn app_ovsk() {
303 let sk = Fr::from(8923u64);
304 let ovsk_m = derive_master_outgoing_viewing_secret_key(&sk);
305 let app = AztecAddress::from(624u64);
306 let ovsk_app = compute_ovsk_app(&ovsk_m, &app);
307 let expected = GrumpkinScalar::from_hex(
308 "0x058452c94b1d8540a39d9343758fc132af3401237bd1ac2a16c37462a173954a",
309 )
310 .expect("valid hex");
311 assert_eq!(ovsk_app, expected);
312 }
313
314 #[test]
315 fn signing_key_equals_ivsk() {
316 let sk = Fr::from(8923u64);
317 let ivsk = derive_master_incoming_viewing_secret_key(&sk);
318 let signing = derive_signing_key(&sk);
319 assert_eq!(ivsk, signing);
320 }
321
322 #[test]
323 fn fq_hi_lo_split() {
324 let sk = Fr::from(8923u64);
325 let nhk_m = derive_master_nullifier_hiding_key(&sk);
326
327 let hi = nhk_m.hi();
328 let lo = nhk_m.lo();
329
330 assert!(!hi.is_zero());
332 assert!(!lo.is_zero());
333
334 let hi_bytes = hi.to_be_bytes();
336 let lo_bytes = lo.to_be_bytes();
337 let mut reconstructed = [0u8; 32];
338 reconstructed[..16].copy_from_slice(&hi_bytes[16..]);
340 reconstructed[16..].copy_from_slice(&lo_bytes[16..]);
341 let original_bytes = nhk_m.to_be_bytes();
342 assert_eq!(reconstructed, original_bytes);
343 }
344}