use crate::{
bls12381::primitives::{
group::{DST, GT, Scalar},
ops::hash_with_namespace,
variant::Variant,
},
sha256::Digest,
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bytes::{Buf, BufMut};
use commonware_codec::{EncodeSize, FixedSize, Read, ReadExt, Write};
use commonware_math::algebra::{Additive, CryptoGroup};
use commonware_utils::sequence::FixedBytes;
use rand_core::CryptoRng;
use thiserror::Error;
use zeroize::Zeroizing;
const DST: DST = b"TLE_BLS12381_XMD:SHA-256_SSWU_RO_H3_";
const BLOCK_SIZE: usize = Digest::SIZE;
pub type Block = FixedBytes<BLOCK_SIZE>;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum Error {
#[error("master public key is the group identity")]
InvalidPublicKey,
}
impl From<Digest> for Block {
fn from(digest: Digest) -> Self {
Block::new(digest.0)
}
}
#[derive(Hash, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ciphertext<V: Variant> {
pub u: V::Public,
pub v: Block,
pub w: Block,
}
impl<V: Variant> Write for Ciphertext<V> {
fn write(&self, buf: &mut impl BufMut) {
self.u.write(buf);
buf.put_slice(self.v.as_ref());
buf.put_slice(self.w.as_ref());
}
}
impl<V: Variant> Read for Ciphertext<V> {
type Cfg = ();
fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, commonware_codec::Error> {
let u = V::Public::read(buf)?;
let v = Block::read(buf)?;
let w = Block::read(buf)?;
Ok(Self { u, v, w })
}
}
impl<V: Variant> EncodeSize for Ciphertext<V> {
fn encode_size(&self) -> usize {
self.u.encode_size() + self.v.encode_size() + self.w.encode_size()
}
}
#[cfg(feature = "arbitrary")]
impl<V: Variant> arbitrary::Arbitrary<'_> for Ciphertext<V>
where
V::Public: for<'a> arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let ge = u.arbitrary()?;
let v = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
let w = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
Ok(Self { u: ge, v, w })
}
}
mod hash {
use super::*;
use crate::{Hasher, Sha256};
pub fn h2(gt: >) -> Block {
let gt = Zeroizing::new(gt.as_slice());
Sha256::hash(&[b"h2", gt.as_ref()]).into()
}
pub fn h3(sigma: &Block, message: &[u8]) -> Scalar {
let mut combined = Zeroizing::new(Vec::with_capacity(sigma.len() + message.len()));
combined.extend_from_slice(sigma.as_ref());
combined.extend_from_slice(message);
Scalar::map(DST, &combined)
}
pub fn h4(sigma: &Block) -> Block {
Sha256::hash(&[b"h4", sigma.as_ref()]).into()
}
}
#[inline]
fn xor(a: &Block, b: &Block) -> Block {
let a = a.as_ref();
let b = b.as_ref();
Block::new(core::array::from_fn(|i| a[i] ^ b[i]))
}
pub fn encrypt<R: CryptoRng, V: Variant>(
rng: &mut R,
public: V::Public,
target: (&[u8], &[u8]),
message: &Block,
) -> Result<Ciphertext<V>, Error> {
if public == V::Public::zero() {
return Err(Error::InvalidPublicKey);
}
let (namespace, target) = target;
let q_id = hash_with_namespace::<V>(V::MESSAGE, namespace, target);
let mut sigma_array = Zeroizing::new([0u8; BLOCK_SIZE]);
rng.fill_bytes(sigma_array.as_mut());
let sigma = Zeroizing::new(Block::new(*sigma_array));
let r = hash::h3(&sigma, message.as_ref());
let mut u = V::Public::generator();
u *= &r;
let mut r_pub = public;
r_pub *= &r;
let gt = V::pairing(&r_pub, &q_id);
let h2_value = Zeroizing::new(hash::h2(>));
let v = xor(&sigma, &h2_value);
let h4_value = Zeroizing::new(hash::h4(&sigma));
let w = xor(message, &h4_value);
Ok(Ciphertext { u, v, w })
}
pub fn decrypt<V: Variant>(signature: &V::Signature, ciphertext: &Ciphertext<V>) -> Option<Block> {
if signature == &V::Signature::zero() {
return None;
}
let gt = V::pairing(&ciphertext.u, signature);
let h2_value = hash::h2(>);
let sigma = xor(&ciphertext.v, &h2_value);
let h4_value = hash::h4(&sigma);
let message = xor(&ciphertext.w, &h4_value);
let r = hash::h3(&sigma, &message);
let mut expected_u = V::Public::generator();
expected_u *= &r;
if ciphertext.u != expected_u {
return None;
}
Some(message)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bls12381::primitives::{
ops,
variant::{MinPk, MinSig},
};
use commonware_math::algebra::Random as _;
use commonware_utils::test_rng;
fn identity_signature_cannot_decrypt<V: Variant>() {
let sigma = Block::new([1; BLOCK_SIZE]);
let message = Block::new([2; BLOCK_SIZE]);
let r = hash::h3(&sigma, message.as_ref());
let mut u = V::Public::generator();
u *= &r;
let identity_pairing = V::pairing(&V::Public::zero(), &V::Signature::generator());
let ciphertext = Ciphertext {
u,
v: xor(&sigma, &hash::h2(&identity_pairing)),
w: xor(&message, &hash::h4(&sigma)),
};
assert!(decrypt::<V>(&V::Signature::zero(), &ciphertext).is_none());
}
#[test]
fn test_identity_signature_cannot_decrypt() {
identity_signature_cannot_decrypt::<MinPk>();
identity_signature_cannot_decrypt::<MinSig>();
}
fn identity_public_key_cannot_encrypt<V: Variant>() {
let result = encrypt::<_, V>(
&mut test_rng(),
V::Public::zero(),
(b"test", b"target"),
&Block::new([0; BLOCK_SIZE]),
);
assert_eq!(result, Err(Error::InvalidPublicKey));
}
#[test]
fn test_identity_public_key_cannot_encrypt() {
identity_public_key_cannot_encrypt::<MinPk>();
identity_public_key_cannot_encrypt::<MinSig>();
}
#[test]
fn test_encrypt_decrypt_minpk() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let target = 10u64.to_be_bytes();
let message = b"Hello, IBE! This is exactly 32b!";
let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let decrypted =
decrypt::<MinPk>(&signature, &ciphertext).expect("Decryption should succeed");
assert_eq!(message.as_ref(), decrypted.as_ref());
}
#[test]
fn test_encrypt_decrypt_minsig() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinSig>(&mut rng);
let target = 20u64.to_be_bytes();
let message = b"Testing MinSig variant - 32 byte";
let signature = ops::sign_message::<MinSig>(&master_secret, b"_TLE_", &target);
let ciphertext = encrypt::<_, MinSig>(
&mut rng,
master_public,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let decrypted =
decrypt::<MinSig>(&signature, &ciphertext).expect("Decryption should succeed");
assert_eq!(message.as_ref(), decrypted.as_ref());
}
#[test]
fn test_wrong_private_key() {
let mut rng = test_rng();
let (_, master_public1) = ops::keypair::<_, MinPk>(&mut rng);
let (master_secret2, _) = ops::keypair::<_, MinPk>(&mut rng);
let target = 30u64.to_be_bytes();
let message = b"Secret message padded to 32bytes";
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public1,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let wrong_signature = ops::sign_message::<MinPk>(&master_secret2, b"_TLE_", &target);
let result = decrypt::<MinPk>(&wrong_signature, &ciphertext);
assert!(result.is_none());
}
#[test]
fn test_tampered_ciphertext() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let target = 40u64.to_be_bytes();
let message = b"Tamper test padded to 32 bytes..";
let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let mut w_bytes = [0u8; BLOCK_SIZE];
w_bytes.copy_from_slice(ciphertext.w.as_ref());
w_bytes[0] ^= 0xFF;
let tampered_ciphertext = Ciphertext {
u: ciphertext.u,
v: ciphertext.v,
w: Block::new(w_bytes),
};
let result = decrypt::<MinPk>(&signature, &tampered_ciphertext);
assert!(result.is_none());
}
#[test]
fn test_encrypt_decrypt_with_namespace() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let namespace = b"example.org";
let target = 80u64.to_be_bytes();
let message = b"Message with namespace - 32 byte";
let signature = ops::sign_message::<MinPk>(&master_secret, namespace, &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(namespace, &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let decrypted =
decrypt::<MinPk>(&signature, &ciphertext).expect("Decryption should succeed");
assert_eq!(message.as_ref(), decrypted.as_ref());
}
#[test]
fn test_namespace_variance() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let namespace1 = b"example.org";
let namespace2 = b"other.org";
let target = 100u64.to_be_bytes();
let message = b"Namespace vs no namespace - 32by";
let signature_ns1 = ops::sign_message::<MinPk>(&master_secret, namespace1, &target);
let signature_ns2 = ops::sign_message::<MinPk>(&master_secret, namespace2, &target);
let ciphertext_ns1 = encrypt::<_, MinPk>(
&mut rng,
master_public,
(namespace1, &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let ciphertext_ns2 = encrypt::<_, MinPk>(
&mut rng,
master_public,
(namespace2, &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let result1 = decrypt::<MinPk>(&signature_ns2, &ciphertext_ns1);
assert!(result1.is_none());
let result2 = decrypt::<MinPk>(&signature_ns1, &ciphertext_ns2);
assert!(result2.is_none());
let decrypted_ns1 = decrypt::<MinPk>(&signature_ns1, &ciphertext_ns1)
.expect("Decryption with matching namespace should succeed");
let decrypted_ns2 = decrypt::<MinPk>(&signature_ns2, &ciphertext_ns2)
.expect("Decryption with matching namespace should succeed");
assert_eq!(message.as_ref(), decrypted_ns1.as_ref());
assert_eq!(message.as_ref(), decrypted_ns2.as_ref());
}
#[test]
fn test_cca_modified_v() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let target = 110u64.to_be_bytes();
let message = b"Another CCA test message 32bytes";
let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let mut v_bytes = [0u8; BLOCK_SIZE];
v_bytes.copy_from_slice(ciphertext.v.as_ref());
v_bytes[0] ^= 0x01;
let tampered_ciphertext = Ciphertext {
u: ciphertext.u,
v: Block::new(v_bytes),
w: ciphertext.w,
};
let result = decrypt::<MinPk>(&signature, &tampered_ciphertext);
assert!(result.is_none());
}
#[test]
fn test_cca_modified_u() {
let mut rng = test_rng();
let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
let target = 70u64.to_be_bytes();
let message = b"CCA security test message 32 byt";
let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
let mut ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(b"_TLE_", &target),
&Block::new(*message),
)
.expect("encryption should succeed");
let mut modified_u = ciphertext.u;
modified_u *= &Scalar::random(&mut rng);
ciphertext.u = modified_u;
let result = decrypt::<MinPk>(&signature, &ciphertext);
assert!(result.is_none());
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Ciphertext<MinPk>>,
}
}
}