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