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