decaf377_fmd/
hkd.rs

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}