use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::crypto::aead::{AeadError, AeadNonce, aead_decrypt, aead_encrypt};
use crate::crypto::hkdf::{HkdfError, hkdf};
use crate::crypto::x25519::{PublicKey, SecretKey, X25519Error};
use crate::crypto::{Rng, RngError};
use crate::key_bundle::{KeyBundleError, OneTimePreKeyId, PreKeyId};
use crate::traits::KeyBundle;
const KDF_INFO: &[u8; 7] = b"p2panda";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct X3dhCiphertext {
pub identity_key: PublicKey,
pub onetime_prekey_id: Option<OneTimePreKeyId>,
pub prekey_id: PreKeyId,
pub ciphertext: Vec<u8>,
pub ephemeral_key: PublicKey,
}
pub fn x3dh_encrypt<KB: KeyBundle>(
plaintext: &[u8],
our_identity_secret: &SecretKey,
their_prekey_bundle: &KB,
rng: &Rng,
) -> Result<X3dhCiphertext, X3dhError> {
their_prekey_bundle.verify()?;
let our_identity_key = our_identity_secret.verifying_key()?;
let our_ephemeral_secret = SecretKey::from_bytes(rng.random_array()?);
let our_ephemeral_key = our_ephemeral_secret.verifying_key()?;
let mut ikm = Vec::with_capacity({
if their_prekey_bundle.onetime_prekey().is_none() {
32 * 4
} else {
32 * 5
}
});
ikm.extend_from_slice(&[0xFFu8; 32]);
ikm.extend_from_slice(
&our_identity_secret.calculate_agreement(their_prekey_bundle.signed_prekey())?,
);
ikm.extend_from_slice(
&our_ephemeral_secret.calculate_agreement(their_prekey_bundle.identity_key())?,
);
ikm.extend_from_slice(
&our_ephemeral_secret.calculate_agreement(their_prekey_bundle.signed_prekey())?,
);
if let Some(onetime_prekey) = their_prekey_bundle.onetime_prekey() {
ikm.extend_from_slice(&our_ephemeral_secret.calculate_agreement(onetime_prekey)?);
}
let sk: [u8; 32] = {
let salt = vec![0_u8; 32];
hkdf(&salt, &ikm, Some(KDF_INFO))?
};
drop(our_ephemeral_secret);
drop(ikm);
let ad = {
let mut buf = Vec::new();
buf.extend_from_slice(our_identity_key.as_bytes());
buf.extend_from_slice(their_prekey_bundle.identity_key().as_bytes());
buf
};
let nonce: AeadNonce = hkdf(b"", &sk, None)?;
let ciphertext = aead_encrypt(&sk, plaintext, nonce, Some(&ad))?;
Ok(X3dhCiphertext {
ciphertext,
ephemeral_key: our_ephemeral_key,
identity_key: our_identity_key,
prekey_id: *their_prekey_bundle.signed_prekey(),
onetime_prekey_id: their_prekey_bundle.onetime_prekey_id(),
})
}
pub fn x3dh_decrypt(
their_ciphertext: &X3dhCiphertext,
our_identity_secret: &SecretKey,
our_prekey_secret: &SecretKey,
our_onetime_secret: Option<&SecretKey>,
) -> Result<Vec<u8>, X3dhError> {
let our_identity_key = our_identity_secret.verifying_key()?;
let mut ikm = Vec::with_capacity(if our_onetime_secret.is_none() {
32 * 4
} else {
32 * 5
});
ikm.extend_from_slice(&[0xFFu8; 32]);
ikm.extend_from_slice(&our_prekey_secret.calculate_agreement(&their_ciphertext.identity_key)?);
ikm.extend_from_slice(
&our_identity_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?,
);
ikm.extend_from_slice(&our_prekey_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?);
if let Some(our_onetime_secret) = our_onetime_secret {
ikm.extend_from_slice(
&our_onetime_secret.calculate_agreement(&their_ciphertext.ephemeral_key)?,
);
}
let sk: [u8; 32] = {
let salt = vec![0_u8; 32];
hkdf(&salt, &ikm, Some(KDF_INFO))?
};
drop(ikm);
let ad = {
let mut buf = Vec::new();
buf.extend_from_slice(their_ciphertext.identity_key.as_bytes());
buf.extend_from_slice(our_identity_key.as_bytes());
buf
};
let nonce: AeadNonce = hkdf(b"", &sk, None)?;
let plaintext = aead_decrypt(&sk, &their_ciphertext.ciphertext, nonce, Some(&ad))?;
Ok(plaintext)
}
#[derive(Debug, Error)]
pub enum X3dhError {
#[error(transparent)]
Rng(#[from] RngError),
#[error(transparent)]
Aead(#[from] AeadError),
#[error(transparent)]
Hkdf(#[from] HkdfError),
#[error(transparent)]
X25519(#[from] X25519Error),
#[error(transparent)]
KeyBundle(#[from] KeyBundleError),
}
#[cfg(test)]
mod tests {
use crate::crypto::Rng;
use crate::crypto::x25519::SecretKey;
use crate::key_bundle::{Lifetime, LongTermKeyBundle, OneTimeKeyBundle, OneTimePreKey, PreKey};
use super::{x3dh_decrypt, x3dh_encrypt};
#[test]
fn encrypt_decrypt() {
let rng = Rng::from_seed([1; 32]);
let bob_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let bob_prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let bob_signed_prekey = PreKey::new(
bob_prekey_secret.verifying_key().unwrap(),
Lifetime::default(),
);
let bob_onetime_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let bob_onetime_prekey = OneTimePreKey::new(bob_onetime_secret.verifying_key().unwrap(), 2);
let bob_prekey_signature = bob_signed_prekey.sign(&bob_identity_secret, &rng).unwrap();
let bob_prekey_bundle = OneTimeKeyBundle::new(
bob_identity_secret.verifying_key().unwrap(),
bob_signed_prekey,
bob_prekey_signature,
Some(bob_onetime_prekey),
);
let alice_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let ciphertext = x3dh_encrypt(
b"Hello, Panda!",
&alice_identity_secret,
&bob_prekey_bundle,
&rng,
)
.unwrap();
let plaintext = x3dh_decrypt(
&ciphertext,
&bob_identity_secret,
&bob_prekey_secret,
Some(&bob_onetime_secret),
)
.unwrap();
assert_eq!(b"Hello, Panda!", plaintext.as_slice());
}
#[test]
fn longterm_key_bundle() {
let rng = Rng::from_seed([1; 32]);
let bob_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let bob_prekey_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let bob_signed_prekey = PreKey::new(
bob_prekey_secret.verifying_key().unwrap(),
Lifetime::default(),
);
let bob_prekey_signature = bob_signed_prekey.sign(&bob_identity_secret, &rng).unwrap();
let bob_prekey_bundle = LongTermKeyBundle::new(
bob_identity_secret.verifying_key().unwrap(),
bob_signed_prekey,
bob_prekey_signature,
);
let alice_identity_secret = SecretKey::from_bytes(rng.random_array().unwrap());
let ciphertext = x3dh_encrypt(
b"Hello, Panda!",
&alice_identity_secret,
&bob_prekey_bundle,
&rng,
)
.unwrap();
let plaintext =
x3dh_decrypt(&ciphertext, &bob_identity_secret, &bob_prekey_secret, None).unwrap();
assert_eq!(b"Hello, Panda!", plaintext.as_slice());
}
}