use crate::{Result, SodiumError};
use std::convert::TryFrom;
pub const PUBLICKEYBYTES: usize =
libsodium_sys::crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize;
pub const SECRETKEYBYTES: usize =
libsodium_sys::crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize;
pub const NONCEBYTES: usize =
libsodium_sys::crypto_box_curve25519xchacha20poly1305_NONCEBYTES as usize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Nonce([u8; NONCEBYTES]);
impl Nonce {
pub fn generate() -> Self {
let mut bytes = [0u8; NONCEBYTES];
crate::random::fill_bytes(&mut bytes);
Self(bytes)
}
pub fn from_bytes(bytes: [u8; NONCEBYTES]) -> Self {
Self(bytes)
}
pub fn try_from_slice(bytes: &[u8]) -> Result<Self> {
if bytes.len() != NONCEBYTES {
return Err(SodiumError::InvalidNonce(format!(
"nonce must be exactly {NONCEBYTES} bytes"
)));
}
let mut nonce_bytes = [0u8; NONCEBYTES];
nonce_bytes.copy_from_slice(bytes);
Ok(Self(nonce_bytes))
}
pub fn as_bytes(&self) -> &[u8; NONCEBYTES] {
&self.0
}
pub fn as_bytes_mut(&mut self) -> &mut [u8; NONCEBYTES] {
&mut self.0
}
pub const fn from_bytes_exact(bytes: [u8; NONCEBYTES]) -> Self {
Self(bytes)
}
}
impl AsRef<[u8]> for Nonce {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Nonce {
type Error = SodiumError;
fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::try_from_slice(slice)
}
}
impl From<[u8; NONCEBYTES]> for Nonce {
fn from(bytes: [u8; NONCEBYTES]) -> Self {
Self(bytes)
}
}
impl From<Nonce> for [u8; NONCEBYTES] {
fn from(nonce: Nonce) -> [u8; NONCEBYTES] {
nonce.0
}
}
pub const MACBYTES: usize = libsodium_sys::crypto_box_curve25519xchacha20poly1305_MACBYTES as usize;
pub const SEEDBYTES: usize =
libsodium_sys::crypto_box_curve25519xchacha20poly1305_SEEDBYTES as usize;
pub const SEALBYTES: usize =
libsodium_sys::crypto_box_curve25519xchacha20poly1305_SEALBYTES as usize;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PublicKey([u8; PUBLICKEYBYTES]);
impl PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != PUBLICKEYBYTES {
return Err(SodiumError::InvalidInput(format!(
"public key must be exactly {PUBLICKEYBYTES} bytes"
)));
}
let mut pk = [0u8; PUBLICKEYBYTES];
pk.copy_from_slice(bytes);
Ok(PublicKey(pk))
}
pub fn as_bytes(&self) -> &[u8; PUBLICKEYBYTES] {
&self.0
}
pub const fn from_bytes_exact(bytes: [u8; PUBLICKEYBYTES]) -> Self {
Self(bytes)
}
}
impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = SodiumError;
fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::from_bytes(slice)
}
}
impl From<[u8; PUBLICKEYBYTES]> for PublicKey {
fn from(bytes: [u8; PUBLICKEYBYTES]) -> Self {
Self(bytes)
}
}
impl From<PublicKey> for [u8; PUBLICKEYBYTES] {
fn from(key: PublicKey) -> [u8; PUBLICKEYBYTES] {
key.0
}
}
#[derive(Debug, Clone, Eq, PartialEq, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
pub struct SecretKey([u8; SECRETKEYBYTES]);
impl SecretKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != SECRETKEYBYTES {
return Err(SodiumError::InvalidInput(format!(
"secret key must be exactly {SECRETKEYBYTES} bytes"
)));
}
let mut sk = [0u8; SECRETKEYBYTES];
sk.copy_from_slice(bytes);
Ok(SecretKey(sk))
}
pub fn as_bytes(&self) -> &[u8; SECRETKEYBYTES] {
&self.0
}
pub const fn from_bytes_exact(bytes: [u8; SECRETKEYBYTES]) -> Self {
Self(bytes)
}
}
impl AsRef<[u8]> for SecretKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for SecretKey {
type Error = SodiumError;
fn try_from(slice: &[u8]) -> std::result::Result<Self, Self::Error> {
Self::from_bytes(slice)
}
}
impl From<[u8; SECRETKEYBYTES]> for SecretKey {
fn from(bytes: [u8; SECRETKEYBYTES]) -> Self {
Self(bytes)
}
}
impl From<SecretKey> for [u8; SECRETKEYBYTES] {
fn from(key: SecretKey) -> [u8; SECRETKEYBYTES] {
key.0
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KeyPair {
pub public_key: PublicKey,
pub secret_key: SecretKey,
}
impl KeyPair {
pub fn new(public_key: PublicKey, secret_key: SecretKey) -> Self {
Self {
public_key,
secret_key,
}
}
pub fn generate() -> Result<Self> {
let mut pk = [0u8; PUBLICKEYBYTES];
let mut sk = [0u8; SECRETKEYBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_keypair(
pk.as_mut_ptr(),
sk.as_mut_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("key generation failed".into()));
}
Ok(Self {
public_key: PublicKey(pk),
secret_key: SecretKey(sk),
})
}
pub fn from_seed(seed: &[u8]) -> Result<Self> {
if seed.len() != SEEDBYTES {
return Err(SodiumError::InvalidInput(format!(
"seed must be exactly {SEEDBYTES} bytes"
)));
}
let mut pk = [0u8; PUBLICKEYBYTES];
let mut sk = [0u8; SECRETKEYBYTES];
unsafe {
let ret = libsodium_sys::crypto_box_curve25519xchacha20poly1305_seed_keypair(
pk.as_mut_ptr(),
sk.as_mut_ptr(),
seed.as_ptr(),
);
if ret != 0 {
return Err(SodiumError::OperationError(
"key generation from seed failed".into(),
));
}
}
Ok(Self {
public_key: PublicKey(pk),
secret_key: SecretKey(sk),
})
}
pub fn encrypt(
&self,
message: &[u8],
nonce: &Nonce,
recipient_pk: &PublicKey,
) -> Result<Vec<u8>> {
encrypt(message, nonce, recipient_pk, &self.secret_key)
}
pub fn decrypt(
&self,
ciphertext: &[u8],
nonce: &Nonce,
sender_pk: &PublicKey,
) -> Result<Vec<u8>> {
decrypt(ciphertext, nonce, sender_pk, &self.secret_key)
}
pub fn encrypt_detached(
&self,
message: &[u8],
nonce: &Nonce,
recipient_pk: &PublicKey,
) -> Result<(Vec<u8>, [u8; MACBYTES])> {
encrypt_detached(message, nonce, recipient_pk, &self.secret_key)
}
pub fn decrypt_detached(
&self,
ciphertext: &[u8],
mac: &[u8; MACBYTES],
nonce: &Nonce,
sender_pk: &PublicKey,
) -> Result<Vec<u8>> {
decrypt_detached(ciphertext, mac, nonce, sender_pk, &self.secret_key)
}
pub fn seal(&self, message: &[u8]) -> Result<Vec<u8>> {
seal(message, &self.public_key)
}
pub fn seal_open(&self, sealed_box: &[u8]) -> Result<Vec<u8>> {
seal_open(sealed_box, &self.public_key, &self.secret_key)
}
}
pub fn encrypt(
message: &[u8],
nonce: &Nonce,
recipient_pk: &PublicKey,
sender_sk: &SecretKey,
) -> Result<Vec<u8>> {
let mut ciphertext = vec![0u8; message.len() + MACBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_easy(
ciphertext.as_mut_ptr(),
message.as_ptr(),
message.len() as u64,
nonce.as_ref().as_ptr(),
recipient_pk.as_bytes().as_ptr(),
sender_sk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("encryption failed".into()));
}
Ok(ciphertext)
}
pub fn decrypt(
ciphertext: &[u8],
nonce: &Nonce,
sender_pk: &PublicKey,
recipient_sk: &SecretKey,
) -> Result<Vec<u8>> {
if ciphertext.len() < MACBYTES {
return Err(SodiumError::InvalidInput("ciphertext too short".into()));
}
let mut message = vec![0u8; ciphertext.len() - MACBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_open_easy(
message.as_mut_ptr(),
ciphertext.as_ptr(),
ciphertext.len() as u64,
nonce.as_ref().as_ptr(),
sender_pk.as_bytes().as_ptr(),
recipient_sk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("decryption failed".into()));
}
Ok(message)
}
pub fn encrypt_detached(
message: &[u8],
nonce: &Nonce,
recipient_pk: &PublicKey,
sender_sk: &SecretKey,
) -> Result<(Vec<u8>, [u8; MACBYTES])> {
let mut ciphertext = vec![0u8; message.len()];
let mut mac = [0u8; MACBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_detached(
ciphertext.as_mut_ptr(),
mac.as_mut_ptr(),
message.as_ptr(),
message.len() as u64,
nonce.as_ref().as_ptr(),
recipient_pk.as_bytes().as_ptr(),
sender_sk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("encryption failed".into()));
}
Ok((ciphertext, mac))
}
pub fn decrypt_detached(
ciphertext: &[u8],
mac: &[u8; MACBYTES],
nonce: &Nonce,
sender_pk: &PublicKey,
recipient_sk: &SecretKey,
) -> Result<Vec<u8>> {
let mut message = vec![0u8; ciphertext.len()];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_open_detached(
message.as_mut_ptr(),
ciphertext.as_ptr(),
mac.as_ptr(),
ciphertext.len() as u64,
nonce.as_ref().as_ptr(),
sender_pk.as_bytes().as_ptr(),
recipient_sk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError("decryption failed".into()));
}
Ok(message)
}
pub fn seal(message: &[u8], recipient_pk: &PublicKey) -> Result<Vec<u8>> {
let mut sealed_box = vec![0u8; message.len() + SEALBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_seal(
sealed_box.as_mut_ptr(),
message.as_ptr(),
message.len() as u64,
recipient_pk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError(
"sealed box encryption failed".into(),
));
}
Ok(sealed_box)
}
pub fn seal_open(
sealed_box: &[u8],
recipient_pk: &PublicKey,
recipient_sk: &SecretKey,
) -> Result<Vec<u8>> {
if sealed_box.len() < SEALBYTES {
return Err(SodiumError::InvalidInput("sealed box too short".into()));
}
let mut message = vec![0u8; sealed_box.len() - SEALBYTES];
let result = unsafe {
libsodium_sys::crypto_box_curve25519xchacha20poly1305_seal_open(
message.as_mut_ptr(),
sealed_box.as_ptr(),
sealed_box.len() as u64,
recipient_pk.as_bytes().as_ptr(),
recipient_sk.as_bytes().as_ptr(),
)
};
if result != 0 {
return Err(SodiumError::OperationError(
"sealed box decryption failed".into(),
));
}
Ok(message)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keypair_generation() {
let keypair = KeyPair::generate().unwrap();
assert_eq!(keypair.public_key.as_bytes().len(), PUBLICKEYBYTES);
assert_eq!(keypair.secret_key.as_bytes().len(), SECRETKEYBYTES);
}
#[test]
fn test_seed_keypair() {
let mut seed = [0u8; SEEDBYTES];
crate::random::fill_bytes(&mut seed);
let keypair1 = KeyPair::from_seed(&seed).unwrap();
let keypair2 = KeyPair::from_seed(&seed).unwrap();
assert_eq!(keypair1.public_key, keypair2.public_key);
assert_eq!(keypair1.secret_key, keypair2.secret_key);
}
#[test]
fn test_encrypt_decrypt() {
let alice = KeyPair::generate().unwrap();
let bob = KeyPair::generate().unwrap();
let nonce = Nonce::generate();
let message = b"Hello, XChaCha20-Poly1305!";
let ciphertext = encrypt(message, &nonce, &bob.public_key, &alice.secret_key).unwrap();
let decrypted = decrypt(&ciphertext, &nonce, &alice.public_key, &bob.secret_key).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_encrypt_decrypt_detached() {
let alice = KeyPair::generate().unwrap();
let bob = KeyPair::generate().unwrap();
let nonce = Nonce::generate();
let message = b"Hello, XChaCha20-Poly1305 with detached MAC!";
let (ciphertext, mac) =
encrypt_detached(message, &nonce, &bob.public_key, &alice.secret_key).unwrap();
let decrypted = decrypt_detached(
&ciphertext,
&mac,
&nonce,
&alice.public_key,
&bob.secret_key,
)
.unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_seal_open() {
let bob = KeyPair::generate().unwrap();
let message = b"Anonymous message for Bob!";
let sealed_box = seal(message, &bob.public_key).unwrap();
let decrypted = seal_open(&sealed_box, &bob.public_key, &bob.secret_key).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_nonce_traits() {
let bytes = [0x42; NONCEBYTES];
let nonce = Nonce::try_from(&bytes[..]).unwrap();
assert_eq!(nonce.as_bytes(), &bytes);
let invalid_bytes = [0x42; NONCEBYTES - 1];
assert!(Nonce::try_from(&invalid_bytes[..]).is_err());
let bytes = [0x43; NONCEBYTES];
let nonce2 = Nonce::from(bytes);
assert_eq!(nonce2.as_bytes(), &bytes);
let extracted: [u8; NONCEBYTES] = nonce2.into();
assert_eq!(extracted, bytes);
let nonce3 = Nonce::generate();
let slice_ref: &[u8] = nonce3.as_ref();
assert_eq!(slice_ref.len(), NONCEBYTES);
}
#[test]
fn test_publickey_traits() {
let bytes = [0x42; PUBLICKEYBYTES];
let key = PublicKey::try_from(&bytes[..]).unwrap();
assert_eq!(key.as_bytes(), &bytes);
let invalid_bytes = [0x42; PUBLICKEYBYTES - 1];
assert!(PublicKey::try_from(&invalid_bytes[..]).is_err());
let bytes = [0x43; PUBLICKEYBYTES];
let key2 = PublicKey::from(bytes);
assert_eq!(key2.as_bytes(), &bytes);
let extracted: [u8; PUBLICKEYBYTES] = key2.into();
assert_eq!(extracted, bytes);
let keypair = KeyPair::generate().unwrap();
let slice_ref: &[u8] = keypair.public_key.as_ref();
assert_eq!(slice_ref.len(), PUBLICKEYBYTES);
}
#[test]
fn test_secretkey_traits() {
let bytes = [0x42; SECRETKEYBYTES];
let key = SecretKey::try_from(&bytes[..]).unwrap();
assert_eq!(key.as_bytes(), &bytes);
let invalid_bytes = [0x42; SECRETKEYBYTES - 1];
assert!(SecretKey::try_from(&invalid_bytes[..]).is_err());
let bytes = [0x43; SECRETKEYBYTES];
let key2 = SecretKey::from(bytes);
assert_eq!(key2.as_bytes(), &bytes);
let extracted: [u8; SECRETKEYBYTES] = key2.into();
assert_eq!(extracted, bytes);
let keypair = KeyPair::generate().unwrap();
let slice_ref: &[u8] = keypair.secret_key.as_ref();
assert_eq!(slice_ref.len(), SECRETKEYBYTES);
}
}