Skip to main content

base64_ng/
alphabet.rs

1//! Base64 alphabets and custom alphabet validation.
2
3use crate::{ct_mask_eq_u8, ct_mask_lt_u8};
4
5/// Alphabet validation error.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum AlphabetError {
8    /// The alphabet contains a non-visible-ASCII byte.
9    InvalidByte {
10        /// Byte index in the alphabet table.
11        index: usize,
12        /// Invalid byte value.
13        byte: u8,
14    },
15    /// The alphabet contains the padding byte `=`.
16    PaddingByte {
17        /// Byte index in the alphabet table.
18        index: usize,
19    },
20    /// The alphabet maps more than one value to the same byte.
21    DuplicateByte {
22        /// First byte index.
23        first: usize,
24        /// Second byte index.
25        second: usize,
26        /// Duplicated byte value.
27        byte: u8,
28    },
29}
30
31impl core::fmt::Display for AlphabetError {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        match self {
34            Self::InvalidByte { index, byte } => {
35                write!(
36                    f,
37                    "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
38                )
39            }
40            Self::PaddingByte { index } => {
41                write!(f, "base64 alphabet contains padding byte at index {index}")
42            }
43            Self::DuplicateByte {
44                first,
45                second,
46                byte,
47            } => write!(
48                f,
49                "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
50            ),
51        }
52    }
53}
54
55#[cfg(feature = "std")]
56impl std::error::Error for AlphabetError {}
57
58/// Defines a custom [`Alphabet`] from a 64-byte string literal.
59///
60/// The generated alphabet is validated at compile time with
61/// [`validate_alphabet`]. Invalid, duplicate, or padding bytes fail the build
62/// instead of creating a malformed runtime profile.
63///
64/// The generated implementation uses the conservative default
65/// [`Alphabet::encode`] behavior: every emitted Base64 byte performs a fixed
66/// 64-entry scan to avoid secret-indexed table lookups. Built-in alphabets use
67/// optimized arithmetic mappers.
68///
69/// The generated [`Alphabet::decode`] implementation delegates to
70/// [`decode_alphabet_byte`]. The constant-time-oriented [`ct`](crate::ct)
71/// module scans the generated `ENCODE` table directly and does not call the
72/// generated `decode` method.
73///
74/// # Examples
75///
76/// ```
77/// base64_ng::define_alphabet! {
78///     struct DotSlash = b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
79/// }
80///
81/// let engine = base64_ng::Engine::<DotSlash, false>::new();
82/// let mut encoded = [0u8; 4];
83/// let written = engine.encode_slice(&[0xff, 0xff, 0xff], &mut encoded).unwrap();
84/// assert_eq!(&encoded[..written], b"9999");
85/// ```
86///
87/// Invalid alphabets fail during compilation:
88///
89/// ```compile_fail
90/// base64_ng::define_alphabet! {
91///     struct Bad = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
92/// }
93/// ```
94#[macro_export]
95macro_rules! define_alphabet {
96    ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
97        $(#[$meta])*
98        #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
99        $vis struct $name;
100
101        impl $crate::Alphabet for $name {
102            const ENCODE: [u8; 64] = *$encode;
103
104            #[inline]
105            fn decode(byte: u8) -> Option<u8> {
106                $crate::decode_alphabet_byte(byte, &Self::ENCODE)
107            }
108        }
109
110        const _: [(); 1] = [(); match $crate::validate_alphabet(
111            &<$name as $crate::Alphabet>::ENCODE,
112        ) {
113            Ok(()) => 1,
114            Err(_) => 0,
115        }];
116    };
117}
118
119/// Validates a 64-byte Base64 alphabet table.
120///
121/// A valid alphabet must contain exactly 64 unique visible ASCII bytes and must
122/// not contain the padding byte `=`.
123///
124/// # Examples
125///
126/// ```
127/// use base64_ng::{Alphabet, Standard, validate_alphabet};
128///
129/// validate_alphabet(&Standard::ENCODE).unwrap();
130/// ```
131pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
132    let mut index = 0;
133    while index < encode.len() {
134        let byte = encode[index];
135        if !is_visible_ascii(byte) {
136            return Err(AlphabetError::InvalidByte { index, byte });
137        }
138        if byte == b'=' {
139            return Err(AlphabetError::PaddingByte { index });
140        }
141
142        let mut duplicate = index + 1;
143        while duplicate < encode.len() {
144            if encode[duplicate] == byte {
145                return Err(AlphabetError::DuplicateByte {
146                    first: index,
147                    second: duplicate,
148                    byte,
149                });
150            }
151            duplicate += 1;
152        }
153
154        index += 1;
155    }
156
157    Ok(())
158}
159
160/// Decodes one byte by scanning a caller-provided alphabet table.
161///
162/// This helper is intended for custom [`Alphabet`] implementations. Validate
163/// the table with [`validate_alphabet`] before trusting the alphabet in a
164/// protocol or public API. The scan always visits all 64 entries before
165/// returning so the match position does not create an early-return timing
166/// signal in the source-level implementation.
167///
168/// # Security
169///
170/// This is a directly callable custom-alphabet helper, not the mapping boundary
171/// used by [`Engine`](crate::Engine) or the constant-time-oriented
172/// [`ct`](crate::ct) module. It is a `const fn` so it does not use the optimizer
173/// barriers, volatile accumulator reads, or generated-code evidence hooks used
174/// by the private `ct` scanner. Do not rely on this helper for military or
175/// cryptographic constant-time guarantees under LTO or future compiler
176/// rewrites. For secret-bearing custom alphabets, use
177/// [`Engine::ct_decoder`](crate::Engine::ct_decoder) or the [`ct`](crate::ct)
178/// module. Both ordinary and secret engine paths derive their mapping directly
179/// from [`Alphabet::ENCODE`] and do not call [`Alphabet::decode`].
180///
181/// # Examples
182///
183/// ```
184/// use base64_ng::{Alphabet, decode_alphabet_byte};
185///
186/// struct DotSlash;
187///
188/// impl Alphabet for DotSlash {
189///     const ENCODE: [u8; 64] =
190///         *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
191///
192///     fn decode(byte: u8) -> Option<u8> {
193///         decode_alphabet_byte(byte, &Self::ENCODE)
194///     }
195/// }
196///
197/// assert_eq!(DotSlash::decode(b'.'), Some(0));
198/// assert_eq!(DotSlash::decode(b'9'), Some(63));
199/// ```
200#[must_use]
201pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
202    let mut index = 0;
203    let mut candidate = 0;
204    let mut decoded = 0;
205    let mut valid = 0;
206    while index < encode.len() {
207        let matches = ct_mask_eq_u8(byte, encode[index]);
208        decoded |= candidate & matches;
209        valid |= matches;
210        index += 1;
211        candidate += 1;
212    }
213
214    if valid == 0 { None } else { Some(decoded) }
215}
216
217/// A Base64 alphabet.
218///
219/// # Security
220///
221/// The default [`Alphabet::encode`] implementation is constant-time-oriented:
222/// it scans all 64 alphabet entries instead of using `ENCODE[value as usize]`.
223/// Direct callers that override `encode` with a table lookup make those direct
224/// calls timing-sensitive with respect to the selected 6-bit value. Public
225/// [`Engine`](crate::Engine) encoding does not call this overridable method:
226/// [`Alphabet::ENCODE`] is its sole output definition for const, scalar, SIMD,
227/// wrapped, and in-place surfaces.
228///
229/// Public [`Engine`](crate::Engine) decoding also treats [`Alphabet::ENCODE`] as
230/// authoritative and does not call [`Alphabet::decode`]. This prevents mutable
231/// or otherwise stateful method overrides from changing results across scalar
232/// and SIMD backends. The [`ct`](crate::ct) module independently scans the same
233/// table with its fixed-work mapper. Direct calls to an overridden `decode`
234/// method retain that implementation's behavior and timing.
235pub trait Alphabet {
236    /// Encoding table indexed by 6-bit values.
237    const ENCODE: [u8; 64];
238
239    /// Encode one 6-bit value into an alphabet byte.
240    ///
241    /// The default implementation scans the alphabet table instead of using a
242    /// secret-indexed table lookup. Built-in alphabets override this with the
243    /// branch-minimized ASCII arithmetic mapper. Custom alphabets that keep the
244    /// default method prioritize timing posture over throughput for direct
245    /// calls. This method is retained as a public low-level mapping helper for
246    /// API compatibility; [`Engine`](crate::Engine) uses [`Self::ENCODE`]
247    /// directly and is unaffected by overrides.
248    #[must_use]
249    fn encode(value: u8) -> u8 {
250        encode_alphabet_value(value, &Self::ENCODE)
251    }
252
253    /// Decode one byte into a 6-bit value.
254    ///
255    /// Implementations that want conservative custom-alphabet timing posture
256    /// should delegate to [`decode_alphabet_byte`], which scans all 64 entries
257    /// before returning. This method is retained as a public low-level mapping
258    /// helper for API compatibility; [`Engine`](crate::Engine) and the `ct`
259    /// module ignore it and derive mappings from [`Self::ENCODE`] directly.
260    fn decode(byte: u8) -> Option<u8>;
261}
262
263const fn is_visible_ascii(byte: u8) -> bool {
264    byte >= 0x21 && byte <= 0x7e
265}
266
267/// The RFC 4648 standard Base64 alphabet.
268#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
269pub struct Standard;
270
271impl Alphabet for Standard {
272    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
273
274    #[inline]
275    fn encode(value: u8) -> u8 {
276        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
277    }
278
279    #[inline]
280    fn decode(byte: u8) -> Option<u8> {
281        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
282    }
283}
284
285/// The RFC 4648 URL-safe Base64 alphabet.
286#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
287pub struct UrlSafe;
288
289impl Alphabet for UrlSafe {
290    const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
291
292    #[inline]
293    fn encode(value: u8) -> u8 {
294        encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
295    }
296
297    #[inline]
298    fn decode(byte: u8) -> Option<u8> {
299        decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
300    }
301}
302
303/// The bcrypt Base64 alphabet.
304///
305/// This alphabet is commonly used by bcrypt hash strings. It is provided as an
306/// alphabet/profile building block; `base64-ng` does not parse or verify full
307/// bcrypt password-hash records.
308///
309/// # Security
310///
311/// The strict [`Alphabet::decode`] implementation delegates to
312/// [`decode_alphabet_byte`]. That helper scans the full alphabet, but it is a
313/// `const fn` and does not use the additional optimizer barriers used by the
314/// [`ct`](crate::ct) module. Do not use strict `Engine<Bcrypt, _>` decode as a
315/// token, key, or password-hash verifier. Use [`crate::ct::CtEngine`] with this
316/// alphabet for secret-bearing comparison workflows.
317#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
318pub struct Bcrypt;
319
320impl Alphabet for Bcrypt {
321    const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
322
323    #[inline]
324    fn decode(byte: u8) -> Option<u8> {
325        decode_alphabet_byte(byte, &Self::ENCODE)
326    }
327}
328
329/// The Unix `crypt(3)` Base64 alphabet.
330///
331/// This alphabet is provided as an explicit legacy interoperability profile.
332/// `base64-ng` does not parse or verify complete password-hash records.
333///
334/// # Security
335///
336/// The strict [`Alphabet::decode`] implementation delegates to
337/// [`decode_alphabet_byte`]. That helper scans the full alphabet, but it is a
338/// `const fn` and does not use the additional optimizer barriers used by the
339/// [`ct`](crate::ct) module. Do not use strict `Engine<Crypt, _>` decode as a
340/// token, key, or password-hash verifier. Use [`crate::ct::CtEngine`] with this
341/// alphabet for secret-bearing comparison workflows.
342#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
343pub struct Crypt;
344
345impl Alphabet for Crypt {
346    const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
347
348    #[inline]
349    fn decode(byte: u8) -> Option<u8> {
350        decode_alphabet_byte(byte, &Self::ENCODE)
351    }
352}
353
354#[inline]
355pub(crate) const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
356    encode_alphabet_value(value, &A::ENCODE)
357}
358
359#[derive(Clone, Copy)]
360pub(crate) enum RuntimeAlphabetMapper {
361    StandardFamily { value_62: u8, value_63: u8 },
362    ScannedTable,
363}
364
365impl RuntimeAlphabetMapper {
366    pub(crate) const fn for_alphabet<A: Alphabet>() -> Self {
367        const STANDARD_PREFIX: [u8; 62] =
368            *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
369
370        let mut index = 0;
371        while index < STANDARD_PREFIX.len() {
372            if A::ENCODE[index] != STANDARD_PREFIX[index] {
373                return Self::ScannedTable;
374            }
375            index += 1;
376        }
377
378        let value_62 = A::ENCODE[62];
379        let value_63 = A::ENCODE[63];
380        if (value_62 == b'+' && value_63 == b'/') || (value_62 == b'-' && value_63 == b'_') {
381            Self::StandardFamily { value_62, value_63 }
382        } else {
383            Self::ScannedTable
384        }
385    }
386
387    #[inline]
388    pub(crate) fn encode<A: Alphabet>(self, value: u8) -> u8 {
389        match self {
390            Self::StandardFamily { value_62, value_63 } => {
391                encode_ascii_base64(value, value_62, value_63)
392            }
393            Self::ScannedTable => encode_alphabet_value(value, &A::ENCODE),
394        }
395    }
396
397    #[inline]
398    pub(crate) fn decode<A: Alphabet>(self, byte: u8) -> Option<u8> {
399        match self {
400            Self::StandardFamily { value_62, value_63 } => {
401                decode_ascii_base64(byte, value_62, value_63)
402            }
403            Self::ScannedTable => decode_alphabet_byte(byte, &A::ENCODE),
404        }
405    }
406}
407
408pub(crate) struct RuntimeAlphabetMapperFor<A: Alphabet>(core::marker::PhantomData<A>);
409
410impl<A: Alphabet> RuntimeAlphabetMapperFor<A> {
411    pub(crate) const VALUE: RuntimeAlphabetMapper = RuntimeAlphabetMapper::for_alphabet::<A>();
412}
413
414#[inline]
415const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
416    let mut output = 0;
417    let mut index = 0;
418    let mut candidate = 0;
419    while index < encode.len() {
420        output |= encode[index] & ct_mask_eq_u8(value, candidate);
421        index += 1;
422        candidate += 1;
423    }
424    output
425}
426
427#[inline]
428const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
429    let upper = ct_mask_lt_u8(value, 26);
430    let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
431    let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
432    let value_62 = ct_mask_eq_u8(value, 0x3e);
433    let value_63 = ct_mask_eq_u8(value, 0x3f);
434
435    (value.wrapping_add(b'A') & upper)
436        | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
437        | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
438        | (value_62_byte & value_62)
439        | (value_63_byte & value_63)
440}
441
442#[inline]
443fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
444    let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
445    let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
446    let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
447    let value_62 = ct_mask_eq_u8(byte, value_62_byte);
448    let value_63 = ct_mask_eq_u8(byte, value_63_byte);
449    let valid = upper | lower | digit | value_62 | value_63;
450
451    let decoded = (byte.wrapping_sub(b'A') & upper)
452        | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
453        | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
454        | (0x3e & value_62)
455        | (0x3f & value_63);
456
457    if valid == 0 { None } else { Some(decoded) }
458}