use crate::{
block_ciphers::BlockCipherProvider,
engines::EngineBLS,
ibe::fullident::{Ciphertext as IBECiphertext, IBESecret, Identity, Input},
};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{
rand::{CryptoRng, Rng},
vec::Vec,
};
pub type OpaqueSecretKey = [u8; 32];
#[derive(CanonicalDeserialize, CanonicalSerialize, Debug)]
pub struct TLECiphertext<E: EngineBLS> {
pub header: IBECiphertext<E>,
pub body: Vec<u8>,
pub cipher_suite: Vec<u8>,
}
#[derive(Debug, PartialEq)]
pub enum Error {
MessageEncryptionError,
DeserializationError,
DeserializationErrorG1,
DeserializationErrorG2,
DeserializationErrorFr,
DecryptionError,
InvalidSignature,
InvalidSecretKey,
}
pub fn tle<E, S, R>(
p_pub: E::PublicKeyGroup,
secret_key: OpaqueSecretKey,
message: &[u8],
id: Identity,
mut rng: R,
) -> Result<TLECiphertext<E>, Error>
where
E: EngineBLS,
S: BlockCipherProvider<32>,
R: Rng + CryptoRng,
{
let input = Input::new(secret_key).expect("The secret key has 32 bytes.");
let header: IBECiphertext<E> = id.encrypt(&input, p_pub, &mut rng);
let body =
S::encrypt(message, secret_key, &mut rng).map_err(|_| Error::MessageEncryptionError)?;
let mut message_bytes = Vec::new();
body.serialize_compressed(&mut message_bytes)
.expect("Encryption output must be serializable.");
Ok(TLECiphertext { header, body: message_bytes, cipher_suite: S::CIPHER_SUITE.to_vec() })
}
pub fn tld<E, S>(
ciphertext: TLECiphertext<E>,
signature: E::SignatureGroup,
) -> Result<Vec<u8>, Error>
where
E: EngineBLS,
S: BlockCipherProvider<32>,
{
let secret_bytes = IBESecret(signature)
.decrypt(&ciphertext.header)
.map_err(|_| Error::InvalidSignature)?;
let ct = S::Ciphertext::deserialize_compressed(&mut &ciphertext.body[..])
.map_err(|_| Error::DeserializationError)?;
S::decrypt(ct, secret_bytes).map_err(|_| Error::DecryptionError)
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
block_ciphers::{AESGCMBlockCipherProvider, AESOutput},
engines::drand::TinyBLS381,
};
use alloc::vec;
use ark_ec::PrimeGroup;
use ark_ff::UniformRand;
use ark_std::rand::rngs::OsRng;
use sha2::Digest;
enum TestStatusReport {
DecryptSuccess { actual: Vec<u8>, expected: Vec<u8> },
DecryptionFailed { error: Error },
}
fn tlock_test_aes_gcm<E: EngineBLS, R: Rng + Sized + CryptoRng>(
inject_bad_ct: bool,
inject_bad_nonce: bool,
handler: &dyn Fn(TestStatusReport) -> (),
) {
let message = b"this is a test message".to_vec();
let id = Identity::new(b"", &message);
let sk = E::Scalar::rand(&mut OsRng);
let p_pub = E::PublicKeyGroup::generator() * sk;
let msk = [1; 32];
let sig: E::SignatureGroup = id.extract::<E>(sk).0;
match tle::<E, AESGCMBlockCipherProvider, OsRng>(p_pub, msk, &message, id, OsRng) {
Ok(mut ct) => {
if inject_bad_ct {
let mut output = AESOutput::deserialize_compressed(&mut &ct.body[..]).unwrap();
output.ciphertext = vec![];
let mut corrupted = Vec::new();
output.serialize_compressed(&mut corrupted).unwrap();
ct.body = corrupted;
}
if inject_bad_nonce {
let mut output = AESOutput::deserialize_compressed(&mut &ct.body[..]).unwrap();
output.nonce = vec![];
let mut corrupted = Vec::new();
output.serialize_compressed(&mut corrupted).unwrap();
ct.body = corrupted;
}
match tld::<E, AESGCMBlockCipherProvider>(ct, sig) {
Ok(output) => {
handler(TestStatusReport::DecryptSuccess {
actual: output,
expected: message,
});
},
Err(e) => {
handler(TestStatusReport::DecryptionFailed { error: e });
},
}
},
Err(_) => {
panic!("The test should pass but failed to run tlock encrypt");
},
}
}
#[test]
pub fn tlock_can_encrypt_decrypt_with_single_sig() {
tlock_test_aes_gcm::<TinyBLS381, OsRng>(false, false, &|status: TestStatusReport| {
match status {
TestStatusReport::DecryptSuccess { actual, expected } => {
assert_eq!(actual, expected);
},
_ => panic!("all other conditions invalid"),
}
});
}
#[test]
pub fn tlock_can_encrypt_decrypt_with_full_sigs_present() {
tlock_test_aes_gcm::<TinyBLS381, OsRng>(false, false, &|status: TestStatusReport| {
match status {
TestStatusReport::DecryptSuccess { actual, expected } => {
assert_eq!(actual, expected);
},
_ => panic!("all other conditions invalid"),
}
});
}
#[test]
pub fn tlock_can_encrypt_decrypt_with_many_identities_at_threshold() {
tlock_test_aes_gcm::<TinyBLS381, OsRng>(false, false, &|status: TestStatusReport| {
match status {
TestStatusReport::DecryptSuccess { actual, expected } => {
assert_eq!(actual, expected);
},
_ => panic!("all other conditions invalid"),
}
});
}
#[test]
pub fn tlock_decryption_fails_with_bad_ciphertext() {
tlock_test_aes_gcm::<TinyBLS381, OsRng>(true, false, &|status: TestStatusReport| {
match status {
TestStatusReport::DecryptionFailed { error } => {
assert_eq!(error, Error::DecryptionError);
},
_ => panic!("all other conditions invalid"),
}
});
}
#[test]
pub fn tlock_decryption_fails_with_bad_nonce() {
tlock_test_aes_gcm::<TinyBLS381, OsRng>(false, true, &|status: TestStatusReport| {
match status {
TestStatusReport::DecryptionFailed { error } => {
assert_eq!(error, Error::DecryptionError);
},
_ => panic!("all other conditions invalid"),
}
});
}
#[test]
pub fn tlock_encrypt_decrypt_drand_quicknet_works() {
let pk_bytes =
b"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"
; let round: u64 = 1000;
let signature =
b"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"
;
let pub_key_bytes = hex::decode(pk_bytes).expect("Decoding failed");
let pub_key =
<TinyBLS381 as EngineBLS>::PublicKeyGroup::deserialize_compressed(&*pub_key_bytes)
.unwrap();
let plaintext = b"this is a test".as_slice();
let esk = [2; 32];
let sig_bytes = hex::decode(signature).expect("The signature should be well formatted");
let sig =
<TinyBLS381 as EngineBLS>::SignatureGroup::deserialize_compressed(&*sig_bytes).unwrap();
let message = {
let mut hasher = sha2::Sha256::new();
hasher.update(round.to_be_bytes());
hasher.finalize().to_vec()
};
let identity = Identity::new(b"", &message);
let ct = tle::<TinyBLS381, AESGCMBlockCipherProvider, OsRng>(
pub_key, esk, plaintext, identity, OsRng,
)
.unwrap();
let result = tld::<TinyBLS381, AESGCMBlockCipherProvider>(ct, sig).unwrap();
assert!(result == plaintext);
}
}