Skip to main content

commonware_cryptography/bls12381/
tle.rs

1//! Timelock Encryption (TLE) over BLS12-381.
2//!
3//! This crate implements Timelock Encryption (TLE) over BLS12-381 using
4//! Identity-Based Encryption (IBE) with the Boneh-Franklin scheme. TLE enables
5//! encrypting messages that can only be decrypted when a valid signature over
6//! a specific target (e.g., timestamp or round number) becomes available.
7//!
8//! # Security
9//!
10//! To achieve CCA-security (resistance against chosen-ciphertext attacks), this
11//! implementation employs the Fujisaki-Okamoto transform, which converts the
12//! underlying CPA-secure IBE scheme into a CCA-secure scheme through:
13//!
14//! * Deriving encryption randomness deterministically from the message and a
15//!   random value (sigma)
16//! * Including integrity checks to detect ciphertext tampering
17//!
18//! # Architecture
19//!
20//! The encryption process involves (for [crate::bls12381::primitives::variant::MinPk]):
21//! 1. Generating a random sigma value
22//! 2. Deriving encryption randomness r = H3(sigma || message)
23//! 3. Computing the ciphertext components:
24//!    - U = r * G (commitment in G1)
25//!    - V = sigma ⊕ H2(e(P_pub, Q_id)^r) (masked random value)
26//!    - W = M ⊕ H4(sigma) (masked message)
27//!
28//! Where Q_id = H1(target) maps the target to a point in G2.
29//!
30//! # Example
31//!
32//! _It is recommended to use a threshold signature scheme to generate decrypting
33//! signatures in production (where no single party owns the private key)._
34//!
35//! ```rust
36//! use commonware_cryptography::bls12381::{
37//!     tle::{encrypt, decrypt, Block},
38//!     primitives::{
39//!         ops::{keypair, sign_message},
40//!         variant::MinPk,
41//!     },
42//! };
43//! use commonware_utils::test_rng;
44//!
45//! let mut rng = test_rng();
46//!
47//! // Generate keypair
48//! let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
49//!
50//! // Define a target (e.g., a timestamp or round number)
51//! let target = 12345u64.to_be_bytes();
52//!
53//! // Create a 32-byte message
54//! let message_bytes = b"This is a secret message 32bytes";
55//! let message = Block::new(*message_bytes);
56//!
57//! // Encrypt the message for the target
58//! let ciphertext = encrypt::<_, MinPk>(
59//!     &mut rng,
60//!     master_public,
61//!     (b"_TLE_", &target),
62//!     &message,
63//! )
64//! .expect("encryption should succeed");
65//!
66//! // Later, when someone has a signature over the target...
67//! let signature = sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
68//!
69//! // They can decrypt the message
70//! let decrypted = decrypt::<MinPk>(&signature, &ciphertext)
71//!     .expect("Decryption should succeed with valid signature");
72//!
73//! assert_eq!(message.as_ref(), decrypted.as_ref());
74//! ```
75//!
76//! # Acknowledgements
77//!
78//! The following resources were used as references when implementing this crate:
79//!
80//! * <https://crypto.stanford.edu/~dabo/papers/bfibe.pdf>: Identity-Based Encryption from the Weil Pairing
81//! * <https://eprint.iacr.org/2023/189>: tlock: Practical Timelock Encryption from Threshold BLS
82//! * <https://github.com/thibmeu/tlock-rs>: tlock-rs: Practical Timelock Encryption/Decryption in Rust
83//! * <https://github.com/drand/tlock> tlock: Timelock Encryption/Decryption Made Practical
84
85use crate::{
86    bls12381::primitives::{
87        group::{DST, GT, Scalar},
88        ops::hash_with_namespace,
89        variant::Variant,
90    },
91    sha256::Digest,
92};
93#[cfg(not(feature = "std"))]
94use alloc::vec::Vec;
95use bytes::{Buf, BufMut};
96use commonware_codec::{EncodeSize, FixedSize, Read, ReadExt, Write};
97use commonware_math::algebra::{Additive, CryptoGroup};
98use commonware_utils::sequence::FixedBytes;
99use rand_core::CryptoRng;
100use thiserror::Error;
101use zeroize::Zeroizing;
102
103/// Domain separation tag for hashing the `h3` message to a scalar.
104const DST: DST = b"TLE_BLS12381_XMD:SHA-256_SSWU_RO_H3_";
105
106/// Block size for encryption operations.
107const BLOCK_SIZE: usize = Digest::SIZE;
108
109/// Block type for IBE.
110pub type Block = FixedBytes<BLOCK_SIZE>;
111
112/// Errors returned while encrypting a message.
113#[derive(Debug, Error, PartialEq, Eq)]
114pub enum Error {
115    /// The master public key is the group identity.
116    #[error("master public key is the group identity")]
117    InvalidPublicKey,
118}
119
120impl From<Digest> for Block {
121    fn from(digest: Digest) -> Self {
122        Block::new(digest.0)
123    }
124}
125
126/// Encrypted message.
127#[derive(Hash, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
128pub struct Ciphertext<V: Variant> {
129    /// First group element U = r * Public::generator().
130    pub u: V::Public,
131    /// Encrypted random value V = sigma XOR H2(e(P_pub, Q_id)^r).
132    pub v: Block,
133    /// Encrypted message W = M XOR H4(sigma).
134    pub w: Block,
135}
136
137impl<V: Variant> Write for Ciphertext<V> {
138    fn write(&self, buf: &mut impl BufMut) {
139        self.u.write(buf);
140        buf.put_slice(self.v.as_ref());
141        buf.put_slice(self.w.as_ref());
142    }
143}
144
145impl<V: Variant> Read for Ciphertext<V> {
146    type Cfg = ();
147
148    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, commonware_codec::Error> {
149        let u = V::Public::read(buf)?;
150        let v = Block::read(buf)?;
151        let w = Block::read(buf)?;
152        Ok(Self { u, v, w })
153    }
154}
155
156impl<V: Variant> EncodeSize for Ciphertext<V> {
157    fn encode_size(&self) -> usize {
158        self.u.encode_size() + self.v.encode_size() + self.w.encode_size()
159    }
160}
161
162#[cfg(feature = "arbitrary")]
163impl<V: Variant> arbitrary::Arbitrary<'_> for Ciphertext<V>
164where
165    V::Public: for<'a> arbitrary::Arbitrary<'a>,
166{
167    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
168        let ge = u.arbitrary()?;
169        let v = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
170        let w = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
171        Ok(Self { u: ge, v, w })
172    }
173}
174
175/// Hash functions for IBE.
176mod hash {
177    use super::*;
178    use crate::{Hasher, Sha256};
179
180    /// H2: GT -> Block
181    ///
182    /// Used to mask the random sigma value.
183    pub fn h2(gt: &GT) -> Block {
184        let gt = Zeroizing::new(gt.as_slice());
185        Sha256::hash(&[b"h2", gt.as_ref()]).into()
186    }
187
188    /// H3: (sigma, M) -> Scalar
189    ///
190    /// Used to derive the random scalar r using RFC9380 hash-to-field.
191    pub fn h3(sigma: &Block, message: &[u8]) -> Scalar {
192        // Combine sigma and message
193        let mut combined = Zeroizing::new(Vec::with_capacity(sigma.len() + message.len()));
194        combined.extend_from_slice(sigma.as_ref());
195        combined.extend_from_slice(message);
196
197        // Map the combined bytes to a scalar via RFC9380 hash-to-field.
198        Scalar::map(DST, &combined)
199    }
200
201    /// H4: sigma -> Block
202    ///
203    /// Used to mask the message.
204    pub fn h4(sigma: &Block) -> Block {
205        Sha256::hash(&[b"h4", sigma.as_ref()]).into()
206    }
207}
208
209/// XOR two [Block]s together.
210#[inline]
211fn xor(a: &Block, b: &Block) -> Block {
212    let a = a.as_ref();
213    let b = b.as_ref();
214    Block::new(core::array::from_fn(|i| a[i] ^ b[i]))
215}
216
217/// Encrypt a message for a given target.
218///
219/// # Steps
220/// 1. Generate random sigma
221/// 2. Derive encryption randomness r = H3(sigma || message)
222/// 3. Create commitment U = r * G
223/// 4. Mask sigma with the pairing result
224/// 5. Mask the message with H4(sigma)
225///
226/// # Arguments
227/// * `rng` - Random number generator
228/// * `public` - Master public key
229/// * `target` - Tuple of (namespace, payload) over which a signature will decrypt the message
230/// * `message` - Message to encrypt
231///
232/// # Returns
233/// * `Result<Ciphertext<V>, Error>` - The encrypted ciphertext, or an encryption error
234pub fn encrypt<R: CryptoRng, V: Variant>(
235    rng: &mut R,
236    public: V::Public,
237    target: (&[u8], &[u8]),
238    message: &Block,
239) -> Result<Ciphertext<V>, Error> {
240    // An identity master key makes the pairing mask independent of the secret key.
241    if public == V::Public::zero() {
242        return Err(Error::InvalidPublicKey);
243    }
244
245    // Hash target to get Q_id in signature group using the variant's message DST
246    let (namespace, target) = target;
247    let q_id = hash_with_namespace::<V>(V::MESSAGE, namespace, target);
248
249    // Generate random sigma
250    let mut sigma_array = Zeroizing::new([0u8; BLOCK_SIZE]);
251    rng.fill_bytes(sigma_array.as_mut());
252    let sigma = Zeroizing::new(Block::new(*sigma_array));
253
254    // Derive scalar r from sigma and message
255    let r = hash::h3(&sigma, message.as_ref());
256
257    // Compute U = r * Public::generator()
258    let mut u = V::Public::generator();
259    u *= &r;
260
261    // Compute e(P_pub, Q_id)^r = e(r * P_pub, Q_id).
262    //
263    // The latter expression is more efficient to compute.
264    let mut r_pub = public;
265    r_pub *= &r;
266    let gt = V::pairing(&r_pub, &q_id);
267
268    // Compute V = sigma XOR H2(e(P_pub, Q_id)^r)
269    let h2_value = Zeroizing::new(hash::h2(&gt));
270    let v = xor(&sigma, &h2_value);
271
272    // Compute W = M XOR H4(sigma)
273    let h4_value = Zeroizing::new(hash::h4(&sigma));
274    let w = xor(message, &h4_value);
275
276    Ok(Ciphertext { u, v, w })
277}
278
279/// Decrypt a ciphertext with a signature over the target specified
280/// during [encrypt].
281///
282/// # Steps
283/// 1. Recover sigma from the pairing
284/// 2. Recover the message
285/// 3. Recompute r = H3(sigma || message)
286/// 4. Verify that U = r * G matches the ciphertext
287///
288/// # Arguments
289/// * `signature` - Signature over the target payload
290/// * `ciphertext` - Ciphertext to decrypt
291///
292/// # Returns
293/// * `Option<Block>` - The decrypted message
294pub fn decrypt<V: Variant>(signature: &V::Signature, ciphertext: &Ciphertext<V>) -> Option<Block> {
295    // An identity signature collapses the pairing to a public constant and bypasses target binding.
296    if signature == &V::Signature::zero() {
297        return None;
298    }
299
300    // Compute e(U, signature)
301    let gt = V::pairing(&ciphertext.u, signature);
302
303    // Recover sigma = V XOR H2(e(U, signature))
304    let h2_value = hash::h2(&gt);
305    let sigma = xor(&ciphertext.v, &h2_value);
306
307    // Recover M = W XOR H4(sigma)
308    let h4_value = hash::h4(&sigma);
309    let message = xor(&ciphertext.w, &h4_value);
310
311    // Recompute r and verify U = r * Public::generator()
312    let r = hash::h3(&sigma, &message);
313    let mut expected_u = V::Public::generator();
314    expected_u *= &r;
315    if ciphertext.u != expected_u {
316        return None;
317    }
318
319    Some(message)
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use crate::bls12381::primitives::{
326        ops,
327        variant::{MinPk, MinSig},
328    };
329    use commonware_math::algebra::Random as _;
330    use commonware_utils::test_rng;
331
332    fn identity_signature_cannot_decrypt<V: Variant>() {
333        let sigma = Block::new([1; BLOCK_SIZE]);
334        let message = Block::new([2; BLOCK_SIZE]);
335        let r = hash::h3(&sigma, message.as_ref());
336
337        let mut u = V::Public::generator();
338        u *= &r;
339        let identity_pairing = V::pairing(&V::Public::zero(), &V::Signature::generator());
340        let ciphertext = Ciphertext {
341            u,
342            v: xor(&sigma, &hash::h2(&identity_pairing)),
343            w: xor(&message, &hash::h4(&sigma)),
344        };
345
346        assert!(decrypt::<V>(&V::Signature::zero(), &ciphertext).is_none());
347    }
348
349    #[test]
350    fn test_identity_signature_cannot_decrypt() {
351        identity_signature_cannot_decrypt::<MinPk>();
352        identity_signature_cannot_decrypt::<MinSig>();
353    }
354
355    fn identity_public_key_cannot_encrypt<V: Variant>() {
356        let result = encrypt::<_, V>(
357            &mut test_rng(),
358            V::Public::zero(),
359            (b"test", b"target"),
360            &Block::new([0; BLOCK_SIZE]),
361        );
362        assert_eq!(result, Err(Error::InvalidPublicKey));
363    }
364
365    #[test]
366    fn test_identity_public_key_cannot_encrypt() {
367        identity_public_key_cannot_encrypt::<MinPk>();
368        identity_public_key_cannot_encrypt::<MinSig>();
369    }
370
371    #[test]
372    fn test_encrypt_decrypt_minpk() {
373        let mut rng = test_rng();
374
375        // Generate master keypair
376        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
377
378        // Target and message
379        let target = 10u64.to_be_bytes();
380        let message = b"Hello, IBE! This is exactly 32b!"; // 32 bytes
381
382        // Generate signature over the target
383        let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
384
385        // Encrypt
386        let ciphertext = encrypt::<_, MinPk>(
387            &mut rng,
388            master_public,
389            (b"_TLE_", &target),
390            &Block::new(*message),
391        )
392        .expect("encryption should succeed");
393
394        // Decrypt
395        let decrypted =
396            decrypt::<MinPk>(&signature, &ciphertext).expect("Decryption should succeed");
397
398        assert_eq!(message.as_ref(), decrypted.as_ref());
399    }
400
401    #[test]
402    fn test_encrypt_decrypt_minsig() {
403        let mut rng = test_rng();
404
405        // Generate master ops::keypair
406        let (master_secret, master_public) = ops::keypair::<_, MinSig>(&mut rng);
407
408        // Target and message
409        let target = 20u64.to_be_bytes();
410        let message = b"Testing MinSig variant - 32 byte";
411
412        // Generate signature over the target
413        let signature = ops::sign_message::<MinSig>(&master_secret, b"_TLE_", &target);
414
415        // Encrypt
416        let ciphertext = encrypt::<_, MinSig>(
417            &mut rng,
418            master_public,
419            (b"_TLE_", &target),
420            &Block::new(*message),
421        )
422        .expect("encryption should succeed");
423
424        // Decrypt
425        let decrypted =
426            decrypt::<MinSig>(&signature, &ciphertext).expect("Decryption should succeed");
427
428        assert_eq!(message.as_ref(), decrypted.as_ref());
429    }
430
431    #[test]
432    fn test_wrong_private_key() {
433        let mut rng = test_rng();
434
435        // Generate two different master ops::keypairs
436        let (_, master_public1) = ops::keypair::<_, MinPk>(&mut rng);
437        let (master_secret2, _) = ops::keypair::<_, MinPk>(&mut rng);
438
439        let target = 30u64.to_be_bytes();
440        let message = b"Secret message padded to 32bytes";
441
442        // Encrypt with first master public key
443        let ciphertext = encrypt::<_, MinPk>(
444            &mut rng,
445            master_public1,
446            (b"_TLE_", &target),
447            &Block::new(*message),
448        )
449        .expect("encryption should succeed");
450
451        // Try to decrypt with signature from second master
452        let wrong_signature = ops::sign_message::<MinPk>(&master_secret2, b"_TLE_", &target);
453        let result = decrypt::<MinPk>(&wrong_signature, &ciphertext);
454
455        assert!(result.is_none());
456    }
457
458    #[test]
459    fn test_tampered_ciphertext() {
460        let mut rng = test_rng();
461
462        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
463        let target = 40u64.to_be_bytes();
464        let message = b"Tamper test padded to 32 bytes.."; // 32 bytes
465
466        // Generate signature over the target
467        let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
468
469        // Encrypt
470        let ciphertext = encrypt::<_, MinPk>(
471            &mut rng,
472            master_public,
473            (b"_TLE_", &target),
474            &Block::new(*message),
475        )
476        .expect("encryption should succeed");
477
478        // Tamper with ciphertext by creating a modified w
479        let mut w_bytes = [0u8; BLOCK_SIZE];
480        w_bytes.copy_from_slice(ciphertext.w.as_ref());
481        w_bytes[0] ^= 0xFF;
482        let tampered_ciphertext = Ciphertext {
483            u: ciphertext.u,
484            v: ciphertext.v,
485            w: Block::new(w_bytes),
486        };
487
488        // Try to decrypt
489        let result = decrypt::<MinPk>(&signature, &tampered_ciphertext);
490        assert!(result.is_none());
491    }
492
493    #[test]
494    fn test_encrypt_decrypt_with_namespace() {
495        let mut rng = test_rng();
496
497        // Generate master ops::keypair
498        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
499
500        // Target and namespace
501        let namespace = b"example.org";
502        let target = 80u64.to_be_bytes();
503        let message = b"Message with namespace - 32 byte"; // 32 bytes
504
505        // Generate signature over the namespaced target
506        let signature = ops::sign_message::<MinPk>(&master_secret, namespace, &target);
507
508        // Encrypt with namespace
509        let ciphertext = encrypt::<_, MinPk>(
510            &mut rng,
511            master_public,
512            (namespace, &target),
513            &Block::new(*message),
514        )
515        .expect("encryption should succeed");
516
517        // Decrypt
518        let decrypted =
519            decrypt::<MinPk>(&signature, &ciphertext).expect("Decryption should succeed");
520
521        assert_eq!(message.as_ref(), decrypted.as_ref());
522    }
523
524    #[test]
525    fn test_namespace_variance() {
526        let mut rng = test_rng();
527
528        // Generate master ops::keypair
529        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
530
531        let namespace1 = b"example.org";
532        let namespace2 = b"other.org";
533        let target = 100u64.to_be_bytes();
534        let message = b"Namespace vs no namespace - 32by"; // 32 bytes
535
536        // Generate signature with namespace1
537        let signature_ns1 = ops::sign_message::<MinPk>(&master_secret, namespace1, &target);
538
539        // Generate signature with namespace2
540        let signature_ns2 = ops::sign_message::<MinPk>(&master_secret, namespace2, &target);
541
542        // Encrypt with namespace1
543        let ciphertext_ns1 = encrypt::<_, MinPk>(
544            &mut rng,
545            master_public,
546            (namespace1, &target),
547            &Block::new(*message),
548        )
549        .expect("encryption should succeed");
550
551        // Encrypt with namespace2
552        let ciphertext_ns2 = encrypt::<_, MinPk>(
553            &mut rng,
554            master_public,
555            (namespace2, &target),
556            &Block::new(*message),
557        )
558        .expect("encryption should succeed");
559
560        // Try to decrypt namespace1 ciphertext with namespace2 signature - should fail
561        let result1 = decrypt::<MinPk>(&signature_ns2, &ciphertext_ns1);
562        assert!(result1.is_none());
563
564        // Try to decrypt namespace2 ciphertext with namespace1 signature - should fail
565        let result2 = decrypt::<MinPk>(&signature_ns1, &ciphertext_ns2);
566        assert!(result2.is_none());
567
568        // Correct decryptions should succeed
569        let decrypted_ns1 = decrypt::<MinPk>(&signature_ns1, &ciphertext_ns1)
570            .expect("Decryption with matching namespace should succeed");
571        let decrypted_ns2 = decrypt::<MinPk>(&signature_ns2, &ciphertext_ns2)
572            .expect("Decryption with matching namespace should succeed");
573
574        assert_eq!(message.as_ref(), decrypted_ns1.as_ref());
575        assert_eq!(message.as_ref(), decrypted_ns2.as_ref());
576    }
577
578    #[test]
579    fn test_cca_modified_v() {
580        let mut rng = test_rng();
581
582        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
583        let target = 110u64.to_be_bytes();
584        let message = b"Another CCA test message 32bytes"; // 32 bytes
585
586        // Generate signature over the target
587        let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
588
589        // Encrypt
590        let ciphertext = encrypt::<_, MinPk>(
591            &mut rng,
592            master_public,
593            (b"_TLE_", &target),
594            &Block::new(*message),
595        )
596        .expect("encryption should succeed");
597
598        // Modify V component (encrypted sigma)
599        let mut v_bytes = [0u8; BLOCK_SIZE];
600        v_bytes.copy_from_slice(ciphertext.v.as_ref());
601        v_bytes[0] ^= 0x01;
602        let tampered_ciphertext = Ciphertext {
603            u: ciphertext.u,
604            v: Block::new(v_bytes),
605            w: ciphertext.w,
606        };
607
608        // Try to decrypt - should fail due to verification
609        let result = decrypt::<MinPk>(&signature, &tampered_ciphertext);
610        assert!(result.is_none());
611    }
612
613    #[test]
614    fn test_cca_modified_u() {
615        let mut rng = test_rng();
616
617        let (master_secret, master_public) = ops::keypair::<_, MinPk>(&mut rng);
618        let target = 70u64.to_be_bytes();
619        let message = b"CCA security test message 32 byt"; // 32 bytes
620
621        // Generate signature over the target
622        let signature = ops::sign_message::<MinPk>(&master_secret, b"_TLE_", &target);
623
624        // Encrypt
625        let mut ciphertext = encrypt::<_, MinPk>(
626            &mut rng,
627            master_public,
628            (b"_TLE_", &target),
629            &Block::new(*message),
630        )
631        .expect("encryption should succeed");
632
633        // Modify U component (this should make decryption fail due to FO transform)
634        let mut modified_u = ciphertext.u;
635        modified_u *= &Scalar::random(&mut rng);
636        ciphertext.u = modified_u;
637
638        // Try to decrypt - should fail
639        let result = decrypt::<MinPk>(&signature, &ciphertext);
640        assert!(result.is_none());
641    }
642
643    #[cfg(feature = "arbitrary")]
644    mod conformance {
645        use super::*;
646        use commonware_codec::conformance::CodecConformance;
647
648        commonware_conformance::conformance_tests! {
649            CodecConformance<Ciphertext<MinPk>>,
650        }
651    }
652}