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(¶ms);
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}