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