Skip to main content

base64_ng/v2/
secret_encoder.rs

1//! Fixed-work scalar core for secret-bearing Base64 encoding.
2
3use super::{
4    alphabet::{STANDARD_ALPHABET, URL_SAFE_ALPHABET, ValidatedAlphabet},
5    contracts::Progress,
6    specifications::{CodecSettings, EncodePadding},
7};
8
9/// Maximum encoded capacity accepted by a stack-backed secret encoder.
10pub const MAX_SECRET_STACK_ENCODED: usize = 1_368;
11
12/// Error returned by a secret encoding frame.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum SecretEncodeError {
16    /// The public cumulative input length exceeds the declared frame limit.
17    InputTooLarge {
18        /// Public input bytes presented so far.
19        input_len: usize,
20        /// Public maximum input bytes declared at construction.
21        maximum_input_len: usize,
22    },
23    /// Secret output storage cannot hold the declared maximum encoded result.
24    OutputFull {
25        /// Required encoded bytes for the declared input bound.
26        required: usize,
27        /// Available secret output bytes.
28        available: usize,
29    },
30    /// Secret input and output storage overlap.
31    OverlappingBuffers,
32    /// A byte-range end address cannot be represented by `usize`.
33    AddressRangeOverflow,
34    /// Encoded-length or source-position arithmetic overflowed.
35    LengthOverflow,
36    /// A preallocated heap encoder could not reserve its complete storage.
37    #[cfg(feature = "alloc")]
38    AllocationFailed,
39    /// The encoder previously failed and is absorbing.
40    Failed,
41    /// The encoder has already completed successfully.
42    Complete,
43}
44
45impl core::fmt::Display for SecretEncodeError {
46    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47        match self {
48            Self::InputTooLarge {
49                input_len,
50                maximum_input_len,
51            } => write!(
52                formatter,
53                "secret input length {input_len} exceeds public frame limit {maximum_input_len}"
54            ),
55            Self::OutputFull {
56                required,
57                available,
58            } => write!(
59                formatter,
60                "secret encoder requires {required} output bytes; storage has {available}"
61            ),
62            Self::OverlappingBuffers => {
63                formatter.write_str("secret encoder input and output must be disjoint")
64            }
65            Self::AddressRangeOverflow => {
66                formatter.write_str("secret encoder byte-range address overflows usize")
67            }
68            Self::LengthOverflow => formatter.write_str("secret encode length overflows usize"),
69            #[cfg(feature = "alloc")]
70            Self::AllocationFailed => {
71                formatter.write_str("failed to reserve bounded secret encoder storage")
72            }
73            Self::Failed => formatter.write_str("secret encoder is in an absorbing failed state"),
74            Self::Complete => formatter.write_str("secret encoder is already complete"),
75        }
76    }
77}
78
79#[cfg(feature = "std")]
80impl std::error::Error for SecretEncodeError {}
81
82#[derive(Clone, Copy, Eq, PartialEq)]
83enum Phase {
84    Active,
85    Failed,
86    Complete,
87}
88
89#[derive(Clone, Copy)]
90enum SecretMapper {
91    Standard,
92    UrlSafe,
93    Scanned(ValidatedAlphabet),
94}
95
96impl SecretMapper {
97    fn new(alphabet: ValidatedAlphabet) -> Self {
98        if alphabet == STANDARD_ALPHABET {
99            Self::Standard
100        } else if alphabet == URL_SAFE_ALPHABET {
101            Self::UrlSafe
102        } else {
103            Self::Scanned(alphabet)
104        }
105    }
106
107    #[inline(never)]
108    fn map(self, value: u8) -> u8 {
109        match self {
110            Self::Standard => secret_encode_ascii(value, b'+', b'/'),
111            Self::UrlSafe => secret_encode_ascii(value, b'-', b'_'),
112            Self::Scanned(alphabet) => secret_encode_scan(value, &alphabet),
113        }
114    }
115}
116
117/// Public metadata and fixed-work core shared by secret encoding frames.
118///
119/// Construction remains frame-owned so this state cannot be detached from
120/// wiping secret output storage.
121pub struct SecretEncoderState {
122    mapper: SecretMapper,
123    padding: EncodePadding,
124    maximum_input_len: usize,
125    maximum_encoded_len: usize,
126    input_len: usize,
127    output_len: usize,
128    tail: [u8; 3],
129    tail_len: usize,
130    phase: Phase,
131    #[cfg(test)]
132    mapping_work: usize,
133}
134
135impl SecretEncoderState {
136    pub(super) fn new(
137        settings: CodecSettings,
138        maximum_input_len: usize,
139        output_capacity: usize,
140    ) -> Result<Self, SecretEncodeError> {
141        let padded = settings.encode_padding() == EncodePadding::Padded;
142        let maximum_encoded_len = crate::checked_encoded_len(maximum_input_len, padded)
143            .ok_or(SecretEncodeError::LengthOverflow)?;
144        if maximum_encoded_len > output_capacity {
145            return Err(SecretEncodeError::OutputFull {
146                required: maximum_encoded_len,
147                available: output_capacity,
148            });
149        }
150        Ok(Self {
151            mapper: SecretMapper::new(*settings.alphabet()),
152            padding: settings.encode_padding(),
153            maximum_input_len,
154            maximum_encoded_len,
155            input_len: 0,
156            output_len: 0,
157            tail: [0; 3],
158            tail_len: 0,
159            phase: Phase::Active,
160            #[cfg(test)]
161            mapping_work: 0,
162        })
163    }
164
165    /// Returns the public maximum input bytes.
166    #[must_use]
167    pub const fn maximum_input_len(&self) -> usize {
168        self.maximum_input_len
169    }
170
171    /// Returns the public maximum encoded bytes.
172    #[must_use]
173    pub const fn maximum_encoded_len(&self) -> usize {
174        self.maximum_encoded_len
175    }
176
177    /// Returns the public input bytes accepted so far.
178    #[must_use]
179    pub const fn input_len(&self) -> usize {
180        self.input_len
181    }
182
183    /// Returns the public encoded bytes initialized so far.
184    #[must_use]
185    pub const fn output_len(&self) -> usize {
186        self.output_len
187    }
188
189    /// Returns whether this encoder has entered an absorbing failure.
190    #[must_use]
191    pub const fn is_failed(&self) -> bool {
192        matches!(self.phase, Phase::Failed)
193    }
194
195    pub(super) fn update(
196        &mut self,
197        input: &[u8],
198        output: &mut [u8],
199    ) -> Result<Progress, SecretEncodeError> {
200        self.require_active()?;
201        let attempted = self
202            .input_len
203            .checked_add(input.len())
204            .ok_or_else(|| self.fail(SecretEncodeError::LengthOverflow))?;
205        if attempted > self.maximum_input_len {
206            return Err(self.fail(SecretEncodeError::InputTooLarge {
207                input_len: attempted,
208                maximum_input_len: self.maximum_input_len,
209            }));
210        }
211
212        let before = self.output_len;
213        for &byte in input {
214            self.tail[self.tail_len] = byte;
215            self.tail_len += 1;
216            if self.tail_len == 3 {
217                self.encode_complete_quantum(output)?;
218            }
219        }
220        self.input_len = attempted;
221        Ok(Progress::new(input.len(), self.output_len - before))
222    }
223
224    pub(super) fn finish(&mut self, output: &mut [u8]) -> Result<usize, SecretEncodeError> {
225        self.require_active()?;
226        if self.tail_len != 0 {
227            self.encode_final_quantum(output)?;
228        }
229        crate::wipe_tail(output, self.output_len);
230        self.phase = Phase::Complete;
231        Ok(self.output_len)
232    }
233
234    fn encode_complete_quantum(&mut self, output: &mut [u8]) -> Result<(), SecretEncodeError> {
235        let end = self
236            .output_len
237            .checked_add(4)
238            .ok_or_else(|| self.fail(SecretEncodeError::LengthOverflow))?;
239        if end > output.len() {
240            return Err(self.fail(SecretEncodeError::OutputFull {
241                required: end,
242                available: output.len(),
243            }));
244        }
245        let values = six_bit_values(self.tail);
246        for (slot, value) in output[self.output_len..end].iter_mut().zip(values) {
247            *slot = self.map(value);
248        }
249        self.output_len = end;
250        self.clear_tail();
251        Ok(())
252    }
253
254    fn encode_final_quantum(&mut self, output: &mut [u8]) -> Result<(), SecretEncodeError> {
255        let first = self.tail[0];
256        let second = self.tail[1];
257        let values = [
258            first >> 2,
259            ((first & 0x03) << 4) | (second >> 4),
260            (second & 0x0f) << 2,
261        ];
262        let produced = final_quantum_output_len(self.tail_len, self.padding);
263        let end = self
264            .output_len
265            .checked_add(produced)
266            .ok_or_else(|| self.fail(SecretEncodeError::LengthOverflow))?;
267        if end > output.len() {
268            return Err(self.fail(SecretEncodeError::OutputFull {
269                required: end,
270                available: output.len(),
271            }));
272        }
273
274        output[self.output_len] = self.map(values[0]);
275        output[self.output_len + 1] = self.map(values[1]);
276        if self.tail_len == 2 {
277            output[self.output_len + 2] = self.map(values[2]);
278        } else if self.padding == EncodePadding::Padded {
279            output[self.output_len + 2] = b'=';
280        }
281        if self.padding == EncodePadding::Padded {
282            output[self.output_len + 3] = b'=';
283        }
284        self.output_len = end;
285        self.clear_tail();
286        Ok(())
287    }
288
289    fn map(&mut self, value: u8) -> u8 {
290        #[cfg(test)]
291        {
292            self.mapping_work += match self.mapper {
293                SecretMapper::Standard | SecretMapper::UrlSafe => 1,
294                SecretMapper::Scanned(_) => 64,
295            };
296        }
297        self.mapper.map(value)
298    }
299
300    fn clear_tail(&mut self) {
301        crate::wipe_bytes(&mut self.tail);
302        self.tail_len = 0;
303    }
304
305    fn require_active(&self) -> Result<(), SecretEncodeError> {
306        match self.phase {
307            Phase::Active => Ok(()),
308            Phase::Failed => Err(SecretEncodeError::Failed),
309            Phase::Complete => Err(SecretEncodeError::Complete),
310        }
311    }
312
313    fn fail(&mut self, error: SecretEncodeError) -> SecretEncodeError {
314        self.phase = Phase::Failed;
315        self.clear_tail();
316        error
317    }
318
319    pub(super) fn latch_external_failure(&mut self) {
320        self.phase = Phase::Failed;
321        self.clear_tail();
322    }
323}
324
325impl Drop for SecretEncoderState {
326    fn drop(&mut self) {
327        self.clear_tail();
328        self.input_len = 0;
329        self.output_len = 0;
330    }
331}
332
333impl core::fmt::Debug for SecretEncoderState {
334    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
335        formatter
336            .debug_struct("SecretEncoderState")
337            .field("tail", &"<redacted>")
338            .field("input_len", &self.input_len)
339            .field("output_len", &self.output_len)
340            .field("maximum_input_len", &self.maximum_input_len)
341            .field("maximum_encoded_len", &self.maximum_encoded_len)
342            .field("failed", &self.is_failed())
343            .finish_non_exhaustive()
344    }
345}
346
347fn six_bit_values(input: [u8; 3]) -> [u8; 4] {
348    [
349        input[0] >> 2,
350        ((input[0] & 0x03) << 4) | (input[1] >> 4),
351        ((input[1] & 0x0f) << 2) | (input[2] >> 6),
352        input[2] & 0x3f,
353    ]
354}
355
356const fn final_quantum_output_len(tail_len: usize, padding: EncodePadding) -> usize {
357    if matches!(padding, EncodePadding::Padded) {
358        4
359    } else {
360        tail_len + 1
361    }
362}
363
364#[inline(never)]
365fn secret_encode_ascii(value: u8, value_62: u8, value_63: u8) -> u8 {
366    let upper = crate::ct_mask_lt_u8(value, 26);
367    let lower = crate::ct_mask_lt_u8(value.wrapping_sub(26), 26);
368    let digit = crate::ct_mask_lt_u8(value.wrapping_sub(52), 10);
369    let is_62 = crate::ct_mask_eq_u8(value, 62);
370    let is_63 = crate::ct_mask_eq_u8(value, 63);
371    core::hint::black_box(
372        (value.wrapping_add(b'A') & upper)
373            | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
374            | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
375            | (value_62 & is_62)
376            | (value_63 & is_63),
377    )
378}
379
380#[inline(never)]
381fn secret_encode_scan(value: u8, alphabet: &ValidatedAlphabet) -> u8 {
382    let mut output = 0u8;
383    let mut candidate = 0u8;
384    while candidate < 64 {
385        let selected = core::hint::black_box(crate::ct_mask_eq_u8(
386            core::hint::black_box(value),
387            core::hint::black_box(candidate),
388        ));
389        output = crate::ct_accumulate_u8(
390            output,
391            core::hint::black_box(alphabet.as_array()[usize::from(candidate)] & selected),
392        );
393        candidate += 1;
394    }
395    output
396}
397
398pub(super) fn require_disjoint(left: &[u8], right: &[u8]) -> Result<(), SecretEncodeError> {
399    require_disjoint_ranges(
400        left.as_ptr() as usize,
401        left.len(),
402        right.as_ptr() as usize,
403        right.len(),
404    )
405}
406
407fn require_disjoint_ranges(
408    left_start: usize,
409    left_len: usize,
410    right_start: usize,
411    right_len: usize,
412) -> Result<(), SecretEncodeError> {
413    let left_end = left_start
414        .checked_add(left_len)
415        .ok_or(SecretEncodeError::AddressRangeOverflow)?;
416    let right_end = right_start
417        .checked_add(right_len)
418        .ok_or(SecretEncodeError::AddressRangeOverflow)?;
419    if left_len != 0 && right_len != 0 && left_start < right_end && right_start < left_end {
420        Err(SecretEncodeError::OverlappingBuffers)
421    } else {
422        Ok(())
423    }
424}
425
426#[cfg(kani)]
427pub(crate) const fn final_quantum_output_len_for_proof(
428    tail_len: usize,
429    padding: EncodePadding,
430) -> usize {
431    final_quantum_output_len(tail_len, padding)
432}
433
434#[cfg(kani)]
435pub(crate) fn require_disjoint_ranges_for_proof(
436    left_start: usize,
437    left_len: usize,
438    right_start: usize,
439    right_len: usize,
440) -> Result<(), SecretEncodeError> {
441    require_disjoint_ranges(left_start, left_len, right_start, right_len)
442}
443
444#[cfg(test)]
445pub(super) fn require_disjoint_ranges_for_test(
446    left_start: usize,
447    left_len: usize,
448    right_start: usize,
449    right_len: usize,
450) -> Result<(), SecretEncodeError> {
451    require_disjoint_ranges(left_start, left_len, right_start, right_len)
452}
453
454#[cfg(test)]
455impl SecretEncoderState {
456    pub(super) const fn mapping_work_for_test(&self) -> usize {
457        self.mapping_work
458    }
459}
460
461#[cfg(test)]
462pub(super) fn map_value_for_test(settings: CodecSettings, value: u8) -> u8 {
463    SecretMapper::new(*settings.alphabet()).map(value)
464}