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