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.
72    /// The order of the peer group elements is preserved for resulting shared keys.
73    pub fn generate(peer_group_elements: Vec<G>) -> hopr_crypto_types::errors::Result<SharedKeys<E, G>> {
74        let mut shared_keys = Vec::new();
75
76        // coeff_prev becomes: x * b_0 * b_1 * b_2 * ...
77        // alpha_prev becomes: x * b_0 * b_1 * b_2 * ... * G
78        let (mut alpha_prev, mut coeff_prev) = G::random_pair();
79
80        // Save the part of the result
81        let alpha = alpha_prev.to_alpha();
82
83        // Iterate through all the given peer public keys
84        let keys_len = peer_group_elements.len();
85        for (i, group_element) in peer_group_elements.into_iter().enumerate() {
86            // Try to decode the given public key point & multiply by the current coefficient
87            let salt = group_element.to_alpha();
88            let shared_secret = group_element.mul(&coeff_prev);
89
90            // Extract the shared secret from the computed EC point and copy it into the shared keys structure
91            shared_keys.push(shared_secret.extract_key(HASH_KEY_SPHINX_SECRET, &salt));
92
93            // Stop here, we don't need to compute anything more
94            if i == keys_len - 1 {
95                break;
96            }
97
98            // Compute the new blinding factor b_k (alpha needs compressing first)
99            let b_k = shared_secret.extract_key(HASH_KEY_SPHINX_BLINDING, &alpha_prev.to_alpha());
100            let b_k_checked = E::from_bytes(b_k.as_ref())?;
101
102            // Update coeff_prev and alpha
103            alpha_prev = alpha_prev.mul(&b_k_checked);
104            coeff_prev = coeff_prev.mul(b_k_checked);
105
106            if !alpha_prev.is_valid() {
107                return Err(CryptoError::CalculationError);
108            }
109        }
110
111        Ok(SharedKeys {
112            alpha,
113            secrets: shared_keys,
114            _d: PhantomData,
115        })
116    }
117
118    /// Calculates the forward transformation given the local private key.
119    /// The `public_group_element` is a precomputed group element associated with the private key for efficiency.
120    pub fn forward_transform(
121        alpha: &Alpha<G::AlphaLen>,
122        private_scalar: &E,
123        public_group_element: &G,
124    ) -> hopr_crypto_types::errors::Result<(Alpha<G::AlphaLen>, SharedSecret)> {
125        let alpha_point = G::from_alpha(alpha.clone())?;
126
127        let s_k = alpha_point.clone().mul(private_scalar);
128
129        let secret = s_k.extract_key(HASH_KEY_SPHINX_SECRET, &public_group_element.to_alpha());
130
131        let b_k = s_k.extract_key(HASH_KEY_SPHINX_BLINDING, alpha);
132
133        let b_k_checked = E::from_bytes(b_k.as_ref())?;
134        let alpha_new = alpha_point.mul(&b_k_checked);
135
136        Ok((alpha_new.to_alpha(), secret))
137    }
138}
139
140const HASH_KEY_PRP: &str = "HASH_KEY_PRP";
141
142const HASH_KEY_REPLY_PRP: &str = "HASH_KEY_REPLY_PRP";
143
144/// Represents an instantiation of the Spinx protocol using the given EC group and corresponding public key object.
145pub trait SphinxSuite {
146    /// Keypair corresponding to the EC group
147    type P: Keypair;
148
149    /// Scalar type supported by the EC group
150    type E: Scalar + for<'a> From<&'a Self::P>;
151
152    /// EC group element
153    type G: GroupElement<Self::E> + for<'a> From<&'a <Self::P as Keypair>::Public>;
154
155    /// Pseudo-Random Permutation used to encrypt and decrypt packet payload
156    type PRP: crypto_traits::StreamCipher + crypto_traits::KeyIvInit;
157
158    /// Convenience function to generate shared keys from the path of public keys.
159    fn new_shared_keys(
160        public_keys: &[<Self::P as Keypair>::Public],
161    ) -> hopr_crypto_types::errors::Result<SharedKeys<Self::E, Self::G>> {
162        SharedKeys::generate(public_keys.iter().map(|pk| pk.into()).collect())
163    }
164
165    /// Instantiates a new Pseudo-Random Permutation IV and key for general packet data.
166    fn new_prp_init(secret: &SecretKey) -> hopr_crypto_types::errors::Result<IvKey<Self::PRP>> {
167        generate_key_iv(secret, HASH_KEY_PRP, None)
168    }
169
170    /// Instantiates a new Pseudo-Random Permutation IV and key for reply data.
171    fn new_reply_prp_init(secret: &SecretKey16, salt: &[u8]) -> hopr_crypto_types::errors::Result<IvKey<Self::PRP>> {
172        generate_key_iv(secret, HASH_KEY_REPLY_PRP, Some(salt))
173    }
174}
175
176#[cfg(test)]
177pub(crate) mod tests {
178    use subtle::ConstantTimeEq;
179
180    use super::*;
181
182    pub fn generic_sphinx_suite_test<S: SphinxSuite>(node_count: usize) {
183        let (pub_keys, priv_keys): (Vec<S::G>, Vec<S::E>) = (0..node_count).map(|_| S::G::random_pair()).unzip();
184
185        // Now generate the key shares for the public keys
186        let generated_shares = SharedKeys::<S::E, S::G>::generate(pub_keys.clone()).unwrap();
187        assert_eq!(
188            node_count,
189            generated_shares.secrets.len(),
190            "number of generated keys should be equal to the number of nodes"
191        );
192
193        let mut alpha_cpy = generated_shares.alpha.clone();
194        for (i, priv_key) in priv_keys.into_iter().enumerate() {
195            let (alpha, secret) =
196                SharedKeys::<S::E, S::G>::forward_transform(&alpha_cpy, &priv_key, &pub_keys[i]).unwrap();
197
198            assert_eq!(
199                secret.ct_eq(&generated_shares.secrets[i]).unwrap_u8(),
200                1,
201                "forward transform should yield the same shared secret"
202            );
203
204            alpha_cpy = alpha;
205        }
206    }
207}