use crate::{
bls12381::primitives::{
group::{Element, Scalar, DST, GT},
ops::{hash_message, hash_message_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_utils::sequence::FixedBytes;
use rand_core::CryptoRngCore;
const DST: DST = b"TLE_BLS12381_XMD:SHA-256_SSWU_RO_H3_";
const BLOCK_SIZE: usize = Digest::SIZE;
pub type Block = FixedBytes<BLOCK_SIZE>;
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()
}
}
mod hash {
use super::*;
use crate::{Hasher, Sha256};
pub fn h2(gt: >) -> Block {
let mut hasher = Sha256::new();
hasher.update(b"h2");
hasher.update(>.as_slice());
hasher.finalize().into()
}
pub fn h3(sigma: &Block, message: &[u8]) -> Scalar {
let mut combined = 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 {
let mut hasher = Sha256::new();
hasher.update(b"h4");
hasher.update(sigma.as_ref());
hasher.finalize().into()
}
}
#[inline]
fn xor(a: &Block, b: &Block) -> Block {
let a_bytes = a.as_ref();
let b_bytes = b.as_ref();
Block::new([
a_bytes[0] ^ b_bytes[0],
a_bytes[1] ^ b_bytes[1],
a_bytes[2] ^ b_bytes[2],
a_bytes[3] ^ b_bytes[3],
a_bytes[4] ^ b_bytes[4],
a_bytes[5] ^ b_bytes[5],
a_bytes[6] ^ b_bytes[6],
a_bytes[7] ^ b_bytes[7],
a_bytes[8] ^ b_bytes[8],
a_bytes[9] ^ b_bytes[9],
a_bytes[10] ^ b_bytes[10],
a_bytes[11] ^ b_bytes[11],
a_bytes[12] ^ b_bytes[12],
a_bytes[13] ^ b_bytes[13],
a_bytes[14] ^ b_bytes[14],
a_bytes[15] ^ b_bytes[15],
a_bytes[16] ^ b_bytes[16],
a_bytes[17] ^ b_bytes[17],
a_bytes[18] ^ b_bytes[18],
a_bytes[19] ^ b_bytes[19],
a_bytes[20] ^ b_bytes[20],
a_bytes[21] ^ b_bytes[21],
a_bytes[22] ^ b_bytes[22],
a_bytes[23] ^ b_bytes[23],
a_bytes[24] ^ b_bytes[24],
a_bytes[25] ^ b_bytes[25],
a_bytes[26] ^ b_bytes[26],
a_bytes[27] ^ b_bytes[27],
a_bytes[28] ^ b_bytes[28],
a_bytes[29] ^ b_bytes[29],
a_bytes[30] ^ b_bytes[30],
a_bytes[31] ^ b_bytes[31],
])
}
pub fn encrypt<R: CryptoRngCore, V: Variant>(
rng: &mut R,
public: V::Public,
target: (Option<&[u8]>, &[u8]),
message: &Block,
) -> Ciphertext<V> {
let q_id = match target {
(None, target) => hash_message::<V>(V::MESSAGE, target),
(Some(namespace), target) => hash_message_namespace::<V>(V::MESSAGE, namespace, target),
};
let mut sigma_array = [0u8; BLOCK_SIZE];
rng.fill_bytes(&mut sigma_array);
let sigma = Block::new(sigma_array);
let r = hash::h3(&sigma, message.as_ref());
let mut u = V::Public::one();
u.mul(&r);
let mut r_pub = public;
r_pub.mul(&r);
let gt = V::pairing(&r_pub, &q_id);
let h2_value = hash::h2(>);
let v = xor(&sigma, &h2_value);
let h4_value = hash::h4(&sigma);
let w = xor(message, &h4_value);
Ciphertext { u, v, w }
}
pub fn decrypt<V: Variant>(signature: &V::Signature, ciphertext: &Ciphertext<V>) -> Option<Block> {
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::one();
expected_u.mul(&r);
if ciphertext.u != expected_u {
return None;
}
Some(message)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bls12381::primitives::{
ops::{keypair, sign_message},
variant::{MinPk, MinSig},
};
use rand::thread_rng;
#[test]
fn test_encrypt_decrypt_minpk() {
let mut rng = thread_rng();
let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
let target = 10u64.to_be_bytes();
let message = b"Hello, IBE! This is exactly 32b!";
let signature = sign_message::<MinPk>(&master_secret, None, &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
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 = thread_rng();
let (master_secret, master_public) = keypair::<_, MinSig>(&mut rng);
let target = 20u64.to_be_bytes();
let message = b"Testing MinSig variant - 32 byte";
let signature = sign_message::<MinSig>(&master_secret, None, &target);
let ciphertext = encrypt::<_, MinSig>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
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 = thread_rng();
let (_, master_public1) = keypair::<_, MinPk>(&mut rng);
let (master_secret2, _) = 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,
(None, &target),
&Block::new(*message),
);
let wrong_signature = sign_message::<MinPk>(&master_secret2, None, &target);
let result = decrypt::<MinPk>(&wrong_signature, &ciphertext);
assert!(result.is_none());
}
#[test]
fn test_tampered_ciphertext() {
let mut rng = thread_rng();
let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
let target = 40u64.to_be_bytes();
let message = b"Tamper test padded to 32 bytes..";
let signature = sign_message::<MinPk>(&master_secret, None, &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
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 = thread_rng();
let (master_secret, master_public) = 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 = sign_message::<MinPk>(&master_secret, Some(namespace), &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(Some(namespace), &target),
&Block::new(*message),
);
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 = thread_rng();
let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
let namespace = b"example.org";
let target = 100u64.to_be_bytes();
let message = b"Namespace vs no namespace - 32by";
let signature_no_ns = sign_message::<MinPk>(&master_secret, None, &target);
let signature_ns = sign_message::<MinPk>(&master_secret, Some(namespace), &target);
let ciphertext_ns = encrypt::<_, MinPk>(
&mut rng,
master_public,
(Some(namespace), &target),
&Block::new(*message),
);
let ciphertext_no_ns = encrypt::<_, MinPk>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
let result1 = decrypt::<MinPk>(&signature_no_ns, &ciphertext_ns);
assert!(result1.is_none());
let result2 = decrypt::<MinPk>(&signature_ns, &ciphertext_no_ns);
assert!(result2.is_none());
let decrypted_ns = decrypt::<MinPk>(&signature_ns, &ciphertext_ns)
.expect("Decryption with matching namespace should succeed");
let decrypted_no_ns = decrypt::<MinPk>(&signature_no_ns, &ciphertext_no_ns)
.expect("Decryption without namespace should succeed");
assert_eq!(message.as_ref(), decrypted_ns.as_ref());
assert_eq!(message.as_ref(), decrypted_no_ns.as_ref());
}
#[test]
fn test_cca_modified_v() {
let mut rng = thread_rng();
let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
let target = 110u64.to_be_bytes();
let message = b"Another CCA test message 32bytes";
let signature = sign_message::<MinPk>(&master_secret, None, &target);
let ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
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 = thread_rng();
let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
let target = 70u64.to_be_bytes();
let message = b"CCA security test message 32 byt";
let signature = sign_message::<MinPk>(&master_secret, None, &target);
let mut ciphertext = encrypt::<_, MinPk>(
&mut rng,
master_public,
(None, &target),
&Block::new(*message),
);
let mut modified_u = ciphertext.u;
modified_u.mul(&Scalar::from_rand(&mut rng));
ciphertext.u = modified_u;
let result = decrypt::<MinPk>(&signature, &ciphertext);
assert!(result.is_none());
}
}