Skip to main content

nox_core/protocol/
kdf.rs

1use ark_bn254::Fr;
2use ark_std::Zero;
3use darkpool_crypto::error::CryptoError;
4use darkpool_crypto::IPoseidonHasher;
5use std::sync::Arc;
6
7pub struct HisokaKdf {
8    hasher: Arc<dyn IPoseidonHasher>,
9}
10
11impl HisokaKdf {
12    pub fn new(hasher: Arc<dyn IPoseidonHasher>) -> Self {
13        Self { hasher }
14    }
15
16    /// Derive a child key from a master key, purpose string, and optional nonce.
17    ///
18    /// Returns `Err` if `purpose` exceeds 32 bytes.
19    ///
20    /// **Collision note (M-04):** When `nonce` is `None` or `Some(Fr::zero())`,
21    /// the hash input is identical: `[master, purpose_fr]`. This is intentional --
22    /// the nonce-less derivation is used only once per purpose (e.g., `"hisoka.ephemeral"`).
23    /// Callers that re-derive with different nonces MUST use nonce >= 1.
24    pub fn derive(&self, purpose: &str, master: Fr, nonce: Option<Fr>) -> Result<Fr, CryptoError> {
25        let purpose_fr = self.hasher.string_to_fr(purpose)?;
26
27        let mut inputs = vec![master, purpose_fr];
28
29        if let Some(n) = nonce {
30            if !n.is_zero() {
31                inputs.push(n);
32            }
33        }
34
35        Ok(self.hasher.hash(&inputs))
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use ark_ff::One;
43    use darkpool_crypto::poseidon::NoxHasher;
44
45    fn kdf() -> HisokaKdf {
46        HisokaKdf::new(Arc::new(NoxHasher))
47    }
48
49    #[test]
50    fn derive_is_deterministic() {
51        let master = Fr::one();
52        let k = kdf();
53        let a = k.derive("test.purpose", master, None).unwrap();
54        let b = k.derive("test.purpose", master, None).unwrap();
55        assert_eq!(a, b);
56    }
57
58    #[test]
59    fn different_purposes_yield_different_keys() {
60        let master = Fr::one();
61        let k = kdf();
62        let a = k.derive("purpose.alpha", master, None).unwrap();
63        let b = k.derive("purpose.beta", master, None).unwrap();
64        assert_ne!(a, b);
65    }
66
67    #[test]
68    fn different_nonces_yield_different_keys() {
69        let master = Fr::one();
70        let k = kdf();
71        let a = k.derive("same", master, Some(Fr::from(1u64))).unwrap();
72        let b = k.derive("same", master, Some(Fr::from(2u64))).unwrap();
73        assert_ne!(a, b);
74    }
75
76    #[test]
77    fn none_and_zero_nonce_are_equivalent() {
78        let master = Fr::one();
79        let k = kdf();
80        let a = k.derive("same", master, None).unwrap();
81        let b = k.derive("same", master, Some(Fr::zero())).unwrap();
82        assert_eq!(
83            a, b,
84            "None and Some(Fr::zero()) must produce the same key (M-04 collision)"
85        );
86    }
87
88    #[test]
89    fn nonzero_nonce_differs_from_none() {
90        let master = Fr::one();
91        let k = kdf();
92        let a = k.derive("same", master, None).unwrap();
93        let b = k.derive("same", master, Some(Fr::from(1u64))).unwrap();
94        assert_ne!(a, b);
95    }
96
97    #[test]
98    fn different_masters_yield_different_keys() {
99        let k = kdf();
100        let a = k.derive("same", Fr::from(100u64), None).unwrap();
101        let b = k.derive("same", Fr::from(200u64), None).unwrap();
102        assert_ne!(a, b);
103    }
104
105    #[test]
106    fn output_is_nonzero() {
107        let k = kdf();
108        let result = k.derive("test", Fr::one(), None).unwrap();
109        assert!(!result.is_zero());
110    }
111}