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 fn generate(peer_group_elements: Vec<G>) -> hopr_crypto_types::errors::Result<SharedKeys<E, G>> {
74 let mut shared_keys = Vec::new();
75
76 let (mut alpha_prev, mut coeff_prev) = G::random_pair();
79
80 let alpha = alpha_prev.to_alpha();
82
83 let keys_len = peer_group_elements.len();
85 for (i, group_element) in peer_group_elements.into_iter().enumerate() {
86 let salt = group_element.to_alpha();
88 let shared_secret = group_element.mul(&coeff_prev);
89
90 shared_keys.push(shared_secret.extract_key(HASH_KEY_SPHINX_SECRET, &salt));
92
93 if i == keys_len - 1 {
95 break;
96 }
97
98 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 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 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
144pub trait SphinxSuite {
146 type P: Keypair;
148
149 type E: Scalar + for<'a> From<&'a Self::P>;
151
152 type G: GroupElement<Self::E> + for<'a> From<&'a <Self::P as Keypair>::Public>;
154
155 type PRP: crypto_traits::StreamCipher + crypto_traits::KeyIvInit;
157
158 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 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 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 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}