1use decaf377::Fr;
2
3#[allow(non_snake_case)]
4pub fn derive_public(
5 root_pub: &decaf377::Element,
6 root_pub_enc: &decaf377::Encoding,
7 index: u8,
8) -> decaf377::Element {
9 let hash = blake2b_simd::Params::default()
10 .personal(b"decaf377-fmd.hkd")
11 .to_state()
12 .update(&root_pub_enc.0)
13 .update(&[index])
14 .finalize();
15 let x = Fr::from_le_bytes_mod_order(hash.as_bytes());
16 let X = x * decaf377::Element::GENERATOR;
17
18 root_pub + X
19}
20
21pub fn derive_private(root_priv: &Fr, root_pub_enc: &decaf377::Encoding, index: u8) -> Fr {
22 let hash = blake2b_simd::Params::default()
23 .personal(b"decaf377-fmd.hkd")
24 .to_state()
25 .update(&root_pub_enc.0)
26 .update(&[index])
27 .finalize();
28 let x = Fr::from_le_bytes_mod_order(hash.as_bytes());
29
30 *root_priv + x
31}
32
33#[cfg(test)]
34mod tests {
35 use proptest::prelude::*;
36
37 use super::*;
38
39 fn fr_strategy() -> BoxedStrategy<decaf377::Fr> {
40 any::<[u8; 32]>()
41 .prop_map(|bytes| decaf377::Fr::from_le_bytes_mod_order(&bytes[..]))
42 .boxed()
43 }
44
45 proptest! {
46 #[test]
47 fn public_private_derivation_match(root_priv in fr_strategy()) {
48 let root_pub = root_priv * decaf377::Element::GENERATOR;
49 let root_pub_enc = root_pub.vartime_compress();
50 for i in 0..16u8 {
51 let child_pub = derive_public(&root_pub, &root_pub_enc, i);
52 let child_priv = derive_private(&root_priv, &root_pub_enc, i);
53 let child_pub_from_priv = child_priv * decaf377::Element::GENERATOR;
54 assert_eq!(child_pub, child_pub_from_priv);
55 }
56 }
57 }
58}