enigma-identity 0.1.0

Enigma Identity: local identity + X3DH bundle + shared secret derivation
Documentation
use crate::error::{EnigmaIdentityError, Result};
use crate::types::{SharedSecret, X3dhBundle, X3dhInitiation, X3dhResponderKeys};
use hkdf::Hkdf;
use rand::rngs::OsRng;
use sha2::Sha256;
use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecret};

pub fn x3dh_initiate(bundle: &X3dhBundle) -> Result<(X3dhInitiation, SharedSecret)> {
    crate::identity::LocalIdentity::verify_bundle(bundle)?;

    let mut rng = OsRng;
    let eph = XSecret::random_from_rng(&mut rng);
    let eph_pk = XPublicKey::from(&eph);

    let responder_spk = XPublicKey::from(bundle.signed_prekey_public_key);

    let dh = eph.diffie_hellman(&responder_spk);
    let shared = derive_shared_secret(&dh.to_bytes(), b"enigma-x3dh-v1")?;

    Ok((
        X3dhInitiation {
            initiator_ephemeral_public_key: eph_pk.to_bytes(),
        },
        shared,
    ))
}

pub fn x3dh_respond(
    responder: &X3dhResponderKeys,
    initiation: &X3dhInitiation,
) -> Result<SharedSecret> {
    let initiator_eph_pk = XPublicKey::from(initiation.initiator_ephemeral_public_key);

    let responder_spk = XSecret::from(responder.signed_prekey_secret_key);
    let dh = responder_spk.diffie_hellman(&initiator_eph_pk);

    derive_shared_secret(&dh.to_bytes(), b"enigma-x3dh-v1")
}

fn derive_shared_secret(ikm: &[u8], info: &[u8]) -> Result<SharedSecret> {
    let hk = Hkdf::<Sha256>::new(None, ikm);
    let mut out = [0u8; 32];
    hk.expand(info, &mut out)
        .map_err(|_| EnigmaIdentityError::CryptoError)?;
    Ok(SharedSecret::new(out))
}