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//!     (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::{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_math::algebra::CryptoGroup;
95use commonware_utils::sequence::FixedBytes;
96use rand_core::CryptoRngCore;
97
98/// Domain separation tag for hashing the `h3` message to a scalar.
99const DST: DST = b"TLE_BLS12381_XMD:SHA-256_SSWU_RO_H3_";
100
101/// Block size for encryption operations.
102const BLOCK_SIZE: usize = Digest::SIZE;
103
104/// Block type for IBE.
105pub type Block = FixedBytes<BLOCK_SIZE>;
106
107impl From<Digest> for Block {
108    fn from(digest: Digest) -> Self {
109        Block::new(digest.0)
110    }
111}
112
113/// Encrypted message.
114#[derive(Hash, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
115pub struct Ciphertext<V: Variant> {
116    /// First group element U = r * Public::generator().
117    pub u: V::Public,
118    /// Encrypted random value V = sigma XOR H2(e(P_pub, Q_id)^r).
119    pub v: Block,
120    /// Encrypted message W = M XOR H4(sigma).
121    pub w: Block,
122}
123
124impl<V: Variant> Write for Ciphertext<V> {
125    fn write(&self, buf: &mut impl BufMut) {
126        self.u.write(buf);
127        buf.put_slice(self.v.as_ref());
128        buf.put_slice(self.w.as_ref());
129    }
130}
131
132impl<V: Variant> Read for Ciphertext<V> {
133    type Cfg = ();
134
135    fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, commonware_codec::Error> {
136        let u = V::Public::read(buf)?;
137        let v = Block::read(buf)?;
138        let w = Block::read(buf)?;
139        Ok(Self { u, v, w })
140    }
141}
142
143impl<V: Variant> EncodeSize for Ciphertext<V> {
144    fn encode_size(&self) -> usize {
145        self.u.encode_size() + self.v.encode_size() + self.w.encode_size()
146    }
147}
148
149#[cfg(feature = "arbitrary")]
150impl<V: Variant> arbitrary::Arbitrary<'_> for Ciphertext<V>
151where
152    V::Public: for<'a> arbitrary::Arbitrary<'a>,
153{
154    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
155        let ge = u.arbitrary()?;
156        let v = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
157        let w = FixedBytes::new(<[u8; BLOCK_SIZE]>::arbitrary(u)?);
158        Ok(Self { u: ge, v, w })
159    }
160}
161
162/// Hash functions for IBE.
163mod hash {
164    use super::*;
165    use crate::{Hasher, Sha256};
166
167    /// H2: GT -> Block
168    ///
169    /// Used to mask the random sigma value.
170    pub fn h2(gt: &GT) -> Block {
171        let mut hasher = Sha256::new();
172        hasher.update(b"h2");
173        hasher.update(&gt.as_slice());
174        hasher.finalize().into()
175    }
176
177    /// H3: (sigma, M) -> Scalar
178    ///
179    /// Used to derive the random scalar r using RFC9380 hash-to-field.
180    pub fn h3(sigma: &Block, message: &[u8]) -> Scalar {
181        // Combine sigma and message
182        let mut combined = Vec::with_capacity(sigma.len() + message.len());
183        combined.extend_from_slice(sigma.as_ref());
184        combined.extend_from_slice(message);
185
186        // Map the combined bytes to a scalar via RFC9380 hash-to-field.
187        //
188        // Strictly speaking, this needs to not be 0, but the odds of this happening
189        // are negligible.
190        Scalar::map(DST, &combined)
191    }
192
193    /// H4: sigma -> Block
194    ///
195    /// Used to mask the message.
196    pub fn h4(sigma: &Block) -> Block {
197        let mut hasher = Sha256::new();
198        hasher.update(b"h4");
199        hasher.update(sigma.as_ref());
200        hasher.finalize().into()
201    }
202}
203
204/// XOR two [Block]s together.
205///
206/// This function takes advantage of the fixed-size nature of blocks
207/// to enable better compiler optimizations. Since we know blocks are
208/// exactly 32 bytes, we can unroll the operation completely.
209#[inline]
210fn xor(a: &Block, b: &Block) -> Block {
211    let a_bytes = a.as_ref();
212    let b_bytes = b.as_ref();
213
214    // Since Block is exactly 32 bytes, we can use array initialization
215    // with const generics to let the compiler fully optimize this
216    Block::new([
217        a_bytes[0] ^ b_bytes[0],
218        a_bytes[1] ^ b_bytes[1],
219        a_bytes[2] ^ b_bytes[2],
220        a_bytes[3] ^ b_bytes[3],
221        a_bytes[4] ^ b_bytes[4],
222        a_bytes[5] ^ b_bytes[5],
223        a_bytes[6] ^ b_bytes[6],
224        a_bytes[7] ^ b_bytes[7],
225        a_bytes[8] ^ b_bytes[8],
226        a_bytes[9] ^ b_bytes[9],
227        a_bytes[10] ^ b_bytes[10],
228        a_bytes[11] ^ b_bytes[11],
229        a_bytes[12] ^ b_bytes[12],
230        a_bytes[13] ^ b_bytes[13],
231        a_bytes[14] ^ b_bytes[14],
232        a_bytes[15] ^ b_bytes[15],
233        a_bytes[16] ^ b_bytes[16],
234        a_bytes[17] ^ b_bytes[17],
235        a_bytes[18] ^ b_bytes[18],
236        a_bytes[19] ^ b_bytes[19],
237        a_bytes[20] ^ b_bytes[20],
238        a_bytes[21] ^ b_bytes[21],
239        a_bytes[22] ^ b_bytes[22],
240        a_bytes[23] ^ b_bytes[23],
241        a_bytes[24] ^ b_bytes[24],
242        a_bytes[25] ^ b_bytes[25],
243        a_bytes[26] ^ b_bytes[26],
244        a_bytes[27] ^ b_bytes[27],
245        a_bytes[28] ^ b_bytes[28],
246        a_bytes[29] ^ b_bytes[29],
247        a_bytes[30] ^ b_bytes[30],
248        a_bytes[31] ^ b_bytes[31],
249    ])
250}
251
252/// Encrypt a message for a given target.
253///
254/// # Steps
255/// 1. Generate random sigma
256/// 2. Derive encryption randomness r = H3(sigma || message)
257/// 3. Create commitment U = r * G
258/// 4. Mask sigma with the pairing result
259/// 5. Mask the message with H4(sigma)
260///
261/// # Arguments
262/// * `rng` - Random number generator
263/// * `public` - Master public key
264/// * `target` - Payload over which a signature will decrypt the message
265/// * `message` - Message to encrypt
266///
267/// # Returns
268/// * `Ciphertext<V>` - The encrypted ciphertext
269pub fn encrypt<R: CryptoRngCore, V: Variant>(
270    rng: &mut R,
271    public: V::Public,
272    target: (Option<&[u8]>, &[u8]),
273    message: &Block,
274) -> Ciphertext<V> {
275    // Hash target to get Q_id in signature group using the variant's message DST
276    let q_id = match target {
277        (None, target) => hash_message::<V>(V::MESSAGE, target),
278        (Some(namespace), target) => hash_message_namespace::<V>(V::MESSAGE, namespace, target),
279    };
280
281    // Generate random sigma
282    let mut sigma_array = [0u8; BLOCK_SIZE];
283    rng.fill_bytes(&mut sigma_array);
284    let sigma = 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 = hash::h2(&gt);
302    let v = xor(&sigma, &h2_value);
303
304    // Compute W = M XOR H4(sigma)
305    let h4_value = 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::{keypair, sign_message},
354        variant::{MinPk, MinSig},
355    };
356    use commonware_math::algebra::Random as _;
357    use rand::thread_rng;
358
359    #[test]
360    fn test_encrypt_decrypt_minpk() {
361        let mut rng = thread_rng();
362
363        // Generate master keypair
364        let (master_secret, master_public) = 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 = sign_message::<MinPk>(&master_secret, None, &target);
372
373        // Encrypt
374        let ciphertext = encrypt::<_, MinPk>(
375            &mut rng,
376            master_public,
377            (None, &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 = thread_rng();
391
392        // Generate master keypair
393        let (master_secret, master_public) = 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 = sign_message::<MinSig>(&master_secret, None, &target);
401
402        // Encrypt
403        let ciphertext = encrypt::<_, MinSig>(
404            &mut rng,
405            master_public,
406            (None, &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 = thread_rng();
420
421        // Generate two different master keypairs
422        let (_, master_public1) = keypair::<_, MinPk>(&mut rng);
423        let (master_secret2, _) = 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            (None, &target),
433            &Block::new(*message),
434        );
435
436        // Try to decrypt with signature from second master
437        let wrong_signature = sign_message::<MinPk>(&master_secret2, None, &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 = thread_rng();
446
447        let (master_secret, master_public) = 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 = sign_message::<MinPk>(&master_secret, None, &target);
453
454        // Encrypt
455        let ciphertext = encrypt::<_, MinPk>(
456            &mut rng,
457            master_public,
458            (None, &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 = thread_rng();
480
481        // Generate master keypair
482        let (master_secret, master_public) = 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 = sign_message::<MinPk>(&master_secret, Some(namespace), &target);
491
492        // Encrypt with namespace
493        let ciphertext = encrypt::<_, MinPk>(
494            &mut rng,
495            master_public,
496            (Some(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 = thread_rng();
510
511        // Generate master keypair
512        let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
513
514        let namespace = b"example.org";
515        let target = 100u64.to_be_bytes();
516        let message = b"Namespace vs no namespace - 32by"; // 32 bytes
517
518        // Generate signature without namespace
519        let signature_no_ns = sign_message::<MinPk>(&master_secret, None, &target);
520
521        // Generate signature with namespace
522        let signature_ns = sign_message::<MinPk>(&master_secret, Some(namespace), &target);
523
524        // Encrypt with namespace
525        let ciphertext_ns = encrypt::<_, MinPk>(
526            &mut rng,
527            master_public,
528            (Some(namespace), &target),
529            &Block::new(*message),
530        );
531
532        // Encrypt without namespace
533        let ciphertext_no_ns = encrypt::<_, MinPk>(
534            &mut rng,
535            master_public,
536            (None, &target),
537            &Block::new(*message),
538        );
539
540        // Try to decrypt namespaced ciphertext with non-namespaced signature - should fail
541        let result1 = decrypt::<MinPk>(&signature_no_ns, &ciphertext_ns);
542        assert!(result1.is_none());
543
544        // Try to decrypt non-namespaced ciphertext with namespaced signature - should fail
545        let result2 = decrypt::<MinPk>(&signature_ns, &ciphertext_no_ns);
546        assert!(result2.is_none());
547
548        // Correct decryptions should succeed
549        let decrypted_ns = decrypt::<MinPk>(&signature_ns, &ciphertext_ns)
550            .expect("Decryption with matching namespace should succeed");
551        let decrypted_no_ns = decrypt::<MinPk>(&signature_no_ns, &ciphertext_no_ns)
552            .expect("Decryption without namespace should succeed");
553
554        assert_eq!(message.as_ref(), decrypted_ns.as_ref());
555        assert_eq!(message.as_ref(), decrypted_no_ns.as_ref());
556    }
557
558    #[test]
559    fn test_cca_modified_v() {
560        let mut rng = thread_rng();
561
562        let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
563        let target = 110u64.to_be_bytes();
564        let message = b"Another CCA test message 32bytes"; // 32 bytes
565
566        // Generate signature over the target
567        let signature = sign_message::<MinPk>(&master_secret, None, &target);
568
569        // Encrypt
570        let ciphertext = encrypt::<_, MinPk>(
571            &mut rng,
572            master_public,
573            (None, &target),
574            &Block::new(*message),
575        );
576
577        // Modify V component (encrypted sigma)
578        let mut v_bytes = [0u8; BLOCK_SIZE];
579        v_bytes.copy_from_slice(ciphertext.v.as_ref());
580        v_bytes[0] ^= 0x01;
581        let tampered_ciphertext = Ciphertext {
582            u: ciphertext.u,
583            v: Block::new(v_bytes),
584            w: ciphertext.w,
585        };
586
587        // Try to decrypt - should fail due to verification
588        let result = decrypt::<MinPk>(&signature, &tampered_ciphertext);
589        assert!(result.is_none());
590    }
591
592    #[test]
593    fn test_cca_modified_u() {
594        let mut rng = thread_rng();
595
596        let (master_secret, master_public) = keypair::<_, MinPk>(&mut rng);
597        let target = 70u64.to_be_bytes();
598        let message = b"CCA security test message 32 byt"; // 32 bytes
599
600        // Generate signature over the target
601        let signature = sign_message::<MinPk>(&master_secret, None, &target);
602
603        // Encrypt
604        let mut ciphertext = encrypt::<_, MinPk>(
605            &mut rng,
606            master_public,
607            (None, &target),
608            &Block::new(*message),
609        );
610
611        // Modify U component (this should make decryption fail due to FO transform)
612        let mut modified_u = ciphertext.u;
613        modified_u *= &Scalar::random(&mut rng);
614        ciphertext.u = modified_u;
615
616        // Try to decrypt - should fail
617        let result = decrypt::<MinPk>(&signature, &ciphertext);
618        assert!(result.is_none());
619    }
620
621    #[cfg(feature = "arbitrary")]
622    mod conformance {
623        use super::*;
624        use commonware_codec::conformance::CodecConformance;
625
626        commonware_conformance::conformance_tests! {
627            CodecConformance<Ciphertext<MinPk>>,
628        }
629    }
630}