hopr_crypto_sphinx/
shared_keys.rs

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