hopr_crypto_packet/sphinx/
shared_keys.rs1use std::{marker::PhantomData, ops::Mul};
2
3use hopr_types::{
4 crypto::prelude::*,
5 primitive::hybrid_array::{Array, ArraySize},
6};
7
8use super::derivation::{create_kdf_instance, generate_key_iv};
9
10pub type SharedSecret = SecretValue<hopr_types::primitive::typenum::U32>;
12
13pub trait Scalar: Mul<Output = Self> + Sized {
15 fn random() -> Self;
17
18 fn from_bytes(bytes: &[u8]) -> hopr_types::crypto::errors::Result<Self>;
20}
21
22pub type Alpha<A> = Array<u8, A>;
25
26pub trait GroupElement<E: Scalar>: Clone + for<'a> Mul<&'a E, Output = Self> {
30 type AlphaLen: ArraySize;
32
33 fn to_alpha(&self) -> Alpha<Self::AlphaLen>;
35
36 fn from_alpha(alpha: Alpha<Self::AlphaLen>) -> hopr_types::crypto::errors::Result<Self>;
38
39 fn generate(scalar: &E) -> Self;
41
42 fn is_valid(&self) -> bool;
44
45 fn random_pair() -> (Self, E) {
49 let scalar = E::random();
50 (Self::generate(&scalar), scalar)
51 }
52
53 fn extract_key(&self, context: &str, salt: &[u8]) -> SharedSecret {
55 let mut output = create_kdf_instance(&self.to_alpha(), context, Some(salt)).expect("invalid sphinx key length");
56 let mut out = SharedSecret::default();
57 output.fill(out.as_mut());
58 out
59 }
60}
61
62pub struct SharedKeys<E: Scalar, G: GroupElement<E>> {
64 pub alpha: Alpha<G::AlphaLen>,
65 pub secrets: Vec<SharedSecret>,
66 _d: PhantomData<(E, G)>,
67}
68
69const HASH_KEY_SPHINX_SECRET: &str = "HASH_KEY_SPHINX_SECRET";
70const HASH_KEY_SPHINX_BLINDING: &str = "HASH_KEY_SPHINX_BLINDING";
71
72impl<E: Scalar, G: GroupElement<E>> SharedKeys<E, G> {
73 pub(crate) fn generate(
77 peer_group_elements: Vec<(G, &Alpha<G::AlphaLen>)>,
78 ) -> hopr_types::crypto::errors::Result<SharedKeys<E, G>> {
79 let mut shared_keys = Vec::with_capacity(peer_group_elements.len());
80
81 let (mut alpha_prev, mut coeff_prev) = G::random_pair();
84
85 let alpha = alpha_prev.to_alpha();
87
88 let keys_len = peer_group_elements.len();
90 for (i, (group_element, salt)) in peer_group_elements.into_iter().enumerate() {
91 let shared_secret = group_element.mul(&coeff_prev);
93
94 shared_keys.push(shared_secret.extract_key(HASH_KEY_SPHINX_SECRET, salt.as_ref()));
96
97 if i == keys_len - 1 {
99 break;
100 }
101
102 let b_k = shared_secret.extract_key(HASH_KEY_SPHINX_BLINDING, &alpha_prev.to_alpha());
104 let b_k_checked = E::from_bytes(b_k.as_ref())?;
105
106 alpha_prev = alpha_prev.mul(&b_k_checked);
108 coeff_prev = coeff_prev.mul(b_k_checked);
109
110 if !alpha_prev.is_valid() {
111 return Err(CryptoError::CalculationError);
112 }
113 }
114
115 Ok(SharedKeys {
116 alpha,
117 secrets: shared_keys,
118 _d: PhantomData,
119 })
120 }
121
122 pub(crate) fn forward_transform(
127 alpha: &Alpha<G::AlphaLen>,
128 private_scalar: &E,
129 public_element_alpha: &Alpha<G::AlphaLen>,
130 ) -> hopr_types::crypto::errors::Result<(Alpha<G::AlphaLen>, SharedSecret)> {
131 let alpha_point = G::from_alpha(alpha.clone())?;
132
133 let s_k = alpha_point.clone().mul(private_scalar);
134
135 let secret = s_k.extract_key(HASH_KEY_SPHINX_SECRET, public_element_alpha);
136
137 let b_k = s_k.extract_key(HASH_KEY_SPHINX_BLINDING, alpha);
138
139 let b_k_checked = E::from_bytes(b_k.as_ref())?;
140 let alpha_new = alpha_point.mul(&b_k_checked);
141
142 if !alpha_new.is_valid() {
143 return Err(hopr_types::crypto::errors::CryptoError::CalculationError);
144 }
145
146 Ok((alpha_new.to_alpha(), secret))
147 }
148}
149
150const HASH_KEY_PRP: &str = "HASH_KEY_PRP";
151
152const HASH_KEY_REPLY_PRP: &str = "HASH_KEY_REPLY_PRP";
153
154pub trait SphinxSuite {
156 type P: Keypair;
158
159 type E: Scalar + for<'a> From<&'a Self::P>;
161
162 type ExpandedPublic;
167
168 type G: GroupElement<Self::E> + for<'a> From<&'a Self::ExpandedPublic>;
170
171 type PRP: crypto_traits::PRP + crypto_traits::KeyIvInit;
173
174 fn expand_public(
178 public_key: &<Self::P as Keypair>::Public,
179 ) -> hopr_types::crypto::errors::Result<Self::ExpandedPublic>;
180
181 fn expand_path(
183 public_keys: &[<Self::P as Keypair>::Public],
184 ) -> hopr_types::crypto::errors::Result<Vec<Self::ExpandedPublic>> {
185 public_keys.iter().map(|pk| Self::expand_public(pk)).collect()
186 }
187
188 fn new_shared_keys<'a>(
193 public_keys: &'a [<Self::P as Keypair>::Public],
194 ) -> hopr_types::crypto::errors::Result<SharedKeys<Self::E, Self::G>>
195 where
196 &'a Alpha<<Self::G as GroupElement<Self::E>>::AlphaLen>: From<&'a <Self::P as Keypair>::Public>,
197 {
198 let expanded = Self::expand_path(public_keys)?;
199
200 SharedKeys::generate(
201 expanded
202 .iter()
203 .zip(public_keys)
204 .map(|(expanded, compact)| (expanded.into(), compact.into()))
205 .collect(),
206 )
207 }
208
209 fn new_shared_keys_expanded<'a>(
211 public_keys: &'a [Self::ExpandedPublic],
212 ) -> hopr_types::crypto::errors::Result<SharedKeys<Self::E, Self::G>>
213 where
214 &'a Alpha<<Self::G as GroupElement<Self::E>>::AlphaLen>: From<&'a Self::ExpandedPublic>,
215 {
216 SharedKeys::generate(public_keys.iter().map(|pk| (pk.into(), pk.into())).collect())
217 }
218
219 fn new_prp_init(secret: &SecretKey) -> hopr_types::crypto::errors::Result<IvKey<Self::PRP>> {
221 generate_key_iv(secret, HASH_KEY_PRP, None)
222 }
223
224 fn new_reply_prp_init(secret: &SecretKey16, salt: &[u8]) -> hopr_types::crypto::errors::Result<IvKey<Self::PRP>> {
226 generate_key_iv(secret, HASH_KEY_REPLY_PRP, Some(salt))
227 }
228}
229
230#[cfg(test)]
231pub(crate) mod tests {
232 use subtle::ConstantTimeEq;
233
234 use super::*;
235
236 #[allow(clippy::type_complexity)]
237 pub fn generic_sphinx_suite_test<S: SphinxSuite>(node_count: usize) {
238 let (pub_keys, priv_keys): (Vec<(S::G, Alpha<<S::G as GroupElement<S::E>>::AlphaLen>)>, Vec<S::E>) = (0
239 ..node_count)
240 .map(|_| {
241 let pair = S::G::random_pair();
242 ((pair.0.clone(), pair.0.to_alpha()), pair.1)
243 })
244 .unzip();
245
246 let pub_keys_alpha = pub_keys
247 .iter()
248 .map(|(pk, alpha)| (pk.clone(), alpha))
249 .collect::<Vec<_>>();
250
251 let generated_shares = SharedKeys::<S::E, S::G>::generate(pub_keys_alpha.clone()).unwrap();
253 assert_eq!(
254 node_count,
255 generated_shares.secrets.len(),
256 "number of generated keys should be equal to the number of nodes"
257 );
258
259 let mut alpha_cpy = generated_shares.alpha.clone();
260 for (i, priv_key) in priv_keys.into_iter().enumerate() {
261 let (alpha, secret) =
262 SharedKeys::<S::E, S::G>::forward_transform(&alpha_cpy, &priv_key, &pub_keys[i].1).unwrap();
263
264 assert_eq!(
265 secret.ct_eq(&generated_shares.secrets[i]).unwrap_u8(),
266 1,
267 "forward transform should yield the same shared secret"
268 );
269
270 alpha_cpy = alpha;
271 }
272 }
273}