hopr_crypto_sphinx/
shared_keys.rs1use 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
8pub type SharedSecret = SecretValue<typenum::U32>;
10
11pub trait Scalar: Mul<Output = Self> + Sized {
13 fn random() -> Self;
15
16 fn from_bytes(bytes: &[u8]) -> hopr_crypto_types::errors::Result<Self>;
18}
19
20pub type Alpha<A> = GenericArray<u8, A>;
23
24pub trait GroupElement<E: Scalar>: Clone + for<'a> Mul<&'a E, Output = Self> {
28 type AlphaLen: ArrayLength;
30
31 fn to_alpha(&self) -> Alpha<Self::AlphaLen>;
33
34 fn from_alpha(alpha: Alpha<Self::AlphaLen>) -> hopr_crypto_types::errors::Result<Self>;
36
37 fn generate(scalar: &E) -> Self;
39
40 fn is_valid(&self) -> bool;
42
43 fn random_pair() -> (Self, E) {
47 let scalar = E::random();
48 (Self::generate(&scalar), scalar)
49 }
50
51 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
60pub 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 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 let (mut alpha_prev, mut coeff_prev) = G::random_pair();
82
83 let alpha = alpha_prev.to_alpha();
85
86 let keys_len = peer_group_elements.len();
88 for (i, (group_element, salt)) in peer_group_elements.into_iter().enumerate() {
89 let shared_secret = group_element.mul(&coeff_prev);
91
92 shared_keys.push(shared_secret.extract_key(HASH_KEY_SPHINX_SECRET, salt.as_ref()));
94
95 if i == keys_len - 1 {
97 break;
98 }
99
100 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 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 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
148pub trait SphinxSuite {
150 type P: Keypair;
152
153 type E: Scalar + for<'a> From<&'a Self::P>;
155
156 type G: GroupElement<Self::E> + for<'a> From<&'a <Self::P as Keypair>::Public>;
158
159 type PRP: crypto_traits::PRP + crypto_traits::KeyIvInit;
161
162 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 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 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 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}