use ed25519_dalek::{PublicKey as Ed25519PublicKey, Signature, Signer, Verifier};
use hkdf::Hkdf;
use rand::rngs::OsRng;
use sha2::Sha256;
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
use crate::Identity;
pub struct E2eeKeypair {
pub secret: StaticSecret,
pub public: X25519PublicKey,
}
pub fn generate_e2ee_keypair() -> E2eeKeypair {
let secret = StaticSecret::new(OsRng);
let public = X25519PublicKey::from(&secret);
E2eeKeypair { secret, public }
}
pub fn sign_e2ee_public(identity: &Identity, session_id: &[u8], public: &X25519PublicKey) -> Vec<u8> {
let msg = e2ee_message(session_id, public);
let signature: Signature = identity.keypair.sign(&msg);
signature.to_bytes().to_vec()
}
pub fn verify_e2ee_public(
peer_public: &Ed25519PublicKey,
session_id: &[u8],
public: &X25519PublicKey,
signature: &[u8],
) -> bool {
let Ok(signature) = Signature::from_bytes(signature) else {
return false;
};
let msg = e2ee_message(session_id, public);
peer_public.verify(&msg, &signature).is_ok()
}
pub fn derive_e2ee_shared_key(
secret: &StaticSecret,
remote_public: &X25519PublicKey,
session_id: &[u8],
) -> [u8; 32] {
let shared = secret.diffie_hellman(remote_public);
let hk = Hkdf::<Sha256>::new(Some(session_id), shared.as_bytes());
let mut out = [0u8; 32];
hk.expand(b"rift-e2ee-v1", &mut out)
.expect("hkdf expand");
out
}
fn e2ee_message(session_id: &[u8], public: &X25519PublicKey) -> Vec<u8> {
let mut msg = Vec::with_capacity(16 + session_id.len() + 32);
msg.extend_from_slice(b"rift-e2ee");
msg.extend_from_slice(session_id);
msg.extend_from_slice(public.as_bytes());
msg
}
pub fn public_key_from_bytes(bytes: [u8; 32]) -> X25519PublicKey {
X25519PublicKey::from(bytes)
}
pub fn ed25519_public_from_bytes(bytes: &[u8]) -> Option<Ed25519PublicKey> {
Ed25519PublicKey::from_bytes(bytes).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn e2ee_sign_verify_roundtrip() {
let identity = Identity::generate();
let session = [7u8; 32];
let kp = generate_e2ee_keypair();
let sig = sign_e2ee_public(&identity, &session, &kp.public);
let ok = verify_e2ee_public(&identity.keypair.public, &session, &kp.public, &sig);
assert!(ok);
}
#[test]
fn e2ee_shared_key_matches() {
let session = [9u8; 32];
let a = generate_e2ee_keypair();
let b = generate_e2ee_keypair();
let ka = derive_e2ee_shared_key(&a.secret, &b.public, &session);
let kb = derive_e2ee_shared_key(&b.secret, &a.public, &session);
assert_eq!(ka, kb);
}
#[test]
fn e2ee_bad_signature_rejected() {
let identity = Identity::generate();
let other = Identity::generate();
let session = [1u8; 32];
let kp = generate_e2ee_keypair();
let sig = sign_e2ee_public(&identity, &session, &kp.public);
let ok = verify_e2ee_public(&other.keypair.public, &session, &kp.public, &sig);
assert!(!ok);
}
}