hopr_crypto_sphinx/
derivation.rs

1use blake2::Blake2s256;
2use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest};
3use generic_array::{ArrayLength, GenericArray};
4use hkdf::SimpleHkdf;
5use hopr_crypto_random::random_fill;
6use hopr_crypto_types::errors::CryptoError::{CalculationError, InvalidParameterSize};
7use hopr_crypto_types::primitives::{DigestLike, SecretKey, SimpleDigest, SimpleMac};
8use hopr_crypto_types::types::{HalfKey, PacketTag};
9use k256::Secp256k1;
10
11// Module-specific constants
12const HASH_KEY_COMMITMENT_SEED: &str = "HASH_KEY_COMMITMENT_SEED";
13const HASH_KEY_HMAC: &str = "HASH_KEY_HMAC";
14const HASH_KEY_PACKET_TAG: &str = "HASH_KEY_PACKET_TAG";
15const HASH_KEY_OWN_KEY: &str = "HASH_KEY_OWN_KEY";
16const HASH_KEY_ACK_KEY: &str = "HASH_KEY_ACK_KEY";
17
18/// Size of the nonce in the Ping sub protocol
19pub const PING_PONG_NONCE_SIZE: usize = 16;
20
21/// Calculates a message authentication code with fixed key tag (HASH_KEY_HMAC)
22/// The given `secret` is first transformed using HKDF before the MAC calculation is performed.
23/// Based on `SimpleMac`
24pub fn create_tagged_mac(secret: &SecretKey, data: &[u8]) -> [u8; SimpleMac::SIZE] {
25    let mut mac = SimpleMac::new(&derive_mac_key(secret));
26    mac.update(data);
27    mac.finalize().into()
28}
29
30/// Helper function to expand an already cryptographically strong key material using the HKDF expand function
31/// The size of the secret must be at least the size of the output of the underlying hash function, which in this
32/// case is Blake2s256, meaning the `secret` size must be at least 32 bytes.
33fn hkdf_expand_from_prk<L: ArrayLength<u8>>(secret: &SecretKey, tag: &[u8]) -> GenericArray<u8, L> {
34    // Create HKDF instance
35    let hkdf = SimpleHkdf::<Blake2s256>::from_prk(secret.as_ref()).expect("size of the hkdf secret key is invalid"); // should not happen
36
37    // Expand the key to the required length
38    let mut out = GenericArray::default();
39    hkdf.expand(tag, &mut out).expect("invalid hkdf output size"); // should not happen
40
41    out
42}
43
44/// Derives a ping challenge (if no challenge is given) or a pong response to a ping challenge.
45pub fn derive_ping_pong(challenge: Option<&[u8]>) -> Box<[u8]> {
46    let mut ret = [0u8; PING_PONG_NONCE_SIZE];
47    match challenge {
48        None => random_fill(&mut ret),
49        Some(chal) => {
50            let mut digest = SimpleDigest::default();
51            digest.update(chal);
52            // Finalize requires enough space for the hash value, so this needs an extra copy
53            let hash = digest.finalize();
54            ret.copy_from_slice(&hash[0..PING_PONG_NONCE_SIZE]);
55        }
56    }
57    ret.into()
58}
59
60/// Derives the commitment seed given the compressed private key representation
61/// and the serialized channel information.
62pub fn derive_commitment_seed(private_key: &[u8], channel_info: &[u8]) -> [u8; SimpleMac::SIZE] {
63    let sk: SecretKey = hkdf_expand_from_prk(
64        &private_key.try_into().expect("commitment private key size invalid"),
65        HASH_KEY_COMMITMENT_SEED.as_bytes(),
66    )
67    .into();
68    let mut mac = SimpleMac::new(&sk);
69    mac.update(channel_info);
70    mac.finalize().into()
71}
72
73/// Derives the packet tag used during packet construction by expanding the given secret.
74pub fn derive_packet_tag(secret: &SecretKey) -> PacketTag {
75    hkdf_expand_from_prk::<typenum::U16>(secret, HASH_KEY_PACKET_TAG.as_bytes()).into()
76}
77
78/// Derives a key for MAC calculation by expanding the given secret.
79pub fn derive_mac_key(secret: &SecretKey) -> SecretKey {
80    hkdf_expand_from_prk::<typenum::U32>(secret, HASH_KEY_HMAC.as_bytes()).into()
81}
82
83/// Internal convenience function to generate key and IV from the given secret.
84/// WARNING: The `iv_first` distinguishes if the IV should be sampled before or after the key is sampled.
85pub(crate) fn generate_key_iv(secret: &SecretKey, info: &[u8], key: &mut [u8], iv: &mut [u8], iv_first: bool) {
86    let hkdf = SimpleHkdf::<Blake2s256>::from_prk(secret.as_ref()).expect("secret key length must be correct");
87
88    let mut out = vec![0u8; key.len() + iv.len()];
89    hkdf.expand(info, &mut out)
90        .expect("key and iv are too big for this kdf");
91
92    if iv_first {
93        let (v_iv, v_key) = out.split_at(iv.len());
94        iv.copy_from_slice(v_iv);
95        key.copy_from_slice(v_key);
96    } else {
97        let (v_key, v_iv) = out.split_at(key.len());
98        key.copy_from_slice(v_key);
99        iv.copy_from_slice(v_iv);
100    }
101}
102
103/// Sample a random secp256k1 field element that can represent a valid secp256k1 point.
104/// The implementation uses `hash_to_field` function as defined in
105/// `<https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#name-hashing-to-a-finite-field>`
106/// The `secret` must be at least `SecretKey::LENGTH` long.
107/// The `tag` parameter will be used as an additional Domain Separation Tag.
108pub fn sample_secp256k1_field_element(secret: &[u8], tag: &str) -> hopr_crypto_types::errors::Result<HalfKey> {
109    if secret.len() >= SecretKey::LENGTH {
110        let scalar = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Sha3_256>>(
111            &[secret],
112            &[b"secp256k1_XMD:SHA3-256_SSWU_RO_", tag.as_bytes()],
113        )
114        .map_err(|_| CalculationError)?;
115        Ok(HalfKey::try_from(scalar.to_bytes().as_ref())?)
116    } else {
117        Err(InvalidParameterSize {
118            name: "secret".into(),
119            expected: SecretKey::LENGTH,
120        })
121    }
122}
123
124/// Used in Proof of Relay to derive own half-key (S0)
125/// The function samples a secp256k1 field element using the given `secret` via `sample_field_element`.
126pub fn derive_own_key_share(secret: &SecretKey) -> HalfKey {
127    sample_secp256k1_field_element(secret.as_ref(), HASH_KEY_OWN_KEY).expect("failed to sample own key share")
128}
129
130/// Used in Proof of Relay to derive the half-key of for the acknowledgement (S1)
131/// The function samples a secp256k1 field element using the given `secret` via `sample_field_element`.
132pub fn derive_ack_key_share(secret: &SecretKey) -> HalfKey {
133    sample_secp256k1_field_element(secret.as_ref(), HASH_KEY_ACK_KEY).expect("failed to sample ack key share")
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use elliptic_curve::{sec1::ToEncodedPoint, ProjectivePoint, ScalarPrimitive};
140    use hex_literal::hex;
141    use hopr_crypto_types::keypairs::{ChainKeypair, Keypair};
142    use hopr_crypto_types::types::PublicKey;
143    use hopr_crypto_types::vrf::derive_vrf_parameters;
144    use k256::Scalar;
145
146    #[test]
147    fn test_derive_commitment_seed() {
148        let priv_key = [0u8; SecretKey::LENGTH];
149        let chinfo = [0u8; SecretKey::LENGTH];
150
151        let res = derive_commitment_seed(&priv_key, &chinfo);
152
153        let r = hex!("0abe559a1577e99e16f112bb8a88f7793ff1fb22af46b810995fb754ea319386");
154        assert_eq!(r, res.as_ref());
155    }
156
157    #[test]
158    fn test_derive_packet_tag() {
159        let tag = derive_packet_tag(&SecretKey::default());
160
161        let r = hex!("e0cf0fb82ea5a541b0367b376eb36a60");
162        assert_eq!(r, tag.as_ref());
163    }
164
165    #[test]
166    fn test_derive_mac_key() {
167        let tag = derive_mac_key(&SecretKey::default());
168
169        let r = hex!("7f656daaf7c2e64bcfc1386f8af273890e863dec63b410967a5652630617b09b");
170        assert_eq!(r, tag.as_ref());
171    }
172
173    #[test]
174    fn test_sample_field_element() {
175        let secret = [1u8; SecretKey::LENGTH];
176        assert!(sample_secp256k1_field_element(&secret, "TEST_TAG").is_ok());
177    }
178
179    #[test]
180    fn test_vrf_parameter_generation() -> anyhow::Result<()> {
181        let dst = b"some DST tag";
182        let priv_key: [u8; 32] = hex!("f13233ff60e1f618525dac5f7d117bef0bad0eb0b0afb2459f9cbc57a3a987ba"); // dummy
183        let message = hex!("f13233ff60e1f618525dac5f7d117bef0bad0eb0b0afb2459f9cbc57a3a987ba"); // dummy
184
185        let keypair = ChainKeypair::from_secret(&priv_key)?;
186        // vrf verification algorithm
187        let pub_key = PublicKey::from_privkey(&priv_key)?;
188
189        let params = derive_vrf_parameters(&message, &keypair, dst)?;
190
191        let cap_b = Secp256k1::hash_from_bytes::<ExpandMsgXmd<sha3::Keccak256>>(
192            &[&pub_key.to_address().as_ref(), &message],
193            &[dst],
194        )?;
195
196        assert_eq!(
197            params.get_s_b_witness(&keypair.public().to_address(), &message, dst)?,
198            (cap_b * params.s).to_encoded_point(false)
199        );
200
201        let a: Scalar = ScalarPrimitive::<Secp256k1>::from_slice(&priv_key)?.into();
202        assert_eq!(params.get_h_v_witness(), (cap_b * a * params.h).to_encoded_point(false));
203
204        let r_v: ProjectivePoint<Secp256k1> = cap_b * params.s - params.V.clone().into_projective_point() * params.h;
205
206        let h_check = Secp256k1::hash_to_scalar::<ExpandMsgXmd<sha3::Keccak256>>(
207            &[
208                &pub_key.to_address().as_ref(),
209                &params.V.as_uncompressed().as_bytes()[1..],
210                &r_v.to_affine().to_encoded_point(false).as_bytes()[1..],
211                &message,
212            ],
213            &[dst],
214        )?;
215
216        assert_eq!(h_check, params.h);
217
218        Ok(())
219    }
220
221    #[test]
222    fn test_mac() {
223        let key = GenericArray::from([1u8; SecretKey::LENGTH]);
224        let data = [2u8; 64];
225        let mac = create_tagged_mac(&key.into(), &data);
226
227        let expected = hex!("77264e8ea3052b621dbb8b1904403a64b1064c884cf7629c266edd7e237f2799");
228        assert_eq!(SimpleMac::SIZE, mac.len());
229        assert_eq!(expected, mac);
230    }
231}