use crate::crypto_secretstream::{Key, PullState, PushState, SecretstreamError, Tag};
use crate::hazmat::dstu9041::curve512::{base_point, is_valid_scalar, point_from_x, Point};
use crate::hazmat::dstu9041::encryption512::{
decrypt as dstu9041_decrypt, encrypt as dstu9041_encrypt,
};
use crate::hazmat::dstu9041::fp512::from_candidate_bytes;
use crate::randombytes::{randombytes_buf, RandomError};
use core::fmt;
use zeroize::Zeroize;
const SEED_LEN: usize = 32;
const SEED_BITS: usize = SEED_LEN * 8;
const KEM_CIPHERTEXT_LEN: usize = 256;
const HEADER_LEN: usize = 32;
const TAG_LEN: usize = 16;
const KDF_CONTEXT: &[u8; 8] = b"cryptbx5";
use crate::hazmat::kupyna_kdf::Kupyna256Kdf;
#[derive(Debug)]
pub enum SealError {
Random(RandomError),
}
impl fmt::Display for SealError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SealError::Random(e) => write!(f, "{e}"),
}
}
}
impl core::error::Error for SealError {}
impl From<RandomError> for SealError {
fn from(e: RandomError) -> Self {
SealError::Random(e)
}
}
#[derive(Debug)]
pub enum OpenError {
Truncated,
InvalidCiphertext,
}
impl fmt::Display for OpenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OpenError::Truncated => write!(f, "input too short to contain a sealed message"),
OpenError::InvalidCiphertext => write!(f, "authentication failed"),
}
}
}
impl core::error::Error for OpenError {}
fn random_valid_scalar() -> Result<[u8; 64], RandomError> {
loop {
let mut candidate = [0u8; 64];
randombytes_buf(&mut candidate)?;
if is_valid_scalar(&candidate) {
return Ok(candidate);
}
candidate.zeroize();
}
}
pub struct SecretKey([u8; 64]);
impl Drop for SecretKey {
fn drop(&mut self) {
self.0.zeroize();
}
}
impl SecretKey {
#[must_use]
pub fn from_bytes(e: &[u8; 64]) -> Option<Self> {
if is_valid_scalar(e) {
Some(SecretKey(*e))
} else {
None
}
}
#[cfg(any(feature = "std", feature = "getrandom"))]
pub fn generate() -> Result<Self, RandomError> {
random_valid_scalar().map(SecretKey)
}
#[must_use]
pub fn to_bytes(&self) -> [u8; 64] {
self.0
}
#[must_use]
pub fn public_key(&self) -> PublicKey {
PublicKey(base_point().scalar_multiply(&self.0))
}
}
#[derive(Clone, Copy)]
pub struct PublicKey(Point);
impl PublicKey {
#[must_use]
pub fn from_bytes(bytes: &[u8; 64]) -> Option<Self> {
from_candidate_bytes(bytes)
.and_then(point_from_x)
.map(PublicKey)
}
#[must_use]
pub fn to_bytes(&self) -> [u8; 64] {
self.0.x.to_be_bytes()
}
}
pub fn seal(message: &[u8], recipient: &PublicKey) -> Result<Vec<u8>, SealError> {
let mut seed = [0u8; SEED_LEN];
randombytes_buf(&mut seed)?;
let mut epsilon = random_valid_scalar()?;
let Ok(kem_ciphertext) = dstu9041_encrypt(&seed, SEED_BITS, recipient.0, &epsilon) else {
unreachable!("seed/epsilon are always valid by construction")
};
epsilon.zeroize();
let mut stream_key_bytes = Kupyna256Kdf::derive_subkey(&seed, 0, KDF_CONTEXT);
seed.zeroize();
let key = Key::from_bytes(stream_key_bytes);
stream_key_bytes.zeroize();
let (mut push, header) = match PushState::init(&key) {
Ok(ok) => ok,
Err(SecretstreamError::Random(e)) => return Err(SealError::Random(e)),
Err(_) => unreachable!("PushState::init only ever fails via SecretstreamError::Random"),
};
let mut ciphertext = vec![0u8; message.len()];
let Ok(tag) = push.push(Tag::Final, message, &mut ciphertext) else {
unreachable!(
"ciphertext.len() == message.len() by construction; stream freshly initialized, \
never finalized yet"
)
};
let mut out = Vec::with_capacity(KEM_CIPHERTEXT_LEN + HEADER_LEN + ciphertext.len() + TAG_LEN);
out.extend_from_slice(&kem_ciphertext);
out.extend_from_slice(&header);
out.extend_from_slice(&ciphertext);
out.extend_from_slice(&tag);
Ok(out)
}
pub fn open(sealed: &[u8], secret: &SecretKey) -> Result<Vec<u8>, OpenError> {
const MIN_LEN: usize = KEM_CIPHERTEXT_LEN + HEADER_LEN + TAG_LEN;
if sealed.len() < MIN_LEN {
return Err(OpenError::Truncated);
}
let mut kem_ciphertext = [0u8; KEM_CIPHERTEXT_LEN];
kem_ciphertext.copy_from_slice(&sealed[..KEM_CIPHERTEXT_LEN]);
let mut header = [0u8; HEADER_LEN];
header.copy_from_slice(&sealed[KEM_CIPHERTEXT_LEN..KEM_CIPHERTEXT_LEN + HEADER_LEN]);
let ciphertext_len = sealed.len() - MIN_LEN;
let ciphertext_start = KEM_CIPHERTEXT_LEN + HEADER_LEN;
let ciphertext = &sealed[ciphertext_start..ciphertext_start + ciphertext_len];
let tag = &sealed[ciphertext_start + ciphertext_len..];
let (m_tilde, bit_len) =
dstu9041_decrypt(&kem_ciphertext, &secret.0).map_err(|_| OpenError::InvalidCiphertext)?;
if bit_len != SEED_BITS {
let mut m_tilde = m_tilde;
m_tilde.zeroize();
return Err(OpenError::InvalidCiphertext);
}
let mut seed = [0u8; SEED_LEN];
seed.copy_from_slice(&m_tilde[m_tilde.len() - SEED_LEN..]);
let mut m_tilde = m_tilde;
m_tilde.zeroize();
let mut stream_key_bytes = Kupyna256Kdf::derive_subkey(&seed, 0, KDF_CONTEXT);
seed.zeroize();
let key = Key::from_bytes(stream_key_bytes);
stream_key_bytes.zeroize();
let mut pull = PullState::init(&key, &header);
let mut plaintext = vec![0u8; ciphertext_len];
pull.pull(Tag::Final.to_byte(), ciphertext, tag, &mut plaintext)
.map_err(|_| OpenError::InvalidCiphertext)?;
Ok(plaintext)
}