hopr_crypto_sphinx/
prg.rs

1use aes::cipher::{KeyIvInit, StreamCipher};
2use hopr_crypto_types::primitives::SecretKey;
3use zeroize::ZeroizeOnDrop;
4
5use crate::derivation::generate_key_iv;
6
7// Module-specific constants
8const AES_BLOCK_SIZE: usize = 16;
9const AES_KEY_SIZE: usize = 16;
10
11const PRG_KEY_LENGTH: usize = AES_KEY_SIZE;
12const PRG_COUNTER_LENGTH: usize = 4;
13const PRG_IV_LENGTH: usize = AES_BLOCK_SIZE - PRG_COUNTER_LENGTH;
14
15const HASH_KEY_PRG: &str = "HASH_KEY_PRG";
16
17type Aes128Ctr32BE = ctr::Ctr32BE<aes::Aes128>;
18
19/// Parameters for the Pseudo-Random Generator (PRG) function
20/// This consists of IV and the raw secret key for use by the underlying block cipher.
21#[derive(ZeroizeOnDrop)]
22pub struct PRGParameters {
23    key: [u8; PRG_KEY_LENGTH],
24    iv: [u8; PRG_IV_LENGTH],
25}
26
27impl Default for PRGParameters {
28    fn default() -> Self {
29        Self {
30            key: [0u8; PRG_KEY_LENGTH],
31            iv: [0u8; PRG_IV_LENGTH],
32        }
33    }
34}
35
36impl PRGParameters {
37    /// Creates new parameters for the PRG by expanding the given
38    /// keying material into the secret key and IV for the underlying block cipher.
39    pub fn new(secret: &SecretKey) -> Self {
40        let mut ret = PRGParameters::default();
41        generate_key_iv(secret, HASH_KEY_PRG.as_bytes(), &mut ret.key, &mut ret.iv, true);
42        ret
43    }
44}
45
46/// Pseudo-Random Generator (PRG) function that is instantiated
47/// using AES-128 block cipher in Counter mode (with 32-bit counter).
48/// It forms an infinite sequence of pseudo-random bytes (generated deterministically from the parameters)
49/// and can be queried by chunks using the `digest` function.
50#[allow(clippy::upper_case_acronyms)]
51#[derive(ZeroizeOnDrop)]
52pub struct PRG {
53    params: PRGParameters,
54}
55
56impl PRG {
57    /// Creates a new PRG instance using the given parameters
58    pub fn from_parameters(params: PRGParameters) -> Self {
59        Self { params }
60    }
61}
62
63impl PRG {
64    /// Gets the chunk of the pseudorandom sequence generated by the PRG
65    /// between `from` (incl.) and `to` (excl.) offsets.
66    pub fn digest(&self, from: usize, to: usize) -> Box<[u8]> {
67        assert!(from < to);
68
69        let first_block = from / AES_BLOCK_SIZE;
70        let start = from % AES_BLOCK_SIZE;
71
72        let last_block_end = to % AES_BLOCK_SIZE;
73        let last_block = to / AES_BLOCK_SIZE + if last_block_end != 0 { 1 } else { 0 };
74        let count_blocks = last_block - first_block;
75        let end = AES_BLOCK_SIZE * count_blocks
76            - if last_block_end > 0 {
77                AES_BLOCK_SIZE - last_block_end
78            } else {
79                0
80            };
81
82        // Allocate required memory
83        let mut key_stream = vec![0u8; count_blocks * AES_BLOCK_SIZE];
84
85        // Set correct counter value to the IV
86        // NOTE: We are using Big Endian ordering for the counter
87        let mut new_iv = [0u8; AES_BLOCK_SIZE];
88        let (prefix, counter) = new_iv.split_at_mut(PRG_IV_LENGTH);
89        prefix.copy_from_slice(&self.params.iv);
90        counter.copy_from_slice(&(first_block as u32).to_be_bytes());
91
92        // Create key stream
93        let mut cipher = Aes128Ctr32BE::new(&self.params.key.into(), &new_iv.into());
94        cipher.apply_keystream(&mut key_stream);
95
96        // Slice the result accordingly
97        let result = &key_stream.as_slice()[start..end];
98        result.into()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use hex_literal::hex;
106
107    #[test]
108    fn test_prg_single_block() {
109        let out = PRG::from_parameters(PRGParameters {
110            key: [0u8; 16],
111            iv: [0u8; 12],
112        })
113        .digest(5, 10);
114        assert_eq!(5, out.len());
115    }
116
117    #[test]
118    fn test_prg_more_blocks() {
119        let out = PRG::from_parameters(PRGParameters {
120            key: [0u8; 16],
121            iv: [0u8; 12],
122        })
123        .digest(0, AES_BLOCK_SIZE * 2);
124
125        assert_eq!(32, out.len());
126    }
127
128    #[test]
129    fn test_prg_across_blocks() {
130        let out = PRG::from_parameters(PRGParameters {
131            key: [0u8; 16],
132            iv: [0u8; 12],
133        })
134        .digest(5, AES_KEY_SIZE * 2 + 10);
135
136        assert_eq!(AES_BLOCK_SIZE * 2 + 5, out.len());
137    }
138
139    #[test]
140    fn test_prg_parameters() {
141        let expected_key = hex!("c642ceb7af7a65308ab2dbff9f6c7132");
142        let expected_iv = hex!("a735d1806513af957dd25de7");
143
144        let params = PRGParameters::new(&SecretKey::default());
145
146        assert_eq!(expected_key, params.key);
147        assert_eq!(expected_iv, params.iv)
148    }
149}