Skip to main content

toolkit_zero/encryption/timelock/
utility.rs

1use super::{TimeLockCadence, TimeLockSalts, KdfParams, TimePrecision, TimeFormat};
2
3/// Compact, self-contained encoding of **all** encryption-time settings —
4/// suitable for plaintext storage within a ciphertext header.
5///
6/// Produced by [`pack`] and supplied to [`timelock`](super::timelock) or
7/// [`timelock_async`](super::timelock_async) as `params: Some(header)` on the
8/// **decryption side**. Only the cadence variant discriminant is recorded; the
9/// actual calendar values (weekday, day-of-month, month) are not stored and
10/// are instead read from the live system clock during decryption.
11///
12/// | Field              | Encoding / Notes                                          |
13/// |--------------------|-----------------------------------------------------------|
14/// | `time_precision`   | `0`=Hour · `1`=Quarter · `2`=Minute                      |
15/// | `time_format`      | `0`=12 hr (`Hour12`) · `1`=24 hr (`Hour24`)              |
16/// | `cadence_variant`  | `0`=None · `1`=DayOfWeek · `2`=DayOfMonth                |
17/// |                    | `3`=MonthOfYear · `4`=DayOfWeekInMonth                   |
18/// |                    | `5`=DayOfMonthInMonth · `6`=DayOfWeekAndDayOfMonth       |
19/// | `salts`            | Three 32-byte salts (not secret; prevent precomputation) |
20/// | `kdf_params`       | Argon2id + scrypt work factors                           |
21#[cfg(any(feature = "enc-timelock-keygen-now", feature = "enc-timelock-keygen-input", feature = "enc-timelock-async-keygen-now", feature = "enc-timelock-async-keygen-input"))]
22#[derive(Debug, Clone)]
23pub struct TimeLockParams {
24    /// `0` = [`TimePrecision::Hour`],
25    /// `1` = [`TimePrecision::Quarter`],
26    /// `2` = [`TimePrecision::Minute`].
27    pub time_precision: u8,
28    /// `0` = [`TimeFormat::Hour12`] (12-hour clock),
29    /// `1` = [`TimeFormat::Hour24`] (24-hour clock).
30    pub time_format: u8,
31    /// [`TimeLockCadence`] variant discriminant (0–6).  The actual calendar
32    /// values for that variant (which weekday, which month, etc.) are
33    /// **not** stored here — the decryption path reads them from the clock.
34    pub cadence_variant: u8,
35    /// Three KDF salts generated at encryption time.
36    ///
37    /// Salts are **not secret**; storing them in the header is standard practice
38    /// and prevents precomputation attacks.
39    pub salts: TimeLockSalts,
40    /// The KDF work-factor parameters used at encryption time.
41    ///
42    /// Stored so the decryption side uses identical memory and iteration costs.
43    pub kdf_params: KdfParams,
44}
45
46/// Encode [`TimePrecision`], [`TimeFormat`], and a [`TimeLockCadence`] reference
47/// into a compact [`TimeLockParams`] for storage in a ciphertext header.
48///
49/// Only the **variant discriminant** of `cadence` is recorded; the actual day,
50/// weekday, or month values are intentionally discarded.
51///
52/// # Example
53///
54/// ```no_run
55/// # use toolkit_zero::encryption::timelock::*;
56/// let salts = TimeLockSalts::generate();
57/// let kdf   = KdfPreset::Balanced.params();
58/// let p = pack(
59///     TimePrecision::Minute,
60///     TimeFormat::Hour24,
61///     &TimeLockCadence::DayOfWeek(Weekday::Tuesday),
62///     salts,
63///     kdf,
64/// );
65/// // p.time_precision == 2, p.time_format == 1, p.cadence_variant == 1
66/// ```
67#[cfg(any(feature = "enc-timelock-keygen-now", feature = "enc-timelock-keygen-input", feature = "enc-timelock-async-keygen-now", feature = "enc-timelock-async-keygen-input"))]
68pub fn pack(
69    precision:  TimePrecision,
70    format:     TimeFormat,
71    cadence:    &TimeLockCadence,
72    salts:      TimeLockSalts,
73    kdf_params: KdfParams,
74) -> TimeLockParams {
75    let tp = match precision {
76        TimePrecision::Hour    => 0,
77        TimePrecision::Quarter => 1,
78        TimePrecision::Minute  => 2,
79    };
80    let tf = match format {
81        TimeFormat::Hour12 => 0,
82        TimeFormat::Hour24 => 1,
83    };
84    TimeLockParams { time_precision: tp, time_format: tf, cadence_variant: cadence.variant_id(), salts, kdf_params }
85}
86
87/// Decode a [`TimeLockParams`] into its constituent [`TimePrecision`],
88/// [`TimeFormat`], and raw cadence variant discriminant.
89///
90/// The returned `u8` maps as follows:
91/// `0` = None, `1` = DayOfWeek, `2` = DayOfMonth, `3` = MonthOfYear,
92/// `4` = DayOfWeekInMonth, `5` = DayOfMonthInMonth, `6` = DayOfWeekAndDayOfMonth.
93/// Any unrecognised value defaults to `0` (None).
94///
95/// # Example
96///
97/// ```no_run
98/// # use toolkit_zero::encryption::timelock::*;
99/// let params = pack(
100///     TimePrecision::Minute,
101///     TimeFormat::Hour24,
102///     &TimeLockCadence::DayOfWeekInMonth(Weekday::Tuesday, Month::February),
103///     TimeLockSalts::generate(),
104///     KdfPreset::Balanced.params(),
105/// );
106/// let (precision, format, variant) = unpack(&params);
107/// // precision == TimePrecision::Minute, format == TimeFormat::Hour24, variant == 4
108/// ```
109#[cfg(any(feature = "enc-timelock-keygen-now", feature = "enc-timelock-keygen-input", feature = "enc-timelock-async-keygen-now", feature = "enc-timelock-async-keygen-input"))]
110pub fn unpack(p: &TimeLockParams) -> (TimePrecision, TimeFormat, u8) {
111    let precision = match p.time_precision {
112        0 => TimePrecision::Hour,
113        1 => TimePrecision::Quarter,
114        _ => TimePrecision::Minute,
115    };
116    let format = match p.time_format {
117        0 => TimeFormat::Hour12,
118        _ => TimeFormat::Hour24,
119    };
120    (precision, format, p.cadence_variant)
121}