Skip to main content

fpr_ff1/
ff1.rs

1//! The public FF1 type (plan 00009 STEP-06).
2
3use std::collections::HashMap;
4
5use crate::engine;
6use crate::error::Error;
7use crate::validate::{self, Operand, MAX_LEN};
8
9/// An FF1 cipher: a key, a radix and optional configuration, validated once.
10///
11/// Every input is validated before any computation, with the same rules,
12/// order and messages as the `fpr-ff1` Python package; see [`Error`].
13///
14/// An instance holds only its configuration. No cipher context is cached:
15/// each call builds its own AES key schedule, so an instance may be shared
16/// freely between threads.
17#[derive(Clone)]
18pub struct FF1 {
19    key: Vec<u8>,
20    radix: u32,
21    min_length: usize,
22    default_tweak: Vec<u8>,
23    min_tweak_len: Option<usize>,
24    max_tweak_len: Option<usize>,
25    alphabet: Option<Alphabet>,
26}
27
28// The key is deliberately left out of Debug output: it is secret material.
29impl std::fmt::Debug for FF1 {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.debug_struct("FF1")
32            .field("radix", &self.radix)
33            .field("min_length", &self.min_length)
34            .field("min_tweak_len", &self.min_tweak_len)
35            .field("max_tweak_len", &self.max_tweak_len)
36            .field("has_alphabet", &self.alphabet.is_some())
37            .finish_non_exhaustive()
38    }
39}
40
41/// The characters for the string interface, one per numeral.
42#[derive(Clone)]
43struct Alphabet {
44    chars: Vec<char>,
45    index: HashMap<char, u16>,
46}
47
48/// Configures an [`FF1`]; obtained from [`FF1::builder`].
49#[derive(Clone)]
50#[must_use = "a builder does nothing until .build() is called"]
51pub struct Builder<'a> {
52    key: &'a [u8],
53    radix: u32,
54    alphabet: Option<&'a str>,
55    tweak: &'a [u8],
56    min_tweak_len: Option<usize>,
57    max_tweak_len: Option<usize>,
58}
59
60impl<'a> Builder<'a> {
61    /// Enable the string interface: exactly `radix` distinct characters, the
62    /// character at position `i` standing for numeral `i`.
63    pub fn alphabet(mut self, alphabet: &'a str) -> Self {
64        self.alphabet = Some(alphabet);
65        self
66    }
67
68    /// The tweak used when a call passes `None`. Defaults to empty.
69    pub fn tweak(mut self, tweak: &'a [u8]) -> Self {
70        self.tweak = tweak;
71        self
72    }
73
74    /// Inclusive minimum tweak length, checked on every tweak.
75    pub fn min_tweak_len(mut self, len: usize) -> Self {
76        self.min_tweak_len = Some(len);
77        self
78    }
79
80    /// Inclusive maximum tweak length, checked on every tweak. `0` is literal
81    /// and admits only an empty tweak; leave it unset for no maximum.
82    pub fn max_tweak_len(mut self, len: usize) -> Self {
83        self.max_tweak_len = Some(len);
84        self
85    }
86
87    /// Validate the configuration and build the cipher.
88    ///
89    /// Checks run in the Python package's order -- key, radix, tweak
90    /// bounds, default tweak, alphabet -- so a configuration with several
91    /// faults reports the same one in both.
92    pub fn build(self) -> Result<FF1, Error> {
93        validate::key(self.key)?;
94        validate::radix(self.radix)?;
95        validate::tweak_bounds(self.min_tweak_len, self.max_tweak_len)?;
96        validate::tweak(self.tweak.len(), self.min_tweak_len, self.max_tweak_len)?;
97        let alphabet = match self.alphabet {
98            None => None,
99            Some(text) => {
100                let chars = validate::alphabet(text, self.radix)?;
101                // radix < 2**16, so every position fits a u16.
102                let index = chars
103                    .iter()
104                    .enumerate()
105                    .map(|(i, &c)| (c, u16::try_from(i).expect("position < radix < 2**16")))
106                    .collect();
107                Some(Alphabet { chars, index })
108            }
109        };
110        Ok(FF1 {
111            key: self.key.to_vec(),
112            radix: self.radix,
113            min_length: validate::min_length(self.radix),
114            default_tweak: self.tweak.to_vec(),
115            min_tweak_len: self.min_tweak_len,
116            max_tweak_len: self.max_tweak_len,
117            alphabet,
118        })
119    }
120}
121
122impl FF1 {
123    /// An FF1 cipher with no alphabet, an empty default tweak and no tweak
124    /// bounds. Equivalent to `FF1::builder(key, radix).build()`.
125    ///
126    /// `key` must be 16, 24 or 32 bytes; `radix` must satisfy
127    /// `2 <= radix < 2**16`.
128    pub fn new(key: &[u8], radix: u32) -> Result<FF1, Error> {
129        FF1::builder(key, radix).build()
130    }
131
132    /// Start configuring an FF1 cipher with an alphabet, a default tweak or
133    /// tweak-length bounds.
134    pub fn builder(key: &[u8], radix: u32) -> Builder<'_> {
135        Builder {
136            key,
137            radix,
138            alphabet: None,
139            tweak: &[],
140            min_tweak_len: None,
141            max_tweak_len: None,
142        }
143    }
144
145    /// Encrypt a numeral sequence (SP 800-38G Algorithm 7). Each numeral must
146    /// be less than the radix. `tweak` of `None` uses the default tweak.
147    pub fn encrypt_numerals(&self, x: &[u16], tweak: Option<&[u8]>) -> Result<Vec<u16>, Error> {
148        self.run(x, tweak, Operand::Plaintext)
149    }
150
151    /// Decrypt a numeral sequence (SP 800-38G Algorithm 8). Each numeral must
152    /// be less than the radix. `tweak` of `None` uses the default tweak.
153    pub fn decrypt_numerals(&self, x: &[u16], tweak: Option<&[u8]>) -> Result<Vec<u16>, Error> {
154        self.run(x, tweak, Operand::Ciphertext)
155    }
156
157    /// Encrypt a string over the configured alphabet.
158    pub fn encrypt(&self, s: &str, tweak: Option<&[u8]>) -> Result<String, Error> {
159        let alphabet = self
160            .alphabet
161            .as_ref()
162            .ok_or_else(|| validate::alphabet_required("encrypt_numerals"))?;
163        let numerals = validate::decode(s, &alphabet.index)?;
164        let out = self.encrypt_numerals(&numerals, tweak)?;
165        Ok(alphabet.encode(&out))
166    }
167
168    /// Decrypt a string over the configured alphabet.
169    pub fn decrypt(&self, s: &str, tweak: Option<&[u8]>) -> Result<String, Error> {
170        let alphabet = self
171            .alphabet
172            .as_ref()
173            .ok_or_else(|| validate::alphabet_required("decrypt_numerals"))?;
174        let numerals = validate::decode(s, &alphabet.index)?;
175        let out = self.decrypt_numerals(&numerals, tweak)?;
176        Ok(alphabet.encode(&out))
177    }
178
179    /// The shortest input accepted for this radix: the smallest `n` with
180    /// `radix**n >= 1_000_000` (6 for radix 10, 20 for radix 2).
181    pub fn min_length(&self) -> usize {
182        self.min_length
183    }
184
185    /// The longest input accepted: `2**32 - 1`, since SP 800-38G requires
186    /// `maxlen < 2**32`.
187    pub fn max_length(&self) -> u64 {
188        MAX_LEN
189    }
190
191    /// Validate in the Python package's order -- length, then tweak, then
192    /// each numeral -- and run the core.
193    fn run(&self, x: &[u16], tweak: Option<&[u8]>, operand: Operand) -> Result<Vec<u16>, Error> {
194        let tweak = tweak.unwrap_or(&self.default_tweak);
195        validate::length(x.len(), self.min_length, self.radix, operand)?;
196        validate::tweak(tweak.len(), self.min_tweak_len, self.max_tweak_len)?;
197        validate::numerals(x, self.radix, operand)?;
198        let encrypt = operand == Operand::Plaintext;
199        // Every condition the core can refuse -- key size, an empty input, a
200        // length or tweak that does not fit four bytes -- was rejected above,
201        // so a core error here would be a defect in this crate, not in the
202        // caller's input.
203        Ok(engine::ff1(&self.key, self.radix, x, tweak, encrypt)
204            .expect("inputs were validated; the FF1 core cannot fail on them"))
205    }
206}
207
208impl Alphabet {
209    fn encode(&self, numerals: &[u16]) -> String {
210        numerals
211            .iter()
212            .map(|&v| self.chars[usize::from(v)])
213            .collect()
214    }
215}