#![allow(dead_code)]
use crate::{GroupElement, Scalar};
use rand_core::{CryptoRng, RngCore};
use std::ops::{Add, Mul, Sub};
use cryptoxide::blake2b::Blake2b;
use cryptoxide::chacha20::ChaCha20;
use cryptoxide::digest::Digest;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PublicKey {
pub pk: GroupElement,
}
#[derive(Clone)]
pub struct SecretKey {
pub sk: Scalar,
}
#[derive(Clone)]
pub struct Keypair {
pub secret_key: SecretKey,
pub public_key: PublicKey,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Ciphertext {
pub(crate) e1: GroupElement,
pub(crate) e2: GroupElement,
}
#[derive(Clone)]
pub struct HybridCiphertext {
pub(crate) e1: GroupElement,
pub(crate) e2: Box<[u8]>,
}
pub struct SymmetricKey {
pub(crate) group_repr: GroupElement,
}
impl PublicKey {
pub const BYTES_LEN: usize = GroupElement::BYTES_LEN;
pub fn to_bytes(&self) -> Vec<u8> {
self.pk.to_bytes().to_vec()
}
pub fn from_bytes(buf: &[u8]) -> Option<Self> {
Some(Self {
pk: GroupElement::from_bytes(buf)?,
})
}
pub(crate) fn encrypt_point<R>(&self, message: &GroupElement, rng: &mut R) -> Ciphertext
where
R: RngCore + CryptoRng,
{
let r = Scalar::random(rng);
self.encrypt_point_with_r(message, &r)
}
fn encrypt_point_return_r<R>(&self, message: &GroupElement, rng: &mut R) -> (Ciphertext, Scalar)
where
R: RngCore + CryptoRng,
{
let r = Scalar::random(rng);
(self.encrypt_point_with_r(message, &r), r)
}
fn encrypt_point_with_r(&self, message: &GroupElement, randomness: &Scalar) -> Ciphertext {
Ciphertext {
e1: &GroupElement::generator() * randomness,
e2: message + &(&self.pk * randomness),
}
}
pub(crate) fn encrypt<R>(&self, message: &Scalar, rng: &mut R) -> Ciphertext
where
R: RngCore + CryptoRng,
{
self.encrypt_point(&(&GroupElement::generator() * message), rng)
}
pub(crate) fn encrypt_return_r<R>(&self, message: &Scalar, rng: &mut R) -> (Ciphertext, Scalar)
where
R: RngCore + CryptoRng,
{
self.encrypt_point_return_r(&(&GroupElement::generator() * message), rng)
}
pub(crate) fn encrypt_with_r(&self, message: &Scalar, randomness: &Scalar) -> Ciphertext {
self.encrypt_point_with_r(&(&GroupElement::generator() * message), randomness)
}
pub(crate) fn hybrid_encrypt<R>(&self, message: &[u8], rng: &mut R) -> HybridCiphertext
where
R: RngCore + CryptoRng,
{
let encryption_randomness = Scalar::random(rng);
let symmetric_key = SymmetricKey {
group_repr: &self.pk * &encryption_randomness,
};
let e1 = encryption_randomness * GroupElement::generator();
let e2 = symmetric_key.process(message).into_boxed_slice();
HybridCiphertext { e1, e2 }
}
}
impl SecretKey {
pub const BYTES_LEN: usize = Scalar::BYTES_LEN;
pub fn generate<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = Scalar::random(rng);
Self { sk }
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
Scalar::from_bytes(bytes).map(|sk| Self { sk })
}
pub(crate) fn recover_symmetric_key(&self, ciphertext: &HybridCiphertext) -> SymmetricKey {
SymmetricKey {
group_repr: &ciphertext.e1 * &self.sk,
}
}
#[allow(dead_code)]
pub(crate) fn hybrid_decrypt(&self, ciphertext: &HybridCiphertext) -> Vec<u8> {
self.recover_symmetric_key(ciphertext)
.process(&ciphertext.e2)
}
pub(crate) fn decrypt_point(&self, cipher: &Ciphertext) -> GroupElement {
&(&cipher.e1 * &self.sk.negate()) + &cipher.e2
}
}
impl SymmetricKey {
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let exponent = Scalar::random(rng);
SymmetricKey {
group_repr: GroupElement::generator() * &exponent,
}
}
fn initialise_encryption(&self) -> ChaCha20 {
let mut out = [0u8; 44];
let mut h = Blake2b::new(44);
h.input(&self.group_repr.to_bytes());
h.result(&mut out);
ChaCha20::new(&out[0..32], &out[32..44])
}
fn process(&self, m: &[u8]) -> Vec<u8> {
let mut key = self.initialise_encryption();
let mut dat = m.to_vec();
key.process_mut(&mut dat);
dat
}
}
impl Keypair {
#[allow(dead_code)]
pub fn from_secretkey(secret_key: SecretKey) -> Self {
let public_key = PublicKey {
pk: &GroupElement::generator() * &secret_key.sk,
};
Keypair {
secret_key,
public_key,
}
}
pub fn generate<R: RngCore + CryptoRng>(rng: &mut R) -> Keypair {
let sk = Scalar::random(rng);
let pk = &GroupElement::generator() * &sk;
Keypair {
secret_key: SecretKey { sk },
public_key: PublicKey { pk },
}
}
}
impl Ciphertext {
pub const BYTES_LEN: usize = GroupElement::BYTES_LEN * 2;
pub fn zero() -> Self {
Ciphertext {
e1: GroupElement::zero(),
e2: GroupElement::zero(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut r = Vec::with_capacity(Self::BYTES_LEN);
r.extend_from_slice(self.e1.to_bytes().as_ref());
r.extend_from_slice(self.e2.to_bytes().as_ref());
debug_assert_eq!(r.len(), Self::BYTES_LEN);
r
}
pub fn from_bytes(slice: &[u8]) -> Option<Ciphertext> {
let e1 = GroupElement::from_bytes(&slice[..GroupElement::BYTES_LEN])?;
let e2 = GroupElement::from_bytes(&slice[GroupElement::BYTES_LEN..])?;
Some(Ciphertext { e1, e2 })
}
pub fn elements(&self) -> (&GroupElement, &GroupElement) {
(&self.e1, &self.e2)
}
}
impl HybridCiphertext {
pub fn to_bytes(&self) -> Vec<u8> {
let mut r = Vec::with_capacity(GroupElement::BYTES_LEN + self.e2.len());
r.extend_from_slice(self.e1.to_bytes().as_ref());
r.extend_from_slice(self.e2.as_ref());
r
}
pub fn from_bytes(slice: &[u8]) -> Option<HybridCiphertext> {
let e1 = GroupElement::from_bytes(&slice[..GroupElement::BYTES_LEN])?;
let e2 = slice[GroupElement::BYTES_LEN..].to_vec().into_boxed_slice();
Some(HybridCiphertext { e1, e2 })
}
}
impl<'a, 'b> Add<&'b Ciphertext> for &'a Ciphertext {
type Output = Ciphertext;
fn add(self, other: &'b Ciphertext) -> Ciphertext {
Ciphertext {
e1: &self.e1 + &other.e1,
e2: &self.e2 + &other.e2,
}
}
}
std_ops_gen!(Ciphertext, Add, Ciphertext, Ciphertext, add);
impl<'a, 'b> Sub<&'b Ciphertext> for &'a Ciphertext {
type Output = Ciphertext;
fn sub(self, other: &'b Ciphertext) -> Ciphertext {
Ciphertext {
e1: &self.e1 - &other.e1,
e2: &self.e2 - &other.e2,
}
}
}
std_ops_gen!(Ciphertext, Sub, Ciphertext, Ciphertext, sub);
impl<'a, 'b> Mul<&'b Scalar> for &'a Ciphertext {
type Output = Ciphertext;
fn mul(self, rhs: &'b Scalar) -> Self::Output {
Ciphertext {
e1: &self.e1 * rhs,
e2: &self.e2 * rhs,
}
}
}
std_ops_gen!(Ciphertext, Mul, Scalar, Ciphertext, mul);
impl<'a> Mul<u64> for &'a Ciphertext {
type Output = Ciphertext;
fn mul(self, rhs: u64) -> Self::Output {
Ciphertext {
e1: &self.e1 * rhs,
e2: &self.e2 * rhs,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;
#[test]
fn zero() {
let cipher = Ciphertext {
e1: GroupElement::zero(),
e2: GroupElement::zero(),
};
assert_eq!(Ciphertext::zero(), cipher)
}
#[test]
fn encrypt_decrypt_point() {
let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
for n in 1..5 {
let keypair = Keypair::generate(&mut rng);
let m = GroupElement::generator() * Scalar::from_u64(n * 24);
let cipher = keypair.public_key.encrypt_point(&m, &mut rng);
let r = keypair.secret_key.decrypt_point(&cipher);
assert_eq!(m, r)
}
}
#[test]
fn encrypt_decrypt() {
let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
for n in 1..5 {
let keypair = Keypair::generate(&mut rng);
let m = Scalar::from_u64(n * 24);
let cipher = keypair.public_key.encrypt(&m, &mut rng);
let r = keypair.secret_key.decrypt_point(&cipher);
assert_eq!(m * GroupElement::generator(), r)
}
}
#[test]
fn symmetric_encrypt_decrypt() {
let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
let k = SecretKey::generate(&mut rng);
let k = Keypair::from_secretkey(k);
let m = [1, 3, 4, 5, 6, 7];
let encrypted = &k.public_key.hybrid_encrypt(&m, &mut rng);
let result = &k.secret_key.hybrid_decrypt(encrypted);
assert_eq!(&m[..], &result[..])
}
#[test]
fn hybrid_serialisation() {
let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
let k = SecretKey::generate(&mut rng);
let k = Keypair::from_secretkey(k);
let m = [1, 3, 4, 5, 6, 7];
let encrypted = &k.public_key.hybrid_encrypt(&m, &mut rng);
let serialised_ciphertext = encrypted.to_bytes();
let deserialised_ciphertext = HybridCiphertext::from_bytes(&serialised_ciphertext);
assert!(deserialised_ciphertext.is_some());
let result = &k
.secret_key
.hybrid_decrypt(&deserialised_ciphertext.unwrap());
assert_eq!(&m[..], &result[..])
}
}