timed_release_crypto/lib.rs
1#![deny(dead_code, unused_variables, missing_docs, unused_doc_comments)]
2#![allow(unused)]
3
4/*!
5# AES-GCM Authenticated Encryption
6AES (Advanced Encryption Standard) is a widely used encryption algorithm for
7securing data. AES has several _operator modes_, of which we have selected GCM
8(Galois/Counter Mode). GCM combines encryption with authentication.
9This ensures that the data is confidential, but also mechanisms to verify that
10the data hasn’t been tampered with.
11
12# Large Numbers
13Working with large cryptographically secure numbers in Rust involves using
14crates that provide efficient, secure, and accurate arithmetic for
15numbers far beyond the size of standard primitive data types like u64 or
16u128. This is essential in cryptographic contexts where numbers can be
17hundreds or even thousands of bits long.
18
19Rust does not provide these capabilities natively for large numbers, so we
20are going to use the crates; `num-bigint` and `rug`.
21num-bigint is beginner-friendly and well-documented.
22
23We will lean heavily on `Biguint` crate to handle common cryptography primitives
24and modular operations, such as:
25+ large prime numbers : secure generation of large primes
26+ modular arithmetic : a mod n in order to ensure that comps stay inside n
27+ modular exponentiation : a^b mod n
28+ modulo inverse : computing (x^-1 mod n)
29
30# Notes on the use of S, modular exponentiation by squaring
31Computing `x^2 mod p` is generally assumed (TODO: citation needed) to be a
32_single operation_ that takes _constant time_. For example, one could just look
33up the multiplication table, which has only p^2 entries and can be precomputed.
34For further elaborations on this important topic:
351. https://en.m.wikipedia.org/wiki/Exponentiation_by_squaring
362. https://math.stackexchange.com/questions/2944032/
37why-is-the-algorithm-for-modular-exponentiation-by-squaring-considered-as-poly-t
38
39# Primality Testing
40... TODO: Elaborate on the use of rug.
41We use the `rug` crate to perform primatlity testing.
42
43# References:
44[1] R. L. Rivest, A. Shamir, and D. A. Wagner. 1996. Time-lock Puzzles and
45    Timed-release Crypto. Technical Report. Cambridge, MA, USA.
46
47[2] Timothy C. May. Timed-release crypto, February 1993.
48    https://cypherpunks.venona.com/date/1993/02/msg00306.html
49    and https://cypherpunks.venona.com/date/1993/02/msg00129.html
50*/
51
52////////////////////////////////////////////////////////////////////////////////
53// Imports
54use aes_gcm::{
55    aead::{Aead, OsRng},
56    Aes256Gcm, Key, KeyInit, Nonce,
57};
58use num_bigint::{BigUint, RandBigInt};
59use num_traits::One;
60use rand::{thread_rng, RngCore};
61
62////////////////////////////////////////////////////////////////////////////////
63// Utilities
64// TODO: consider removing this or moving it to a separate module.
65/// Utility function which does modular exponentiation of Biguint:
66/// a^b mod n == (base^exponent) % n
67#[allow(dead_code)]
68fn mod_exp(base: &BigUint, exponent: &BigUint, modulo: &BigUint) -> BigUint {
69    // TODO: Remove the two lines below if base.modpow is Copy.
70    // let mod_exp = base.modpow(exponent, modulo);
71    // println!("(a^e) mod n = {}", mod_exp);
72
73    base.modpow(exponent, modulo)
74}
75
76/// For primality test we use the classic Rabin-Miller test.
77#[derive(PartialEq, Debug)]
78pub enum Primality {
79    /// The number has been confirmed, deterministically, to be prime.
80    Prime,
81    /// The number is _only probably_ prime.
82    /// Using Rabin-Miller we can get the probability as low as we want, but we
83    /// can't get it to 0 (Prime).
84    /// For deterministic primality testing, see TODO: wiki link.
85    Probable,
86    /// The number has been confirmed to be a composite number (i.e. not Prime).
87    Composite,
88}
89
90////////////////////////////////////////////////////////////////////////////////
91/// TimeLockPuzzle
92///
93/// A tuple which representes the time-lock puzzle in its raw form.
94//  TODO: elaborate: Parameters represent: TimeLockPuzzle(n, a, t, CK, CM).
95struct TimeLockPuzzle {
96    _n: BigUint,
97    _a: BigUint,
98    _t: BigUint,
99    _ck: BigUint,
100    _cm: Vec<u8>,
101}
102
103////////////////////////////////////////////////////////////////////////////////
104/// Capsule
105///
106/// Consists of a Capsule, which can be thought of as a key chain, and a Puzzle.
107/// The Puzzle is an encrypted messagecan stored inside the Capsule.
108/// __Defition:__ a solver is anyone, attacker or intended recipient, trying to
109/// crack the encryption to get the store message inside.
110///
111/// TODO: improve wording of the following interface desriptoin:
112/// The Capsule has the following interface:
113/// +  `.create()`
114///     - function will generate private keys `p` and `q`, if provided with:
115///     `M`: a message,
116///     `a`: a constant,
117///     `t` : the desired time lock period, and...
118///     `s`: assumed squaring power of the solver
119///
120/// + `.arm()`
121///     - will TODO(zero out the private keys `p` and `q`) that were
122///     used during the creation of the puzzle, thereby locking the capsule.
123///
124/// + `.log()`
125///     - a method which will print all the steps which where taken during the
126///     creation of the Puzzle.
127///
128// TODO: turn off pub visibility and confirm that p and q are _NOT_ exported.
129pub struct Capsule {
130    // TODO: consider creating a Default for Capsule, with a:BigUint::from(2:32)
131    p: BigUint,
132    q: BigUint,
133    a: BigUint,
134    tlp: Option<TimeLockPuzzle>,
135}
136
137impl Default for Capsule {
138    fn default() -> Self {
139        Capsule {
140            p: Capsule::generate_large_random_prime(256u64),
141            q: Capsule::generate_large_random_prime(256u64),
142            a: BigUint::from(2u32),
143            tlp: None,
144        }
145    }
146}
147
148impl Capsule {
149    /// We create a new Capsule with a fresh pair of private keys, p and q.
150    pub fn new(&self, bits: u64) -> Self {
151        // TODO: verify: do we need to assert bits length > 160 here?
152        Capsule {
153            p: Capsule::generate_large_random_prime(bits),
154            q: Capsule::generate_large_random_prime(bits),
155            a: BigUint::from(2u32),
156            tlp: None,
157        }
158    }
159
160    /// Utility function in case we need to generate a new pair of private keys.
161    fn generate_new_keypair(&mut self, bits: u64) {
162        // TODO: verify: do we need to assert bits length > 160 here?
163        self.p = Capsule::generate_large_random_prime(bits);
164        self.q = Capsule::generate_large_random_prime(bits);
165    }
166
167    /// Function to create a puzzle and store it in the capsule.
168    /// TODO: elaborate : tuple (n,a,t,ck, cm) represents a time-lock-puzzle.
169    fn create_puzzle(
170        &self,
171        s: &BigUint,
172        t: &BigUint,
173        _plaintext: &[u8],
174        //) -> (BigUint, BigUint, BigUint, BigUint, Vec<u8>) {
175    ) -> TimeLockPuzzle {
176        // TODO: improve name of phi_n.
177        let n = self.compute_composite(&self.p, &self.q);
178        let phi_n = self.phi(&self.p, &self.q);
179        let t = self.compute_puzzle_strength(s, t);
180        // TODO: consider renaming crypto_system_key to aes_gcm_key
181        // to makes it clear that the key is part of the AES-GCM key process.
182
183        // TODO:consider changing the type of crypto_system_key to aes_gcm::Key.
184        let (crypto_sys_key, ciphertext) =
185            self.compute_cm(_plaintext);
186        let cipherkey =
187            self.compute_ck(&self.a, &phi_n, &t, &n, &crypto_sys_key);
188
189        // Puzzle created.
190        TimeLockPuzzle {
191            _n: n,
192            _a: self.a.clone(),
193            _t: t,
194            _ck: cipherkey,
195            _cm: ciphertext,
196        }
197    }
198}
199
200impl Capsule {
201    // TODO: work on next...
202    // lock()
203    // open()
204    // arm();
205    // log();
206}
207
208impl Capsule {
209    /// Returns a large random number of length based on the number of bits
210    /// passed as argument. The bit-size is expressed in u64.
211    ///
212    /// Large is a subjective term, but for our purposes we want to make sure
213    /// that the bits length is at least 160 bits.
214    ///
215    /// # Examples : exampel of use.
216    ///
217    /// ```
218    /// use timed_release_crypto::Capsule;
219    /// use num_bigint::{BigUint, RandBigInt};
220    /// // Generate two random 256-bit number.
221    /// let p: BigUint = Capsule::generate_large_random_number(256u64);
222    /// let q: BigUint = Capsule::generate_large_random_number(256u64);
223    /// assert_ne!(p,q);
224    /// println!("256-bit Random Number P: {}", p);
225    /// println!("256-bit Random Number Q: {}", q);
226    /// ```
227    ///
228    /// Panic : This function will panic if passed a bits value below 160.
229    ///
230    /// Failure : TODO: consider changing return value to Result<BigUint, Err>.
231    ///
232    /// TODO: clean up: Require modulo 2^32 - see p3, ref[1]
233    pub fn generate_large_random_number(bits: u64) -> BigUint {
234        // For Time-Lock-Puzzle purposes we want to work with over 159 bits.
235        assert!(bits > 159);
236
237        let mut rng = thread_rng();
238        let prime_candidate: BigUint = rng.gen_biguint(bits);
239        println!("Generated Large Number: {}", prime_candidate);
240
241        // TODO: remove: rand::thread_rng().gen_bigint(bits)
242        prime_candidate
243    }
244
245    /// Rudimentary probabilistic primality test using Fermat's Primality Test.
246    ///
247    /// Fermat's Primality Test checks if:
248    /// is a random base number, raised to n-1 modulo n, equal to 1?
249    /// If true, then the candidate is probably prime. Please note the word
250    /// _probably_! If we want to be 100% sure that the candidate is prime, we
251    /// need to use a deterministic, rather than probabilistic primality test.
252    ///
253    /// A deterministic method may be too slow for our purposes. In that case
254    /// an alternative is to use the Rabin-Miller primality test.
255    ///
256    /// TODO: Look up a deterministic primality-test method.
257    /// TODO: consider primality to be a feature.
258    fn is_probably_prime(candidate: &BigUint) -> bool {
259        // Substep 01: check that the candidate is larger than 1.
260        if *candidate <= BigUint::from(1u32) {
261            return false;
262        }
263
264        // Substep 02: Generate a random base number between [1, candidate).
265        // 1 <= base < candidate
266        let mut rng = thread_rng();
267        let base: BigUint = 
268            rng.gen_biguint_range(&BigUint::one(), candidate);
269
270        // Substep 03: Compute the modular: base^(candidate-1) % candidate .
271        let result = base.modpow(
272            &(candidate - BigUint::one()), 
273            candidate
274        );
275
276        // Substep 04: If the result is 1 then the candidate is probably prime.
277        // TODO: consider returning an enum:
278        // Primality::Prime,
279        // Primality::Probable,
280        // Primality::Composite,
281        result.is_one()
282    }
283
284    fn generate_large_random_prime(bits: u64) -> BigUint {
285        // TODO: Bench this loop to see how long 50 runs take.
286        loop {
287            let prime_candidate = Self::generate_large_random_number(bits);
288            if Self::is_probably_prime(&prime_candidate) {
289                return prime_candidate;
290            }
291        }
292    }
293
294    ////////////////////////////////////////////////////////////////////////////
295    /// Step 01: Generate (and test for primality) two distinct large secret 
296    /// prime numbers `P` and `Q` and multiply them together to form the 
297    /// composite `n=pq`.
298    ///
299    fn compute_composite(&self, p: &BigUint, q: &BigUint) -> BigUint {
300        // BigUint will panic if subraction reaches into negative numbers.
301        assert!(p >= &BigUint::from(0u32) && q >= &BigUint::from(0u32));
302
303        // Ref: [1] page 3. equation (1): n = p*q
304        p * q
305    }
306
307    ////////////////////////////////////////////////////////////////////////////
308    /// Step 02: Compute the Euler Totient: phi(n) = (P-1)*(Q-1).
309    /// TODO: lookup: is it possible to write the congruent equal sign?
310    /// The Euler Phi Function is used to ...
311    /// The formula `a^{phi(m)} is congruent with 1 (mod m)`.
312    /// TODO: Explain the mathematics behind using phi funciton.
313    ///
314    /// # Panic
315    /// BigUnit does not support subtraction resulting in a negative number.
316    /// We assert that both arguments are positive, instead of panicking.
317    fn phi(&self, p: &BigUint, q: &BigUint) -> BigUint {
318        // BigUint will panic if subraction reaches into negative numbers.
319        assert!(p >= &BigUint::from(0u32) && q >= &BigUint::from(0u32));
320
321        // Ref: [1] page 3. equation (2): phi(n) = (p-1)*(q-1)
322        (p - &BigUint::from(1u32)) * (q - &BigUint::from(1u32))
323    }
324
325    ////////////////////////////////////////////////////////////////////////////
326    /// Step 03: Compute `t=TS`, where:
327    /// + T is the prefered approximate period in seconds that the puzzle should
328    /// withstand an attempt to decrypt it.
329    /// + S is the number of `squarings modulo n` per second that can be 
330    /// performed by the agent (attacker/or intended receiver) trying to decrypt 
331    /// the message.
332    /// 
333    /// TODO: question: what should we base our S value on?
334    fn compute_puzzle_strength(&self, s: &BigUint, t: &BigUint) -> BigUint {
335        // TODO: consider if S needs to be dealt with here.
336        let t = s * t;
337        t
338    }
339
340    ////////////////////////////////////////////////////////////////////////////
341    // Step 04: Generate a `random key K` for a conventional cryptosystem.
342    // We're using AES-GCM (with a 256-bit key) via the `aes_gcm` crate.
343    // The crate provides AES-256-GCM encryption and decryption capabilities.
344    fn compute_cm(&self, plaintext: &[u8]) -> (Key<Aes256Gcm>, Vec<u8>) {
345        // Substep 01
346        // Generate a random 32-byte (256 bit) key for AES_256.
347        // We use `OsRng` to generate cryptographically secure random numbers.
348        // Note: Key::<Aes256Gcm> is equal to GenericArray<u8, U32>.
349        let key: Key<Aes256Gcm> = Aes256Gcm::generate_key(&mut OsRng);
350        println!("Generated Key: {:?}", key);
351
352        // Substep 02
353        // We generate a random nonce (Number used ONCE), a random 96-bit
354        // (12 bytes, [u8;12]) value, that is used to ensure the uniqueness of our
355        // encryption operations.
356        //
357        // TODO: remove the line below if not needed and tests pass.
358        // let mut nonce: BigUint = Nonce::generate(&mut OsRng);
359        let mut nonce_bytes = [0u8; 12];
360        OsRng.fill_bytes(&mut nonce_bytes);
361        let nonce = 
362            Nonce::from_slice(&nonce_bytes);
363        println!("Generated Nonce: {:?}", nonce);
364
365        // Substep 03
366        // Encrypt the message M with key K and encryption algoritym AES-GCM
367        // AES-GCM is an excellent tool for encryption, key initialization,
368        // and random number generation. We use AES-256-GCM to generate keys of
369        // length 256 bits to obtain the cipertext: C_M = AES-GCM(K,M).
370        //
371        // Initialize the AES-GCM cipher with the generated key.
372        // Key: A randomly generated 256-bit key used for encryption/decryption.
373        let cipher = Aes256Gcm::new(&key);
374
375        // Substep 04
376        // The plaintext message `M` is encrypted into ciphertext form, `CM`.
377        // The plaintext needs to be in byte form, for example `b"hello"`.
378        // Encrypt the plaintext: CM = AES-GCM(K,M), using the nonce and key.
379        println!("Plaintext: {:?}", plaintext);
380
381        let cm = cipher
382            .encrypt(&nonce, plaintext.as_ref())
383            .expect("Encryption failed");
384        println!("Ciphertext: {:?}", cm);
385
386        // TODO: consider returning a Result rather than simply doing an assert
387        // to check that CM isn't empty.
388        assert!(cm.len() > 0);
389
390        // TODO: does the nonce need to be returned/persisted or can we simply
391        // discard it for now?
392        (key, cm)
393    }
394
395    ////////////////////////////////////////////////////////////////////////////
396    // Step 05: Pick a random `a module n`, with 1 < a < n, and compute
397    // TODO: see which arguments that are actually needed.
398    fn compute_ck(
399        &self,
400        a: &BigUint,
401        phi_n: &BigUint,
402        t: &BigUint,
403        n: &BigUint,
404        k: &Key<Aes256Gcm>,
405    ) -> BigUint {
406        // TODO: consider moving `a` into a global const or part of Capsule.
407        // Note: According to Ref. [1] page.4: "Indeed, in practice choosing a
408        // fixed value of `a=2` should be safe with high probability".
409        //let a: BigUint = BigUint::from(2u32);
410        
411        // Substep 01
412        // Ref: [1] page 4. equation (6): e = 2^t (mod phi(n))
413        // TODO: remove line below once confirmed line underneath is correct.
414        let e: BigUint = a.modpow(t, phi_n);
415
416        // Substep 02
417        // Ref: [1] page 4. equation (7): b = a^e (mod n)
418        // TODO: remove line below, once confirmed line underneath is correct.
419        let b: BigUint = a.modpow(&e, n);
420
421        // Substep 03
422        // Encryption of the public key K is done by computing:
423        // Ref: [1] page 4. equation (5): C_K = K + a^2^t (mod n)
424        //
425        // I faced some challenges here so I'm documenting clarifications.
426        // The passed crypto_system_key `k` is of type Key<Aes256Gcm> and is
427        // essentially an array of bytes, (GenericArray<u8, U32>).
428        // 
429        // Before we can perform the operation K + a^2^t (mod n), 
430        // where a^2^t is of type BigUint, we need to convert k:Key<Aes256Gcm> 
431        // into k:BigUint.
432        //
433        // We can use the `.as_slice()` method to get the raw byte array,
434        // and then use BigUint::from_bytes_be to convert it into a BigUint.
435        let ck = (BigUint::from_bytes_be(k.as_slice()) + b) % n;
436
437        ck
438    }
439} // Capsule
440
441////////////////////////////////////////////////////////////////////////////////
442// Tests
443#[cfg(test)]
444mod test {
445    use super::*;
446
447    #[test]
448    fn test_phi() {
449        let p = BigUint::from(631u32);
450        let q = BigUint::from(2153u32);
451        let c = Capsule::default();
452
453        assert_eq!(c.phi(&p, &q), BigUint::from(1355760u32));
454    }
455
456    #[test]
457    fn test_bit_size_of_generated_number() {
458        let bits = 256;
459        let p = Capsule::generate_large_random_number(bits);
460
461        // Check the number of bits
462        let num_bits = p.bits();
463        assert!(
464            num_bits <= bits && num_bits > bits - 8,
465            "The number of bits ({}) is not in the expected range for {} bits.",
466            num_bits, bits
467        );
468    }
469
470    #[test]
471    fn test_randomness_of_generated_numbers() {
472        let bits = 256;
473        let p = Capsule::generate_large_random_number(bits);
474        let q = Capsule::generate_large_random_number(bits);
475
476        // TODO: instead of pannicking - simply rerun the generation.
477        assert_ne!(
478            p, q,
479            "The two generated numbers are the same, which is very unlikely."
480        );
481    }
482
483    #[test]
484    fn test_compute_composite() {
485
486        let capsule = Capsule {
487            p: BigUint::one(),
488            q: BigUint::one(),
489            a: BigUint::from(2u32),
490            tlp: None,
491        };
492
493        // Test values for p and q
494        let p = BigUint::from(13u32);
495        let q = BigUint::from(17u32);
496        // Compute the composite
497        let result = capsule.compute_composite(&p, &q);
498        // Expected result
499        let expected = p.clone() * q.clone();
500        // Assert the result matches the expected value
501        assert_eq!(result, expected, "The computed composite is incorrect");
502    }
503}
504
505// END
506////////////////////////////////////////////////////////////////////////////////