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(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 G: GroupElement<Self::E> + for<'a> From<&'a <Self::P as Keypair>::Public>;
164
165 type PRP: crypto_traits::PRP + crypto_traits::KeyIvInit;
167
168 fn new_shared_keys<'a>(
170 public_keys: &'a [<Self::P as Keypair>::Public],
171 ) -> hopr_types::crypto::errors::Result<SharedKeys<Self::E, Self::G>>
172 where
173 &'a Alpha<<Self::G as GroupElement<Self::E>>::AlphaLen>: From<&'a <Self::P as Keypair>::Public>,
174 {
175 SharedKeys::generate(public_keys.iter().map(|pk| (pk.into(), pk.into())).collect())
176 }
177
178 fn new_prp_init(secret: &SecretKey) -> hopr_types::crypto::errors::Result<IvKey<Self::PRP>> {
180 generate_key_iv(secret, HASH_KEY_PRP, None)
181 }
182
183 fn new_reply_prp_init(secret: &SecretKey16, salt: &[u8]) -> hopr_types::crypto::errors::Result<IvKey<Self::PRP>> {
185 generate_key_iv(secret, HASH_KEY_REPLY_PRP, Some(salt))
186 }
187}
188
189#[cfg(test)]
190pub(crate) mod tests {
191 use subtle::ConstantTimeEq;
192
193 use super::*;
194
195 #[allow(clippy::type_complexity)]
196 pub fn generic_sphinx_suite_test<S: SphinxSuite>(node_count: usize) {
197 let (pub_keys, priv_keys): (Vec<(S::G, Alpha<<S::G as GroupElement<S::E>>::AlphaLen>)>, Vec<S::E>) = (0
198 ..node_count)
199 .map(|_| {
200 let pair = S::G::random_pair();
201 ((pair.0.clone(), pair.0.to_alpha()), pair.1)
202 })
203 .unzip();
204
205 let pub_keys_alpha = pub_keys
206 .iter()
207 .map(|(pk, alpha)| (pk.clone(), alpha))
208 .collect::<Vec<_>>();
209
210 let generated_shares = SharedKeys::<S::E, S::G>::generate(pub_keys_alpha.clone()).unwrap();
212 assert_eq!(
213 node_count,
214 generated_shares.secrets.len(),
215 "number of generated keys should be equal to the number of nodes"
216 );
217
218 let mut alpha_cpy = generated_shares.alpha.clone();
219 for (i, priv_key) in priv_keys.into_iter().enumerate() {
220 let (alpha, secret) =
221 SharedKeys::<S::E, S::G>::forward_transform(&alpha_cpy, &priv_key, &pub_keys[i].1).unwrap();
222
223 assert_eq!(
224 secret.ct_eq(&generated_shares.secrets[i]).unwrap_u8(),
225 1,
226 "forward transform should yield the same shared secret"
227 );
228
229 alpha_cpy = alpha;
230 }
231 }
232}