hopr_crypto_sphinx/
shared_keys.rs

1use std::{marker::PhantomData, ops::Mul};
2
3use generic_array::{ArrayLength, GenericArray};
4use hopr_crypto_types::prelude::*;
5
6use crate::derivation::{create_kdf_instance, generate_key_iv};
7
8/// Represents a shared secret with a remote peer.
9pub type SharedSecret = SecretValue<typenum::U32>;
10
11/// Types representing a valid non-zero scalar of an additive abelian group.
12pub trait Scalar: Mul<Output = Self> + Sized {
13    /// Generates a random scalar using a cryptographically secure RNG.
14    fn random() -> Self;
15
16    /// Create scalar from bytes
17    fn from_bytes(bytes: &[u8]) -> hopr_crypto_types::errors::Result<Self>;
18}
19
20/// Represents the Alpha value of a certain length in the Sphinx protocol
21/// The length of the alpha value is directly dependent on the group element.
22pub type Alpha<A> = GenericArray<u8, A>;
23
24/// Generic additive abelian group element with an associated scalar type.
25/// It also comes with the associated Alpha value size.
26/// A group element is considered valid if it is not neutral or a torsion element of small order.
27pub trait GroupElement<E: Scalar>: Clone + for<'a> Mul<&'a E, Output = Self> {
28    /// Length of the Alpha value - a binary representation of the group element.
29    type AlphaLen: ArrayLength;
30
31    /// Converts the group element to a binary format suitable for representing the Alpha value.
32    fn to_alpha(&self) -> Alpha<Self::AlphaLen>;
33
34    /// Converts the group element from the binary format representing an Alpha value.
35    fn from_alpha(alpha: Alpha<Self::AlphaLen>) -> hopr_crypto_types::errors::Result<Self>;
36
37    /// Create a group element using the group generator and the given scalar
38    fn generate(scalar: &E) -> Self;
39
40    /// Group element is considered valid if it is not a neutral element and also not a torsion element of small order.
41    fn is_valid(&self) -> bool;
42
43    /// Generates a random pair of group element and secret scalar.
44    /// This is a convenience method that internally calls the `random` method of the associated Scalar
45    /// and constructs the group element using `generate`.
46    fn random_pair() -> (Self, E) {
47        let scalar = E::random();
48        (Self::generate(&scalar), scalar)
49    }
50
51    /// Extract a keying material from a group element using a KDF
52    fn extract_key(&self, context: &str, salt: &[u8]) -> SharedSecret {
53        let mut output = create_kdf_instance(&self.to_alpha(), context, Some(salt)).expect("invalid sphinx key length");
54        let mut out = SharedSecret::default();
55        output.fill(out.as_mut());
56        out
57    }
58}
59
60/// Structure containing shared keys for peers using the Sphinx algorithm.
61pub struct SharedKeys<E: Scalar, G: GroupElement<E>> {
62    pub alpha: Alpha<G::AlphaLen>,
63    pub secrets: Vec<SharedSecret>,
64    _d: PhantomData<(E, G)>,
65}
66
67const HASH_KEY_SPHINX_SECRET: &str = "HASH_KEY_SPHINX_SECRET";
68const HASH_KEY_SPHINX_BLINDING: &str = "HASH_KEY_SPHINX_BLINDING";
69
70impl<E: Scalar, G: GroupElement<E>> SharedKeys<E, G> {
71    /// Generates shared secrets given the group element of the peers and their Alpha value encodings.
72    ///
73    /// The order of the peer group elements is preserved for resulting shared keys.
74    pub(crate) fn generate(
75        peer_group_elements: Vec<(G, &Alpha<G::AlphaLen>)>,
76    ) -> hopr_crypto_types::errors::Result<SharedKeys<E, G>> {
77        let mut shared_keys = Vec::new();
78
79        // coeff_prev becomes: x * b_0 * b_1 * b_2 * ...
80        // alpha_prev becomes: x * b_0 * b_1 * b_2 * ... * G
81        let (mut alpha_prev, mut coeff_prev) = G::random_pair();
82
83        // Save the part of the result
84        let alpha = alpha_prev.to_alpha();
85
86        // Iterate through all the given peer public keys
87        let keys_len = peer_group_elements.len();
88        for (i, (group_element, salt)) in peer_group_elements.into_iter().enumerate() {
89            // Multiply the public key by the current coefficient
90            let shared_secret = group_element.mul(&coeff_prev);
91
92            // Extract the shared secret from the computed EC point and copy it into the shared keys structure
93            shared_keys.push(shared_secret.extract_key(HASH_KEY_SPHINX_SECRET, salt.as_ref()));
94
95            // Stop here, we don't need to compute anything more
96            if i == keys_len - 1 {
97                break;
98            }
99
100            // Compute the new blinding factor b_k (alpha needs compressing first)
101            let b_k = shared_secret.extract_key(HASH_KEY_SPHINX_BLINDING, &alpha_prev.to_alpha());
102            let b_k_checked = E::from_bytes(b_k.as_ref())?;
103
104            // Update coeff_prev and alpha
105            alpha_prev = alpha_prev.mul(&b_k_checked);
106            coeff_prev = coeff_prev.mul(b_k_checked);
107
108            if !alpha_prev.is_valid() {
109                return Err(CryptoError::CalculationError);
110            }
111        }
112
113        Ok(SharedKeys {
114            alpha,
115            secrets: shared_keys,
116            _d: PhantomData,
117        })
118    }
119
120    /// Calculates the forward transformation given the local private key.
121    ///
122    /// Efficiency note: the `public_element_alpha` is a precomputed group element Alpha encoding associated with the
123    /// `private_scalar`.
124    pub(crate) fn forward_transform(
125        alpha: &Alpha<G::AlphaLen>,
126        private_scalar: &E,
127        public_element_alpha: &Alpha<G::AlphaLen>,
128    ) -> hopr_crypto_types::errors::Result<(Alpha<G::AlphaLen>, SharedSecret)> {
129        let alpha_point = G::from_alpha(alpha.clone())?;
130
131        let s_k = alpha_point.clone().mul(private_scalar);
132
133        let secret = s_k.extract_key(HASH_KEY_SPHINX_SECRET, public_element_alpha);
134
135        let b_k = s_k.extract_key(HASH_KEY_SPHINX_BLINDING, alpha);
136
137        let b_k_checked = E::from_bytes(b_k.as_ref())?;
138        let alpha_new = alpha_point.mul(&b_k_checked);
139
140        Ok((alpha_new.to_alpha(), secret))
141    }
142}
143
144const HASH_KEY_PRP: &str = "HASH_KEY_PRP";
145
146const HASH_KEY_REPLY_PRP: &str = "HASH_KEY_REPLY_PRP";
147
148/// Represents an instantiation of the Spinx protocol using the given EC group and corresponding public key object.
149pub trait SphinxSuite {
150    /// Keypair corresponding to the EC group
151    type P: Keypair;
152
153    /// Scalar type supported by the EC group
154    type E: Scalar + for<'a> From<&'a Self::P>;
155
156    /// EC group element
157    type G: GroupElement<Self::E> + for<'a> From<&'a <Self::P as Keypair>::Public>;
158
159    /// Pseudo-Random Permutation used to encrypt and decrypt packet payload
160    type PRP: crypto_traits::PRP + crypto_traits::KeyIvInit;
161
162    /// Convenience function to generate shared keys from the path of public keys.
163    fn new_shared_keys<'a>(
164        public_keys: &'a [<Self::P as Keypair>::Public],
165    ) -> hopr_crypto_types::errors::Result<SharedKeys<Self::E, Self::G>>
166    where
167        &'a Alpha<<Self::G as GroupElement<Self::E>>::AlphaLen>: From<&'a <Self::P as Keypair>::Public>,
168    {
169        SharedKeys::generate(public_keys.iter().map(|pk| (pk.into(), pk.into())).collect())
170    }
171
172    /// Instantiates a new Pseudo-Random Permutation IV and key for general packet data.
173    fn new_prp_init(secret: &SecretKey) -> hopr_crypto_types::errors::Result<IvKey<Self::PRP>> {
174        generate_key_iv(secret, HASH_KEY_PRP, None)
175    }
176
177    /// Instantiates a new Pseudo-Random Permutation IV and key for reply data.
178    fn new_reply_prp_init(secret: &SecretKey16, salt: &[u8]) -> hopr_crypto_types::errors::Result<IvKey<Self::PRP>> {
179        generate_key_iv(secret, HASH_KEY_REPLY_PRP, Some(salt))
180    }
181}
182
183#[cfg(test)]
184pub(crate) mod tests {
185    use subtle::ConstantTimeEq;
186
187    use super::*;
188
189    pub fn generic_sphinx_suite_test<S: SphinxSuite>(node_count: usize) {
190        let (pub_keys, priv_keys): (Vec<(S::G, Alpha<<S::G as GroupElement<S::E>>::AlphaLen>)>, Vec<S::E>) = (0
191            ..node_count)
192            .map(|_| {
193                let pair = S::G::random_pair();
194                ((pair.0.clone(), pair.0.to_alpha()), pair.1)
195            })
196            .unzip();
197
198        let pub_keys_alpha = pub_keys
199            .iter()
200            .map(|(pk, alpha)| (pk.clone(), alpha))
201            .collect::<Vec<_>>();
202
203        // Now generate the key shares for the public keys
204        let generated_shares = SharedKeys::<S::E, S::G>::generate(pub_keys_alpha.clone()).unwrap();
205        assert_eq!(
206            node_count,
207            generated_shares.secrets.len(),
208            "number of generated keys should be equal to the number of nodes"
209        );
210
211        let mut alpha_cpy = generated_shares.alpha.clone();
212        for (i, priv_key) in priv_keys.into_iter().enumerate() {
213            let (alpha, secret) =
214                SharedKeys::<S::E, S::G>::forward_transform(&alpha_cpy, &priv_key, &pub_keys[i].1).unwrap();
215
216            assert_eq!(
217                secret.ct_eq(&generated_shares.secrets[i]).unwrap_u8(),
218                1,
219                "forward transform should yield the same shared secret"
220            );
221
222            alpha_cpy = alpha;
223        }
224    }
225}