wolf_crypto/
ct.rs

1//! Constant-Time Programming Utilities
2
3#[cfg(llvm_ir_check)]
4extern crate core;
5
6use core::hint::black_box;
7#[cfg(not(llvm_ir_check))]
8use crate::opaque_res::Res;
9
10#[cfg(llvm_ir_check)]
11pub struct Res(pub bool);
12#[cfg(not(llvm_ir_check))]
13use crate::buf::InvalidSize;
14#[cfg(llvm_ir_check)]
15pub struct InvalidSize;
16
17macro_rules! smear {
18    ($b:ident) => {{
19        $b |= $b >> 1;
20        $b |= $b >> 2;
21        $b |= $b >> 4;
22        $b |= $b >> 8;
23        $b |= $b >> 16;
24    }};
25}
26
27/// Performs a constant-time greater-than comparison.
28///
29/// # Arguments
30///
31/// * `left` - The left-hand side operand.
32/// * `right` - The right-hand side operand.
33///
34/// # Returns
35///
36/// Returns `1` if `left > right`, otherwise `0`.
37///
38/// # Constant Time Verification
39///
40/// To verify that this function is truly constant-time we leveraged `haybale-pitchfork` by
41/// UCSD `PLSysSec`.
42///
43/// ## Debug Build
44///
45/// This project only supports llvm-14, so the original llvm-18 bitcode required slight manual
46/// modifications. These modifications were only in removing LLVM's opaque pointer, replacing it
47/// with the associated LLVM 15 and below pointer type (i32*).
48///
49/// You can find the original LLVM and the translation which we verified in the llvm directory in
50/// this crate's manifest directory.
51///
52/// ### Results
53///
54/// ```txt
55/// Results for gt:
56///
57/// verified paths: 1
58/// constant-time violations found: 0
59///
60/// Coverage stats:
61///
62/// Block coverage of toplevel function (gt): 100.0%
63///
64///
65/// gt is constant-time
66/// ```
67///
68/// ## Optimized Build
69///
70/// In the optimized build, the LLVM IR contained no usage of opaque pointers, though the return
71/// type was unsupported. So, the returning of the result was removed. Though in a separate check
72/// this was confirmed to be constant time on its own.
73///
74/// ### Results
75///
76/// ```txt
77/// Results for gt:
78///
79/// verified paths: 1
80/// constant-time violations found: 0
81///
82/// Coverage stats:
83///
84///   Block coverage of toplevel function (gt): 100.0%
85///
86///
87/// gt is constant-time
88/// ```
89///
90/// # Functional Correctness
91///
92/// This was verified to be correct using `kani` with the assurance of completeness. You can
93/// find the associated proof at the bottom of this file.
94#[cfg_attr(llvm_ir_check, no_mangle)]
95pub const fn gt(left: u32, right: u32) -> u32 {
96    let gtb = left & !right;
97    let mut ltb = !left & right;
98
99    smear!(ltb);
100
101    let mut bit = gtb & !ltb;
102    // smear the highest set bit
103    smear!(bit);
104
105    bit & 1
106}
107
108#[inline(always)]
109const fn create_mask(overflow: u32) -> u32 {
110    !overflow.wrapping_neg()
111}
112
113#[inline(always)]
114const fn mask_add(left: u32, right: u32, mask: u32) -> u32 {
115    left.wrapping_add(right & mask)
116}
117
118/// Performs constant-time addition without wrapping on overflow.
119///
120/// # Arguments
121///
122/// * `a` - The first operand.
123/// * `b` - The second operand.
124///
125/// # Returns
126///
127/// A tuple containing the sum and a `Res` indicating if there was no overflow.
128///
129/// # Constant Time Verification
130///
131/// See the above [`gt`] functions Constant Time Verification section for more details regarding
132/// the setup, as the process is equivalent.
133///
134/// ## Results
135///
136/// ```txt
137/// Results for add_no_wrap:
138///
139/// verified paths: 1
140/// constant-time violations found: 0
141///
142/// Coverage stats:
143///
144///   Block coverage of toplevel function (add_no_wrap): 100.0%
145///
146///
147/// add_no_wrap is constant-time
148/// ```
149#[cfg_attr(not(llvm_ir_check), inline)]
150#[cfg_attr(llvm_ir_check, no_mangle)]
151#[cfg_attr(not(feature = "allow-non-fips"), allow(dead_code))]
152pub fn add_no_wrap(a: u32, b: u32) -> (u32, Res) {
153    let overflow = gt(b, u32::MAX.wrapping_sub(a));
154
155    // LLVM and rustc is ridiculous
156    //
157    // So, this:
158    //   let sum = a.wrapping_add(b & (!overflow.wrapping_neg()));
159    //
160    // Got "optimized" (if you want to call pipeline bubbles optimization) into LLVM's select.
161    // LLVM's select WITHOUT optimizations (what??) was performed with the bitmask as I wrote.
162    //
163    // Now, WITH optimizations LLVM's select got "optimized" into the following instructions:
164    //   - testl   $65537, %ecx
165    //   - cmovel  %esi, %eax
166    //   - sete    %dl
167    //
168    // Great optimizations! Violated all constant time properties, and there isn't a chance in
169    // hell that this is faster.
170    //
171    // black_box fixed this, there is no longer any select instruction in the IR, but this is still
172    // quite annoying.
173    let sum = mask_add(a, b, black_box(create_mask(overflow)));
174    (sum, Res(overflow as u8 == 0))
175}
176
177#[inline(always)]
178fn volatile(byte: u8) -> u8 {
179    unsafe { core::ptr::read_volatile(&byte) }
180}
181
182#[inline(always)]
183fn eq_hsb(xor: u8) -> u8 {
184    volatile(xor | volatile(xor.wrapping_neg())) >> 7
185}
186
187#[cfg_attr(not(llvm_ir_check), inline(always))]
188#[cfg_attr(llvm_ir_check, no_mangle)]
189pub fn byte_eq(a: u8, b: u8) -> u8 {
190    // LLVM IR without optimizations:
191    //   ; Function Attrs: nonlazybind uwtable
192    //   define i8 @byte_eq(i8 %a, i8 %b) unnamed_addr #1 {
193    //   start:
194    //     %xor = xor i8 %a, %b
195    //     %_0.i = sub i8 0, %xor
196    //     %_5 = or i8 %xor, %_0.i
197    //     %set_hsb = lshr i8 %_5, 7
198    //     %_0 = xor i8 %set_hsb, 1
199    //     ret i8 %_0
200    //   }
201    //
202    // LLVM IR with optimizations:
203    //   ; Function Attrs: mustprogress nofree norecurse nosync nounwind nonlazybind willreturn memory(none) uwtable
204    //   define noundef i8 @byte_eq(i8 noundef %a, i8 noundef %b) unnamed_addr #0 {
205    //   start:
206    //     %0 = icmp eq i8 %a, %b
207    //     %_0 = zext i1 %0 to i8
208    //     ret i8 %_0
209    //   }
210    //
211    // OK so with optimizations it seems our constant time properties are being violated, that's
212    // obviously unacceptable.
213    //
214    // Issue was in the original inlined:
215    //
216    //   (xor | xor.wrapping_neg()) >> 7
217    //
218    // Using a volatile read on the result of (xor | xor.wrapping_neg()) prevented our constant
219    // time properties from being violated.
220    //
221    // Output assembly is also constant time:
222    //
223    //    xorl    %esi, %edi
224    //    movl    %edi, %eax
225    //    negb    %al
226    //    orb %dil, %al
227    //    movb    %al, -1(%rsp)
228    //    movzbl  -1(%rsp), %eax
229    //    notb    %al
230    //    shrb    $7, %al
231    //    retq
232    //
233    // Most concerning instruction to me is MOVZLB, but this is in general considered constant
234    // time.
235    //
236    // Output assembly for debug builds is constant time as well:
237    //
238    //    movb    %sil, %al
239    //    movb    %dil, %cl
240    //    xorb    %cl, %al
241    //    xorl    %ecx, %ecx
242    //    subb    %al, %cl
243    //    orb %cl, %al
244    //    movb    %al, 7(%rsp)
245    //    leaq    7(%rsp), %rdi
246    //    callq   *_ZN4core3ptr13read_volatile17h7014fc51810b013aE@GOTPCREL(%rip)
247    //    shrb    $7, %al
248    //    xorb    $1, %al
249    //    popq    %rcx
250    //
251    // OK, we are ready for verification of these constant time properties.
252
253    eq_hsb(b ^ a) ^ volatile(1)
254}
255
256macro_rules! unroll_ct_cmp {
257    (g2 $start:expr, $result:ident, $left:ident, $right:ident) => {{
258        // I am using unchecked operations so that I can focus on what matters in the assembly for
259        // debug builds without all of the noise of bounds checking. My usage is sound, and the
260        // caller must wrap this macro in unsafe.
261        $result &= byte_eq(*$left.get_unchecked($start), *$right.get_unchecked($start));
262        $result &= byte_eq(
263            *$left.get_unchecked($start.wrapping_add(1)),
264            *$right.get_unchecked($start.wrapping_add(1))
265        );
266    }};
267    (g4 $start:expr, $result:ident, $left:ident, $right:ident) => {
268        unroll_ct_cmp!(g2 $start, $result, $left, $right);
269        unroll_ct_cmp!(g2 $start.wrapping_add(2) as usize, $result, $left, $right);
270    };
271    (g8 $start:expr, $result:ident, $left:ident, $right:ident) => {{
272        unroll_ct_cmp!(g4 $start, $result, $left, $right);
273        unroll_ct_cmp!(g4 $start + 4, $result, $left, $right);
274    }};
275    (g16 $start:expr, $result:ident, $left:ident, $right:ident) => {{
276        unroll_ct_cmp!(g8 $start, $result, $left, $right);
277        unroll_ct_cmp!(g8 $start + 8, $result, $left, $right);
278    }}
279}
280
281#[cfg_attr(llvm_ir_check, no_mangle)]
282pub unsafe fn cmp_bytes_4_unchecked(mut res: u8, a: &[u8], b: &[u8]) -> u8 {
283    unroll_ct_cmp!(g4 0usize, res, a, b);
284    res
285}
286
287/// Compare two slices in constant-time.
288///
289/// # Note
290///
291/// If the length of slice `a` and slice `b` are not equivalent, this will exit early. In short,
292/// there is variable timing on length comparisons.
293///
294/// # Warning
295///
296/// Constant-time programming is nuanced, this implementation provides a *best-effort*
297/// constant-time equivalence check. While tools for verifying constant time properties over LLVM
298/// bitcode, a great deal of testing, paired with manual review of the output assembly,
299/// build some degree of confidence, there is still no guarantee of constant-time properties across
300/// all existing hardware.
301///
302/// # Returns
303///
304/// * `0`: `a != b`
305/// * `1`: `a == b`
306#[cfg_attr(llvm_ir_check, no_mangle)]
307#[must_use]
308pub fn cmp_slice(a: &[u8], b: &[u8]) -> u8 {
309    if a.len() != b.len() { return 0 }
310
311    let mut rem = a.len();
312    let mut res = volatile(1u8);
313
314    while rem >= 4 {
315        // loop invariant (added post ct analysis).
316        debug_assert!(rem <= a.len());
317
318        let next = rem.wrapping_sub(4);
319        // Again, bounds checking branches in debug builds are quite noisy, they get in the way
320        // of reviewing the assembly for genuine constant-time violations. My usage is sound,
321        // see the tests below if you are curious.
322        res &= unsafe {
323            cmp_bytes_4_unchecked(
324                res,
325                a.get_unchecked(next..rem),
326                b.get_unchecked(next..rem)
327            )
328        };
329
330        rem = next;
331    }
332
333    match rem {
334        3 => unsafe {
335            res &= byte_eq(*a.get_unchecked(0), *b.get_unchecked(0));
336            unroll_ct_cmp!(g2 rem.wrapping_sub(2), res, a, b);
337        }
338        2 => unsafe { unroll_ct_cmp!(g2 rem.wrapping_sub(2), res, a, b) },
339        1 => unsafe { res &= byte_eq(*a.get_unchecked(0), *b.get_unchecked(0)) },
340        _ => {}
341    }
342
343    res
344}
345
346/// Compare two slices in constant-time.
347///
348/// # Arguments
349///
350/// The two arguments being compared in constant-time, both of these arguments must implement
351/// `AsRef<[u8]>` (such as `&str`, `&[u8]` itself, etc.)
352///
353/// # Note
354///
355/// If the length of slice `a` and slice `b` are not equivalent, this will exit early. In short,
356/// there is variable timing on length comparisons.
357///
358/// # Warning
359///
360/// Constant-time programming is nuanced, this implementation provides a *best-effort*
361/// constant-time equivalence check. While tools for verifying constant time properties over LLVM
362/// bitcode, a great deal of testing, paired with manual review of the output assembly,
363/// build some degree of confidence, there is still no guarantee of constant-time properties across
364/// all existing hardware.
365///
366/// # Returns
367///
368/// `true` if `a == b`, `false` otherwise.
369#[must_use]
370pub fn ct_eq<A: AsRef<[u8]>, B: AsRef<[u8]>>(a: A, b: B) -> bool {
371    cmp_slice(a.as_ref(), b.as_ref()) != 0
372}
373
374#[must_use]
375#[inline]
376pub const fn hex_encode_len(len: usize) -> usize {
377    len << 1
378}
379
380#[inline]
381fn encode_byte(byte: u8, output: &mut [u8]) {
382    let lower = (byte & 0xf) as u32;
383    let upper = (byte >> 4) as u32;
384
385    let h =
386        87u32.wrapping_add(lower)
387            .wrapping_add(lower.wrapping_sub(10u32).wrapping_shr(8) & !38u32)
388            .wrapping_shl(8)
389            |
390            87u32.wrapping_add(upper)
391                .wrapping_add(upper.wrapping_sub(10u32).wrapping_shr(8) & !38u32);
392
393    // truncate
394    output[0] = h as u8;
395    // get highest byte
396    output[1] = h.wrapping_shr(8) as u8;
397}
398
399/*
400    Hex Encode to the str Type Considerations.
401
402    While we are certain we are always producing valid utf8, using str::from_utf8 is still risky
403    due to its data dependent branches.
404
405    So, it is best to avoid this in favor of the unsafe alternative, though this comes with risks,
406    and these risks must be addressed via verification. While we already have a property test which
407    checks that the hex_encode function always produces utf8 with 1 million inputs, this is simply
408    not enough.
409
410    The challenge with using a CBMC approach with some degree of completeness is unwinding. If we
411    were to verify for all slices that hex_encode produces utf8 this would be incredibly
412    resource intensive, and a consumer grade PC simply cannot hold up here.
413
414    But this is thinking about the problem incorrectly, as what really matters is does the encoding
415    of a single byte always produce valid utf8. If that is guaranteed, then the correctness of
416    hex_encode can be guaranteed via structural induction over the input slice.
417
418    The inductive argument would be as follows:
419    1. Base case: Prove that encoding a single byte always produces valid UTF-8.
420    2. Inductive step: Assume encoding n bytes produces valid UTF-8, then prove that
421       encoding n+1 bytes also produces valid UTF-8.
422
423    This approach allows us to reason about the correctness of hex_encode for inputs
424    of arbitrary length without needing to verify all possible inputs exhaustively.
425*/
426
427/// Constant-Time Hex Encoding
428///
429/// # Arguments
430///
431/// * `input`  - A slice of bytes to be hex-encoded.
432/// * `output` - The buffer to write the hex string into.
433///
434/// # Returns
435///
436/// The number of bytes written to the `output` buffer (which will always be twice the length of
437/// the input).
438///
439/// # Errors
440///
441/// This function returns `Err(InvalidSize)` if the `output` buffer has a length less than
442/// `input.len() * 2`.
443///
444/// # Security
445///
446/// This function is designed to operate in constant-time, avoiding data-dependent branches that
447/// could lead to timing side-channel attacks. The encoding process uses arithmetic and bitwise
448/// operations to guarantee uniform execution time regardless of the input values.
449///
450/// # UTF-8 Considerations
451///
452/// Rust's `str::from_utf8` is discouraged in contexts that require constant-time execution due
453/// to the presence of data-dependent branches plus lookup tables in its implementation.
454///
455/// **It is safe to use `str::from_utf8_unchecked` on the output of this function**, however,
456/// **it is only safe over the encoded region of the output buffer** (up until the returned
457/// length). If you need a `str` representation, consider the less error-prone [`encode_str`]
458/// function.
459///
460/// If you are curious / skeptical of the safety in this recommendation, please read the
461/// verification section below.
462///
463/// # Example
464///
465/// ```
466/// use wolf_crypto::{hex, ct_eq};
467///
468/// let mut output = [0u8; 22]; // 11 * 2
469/// let len = hex::encode_into(b"hello world", &mut output).unwrap();
470///
471/// assert_eq!(len, 22);
472/// assert!(
473///     // since we are using a constant-time encoding, we probably also want
474///     // to use a constant-time comparison.
475///     ct_eq(
476///         // SAFETY: The encoded output is formally guaranteed to be valid
477///         // UTF-8. This is just for example, ct_eq can of course compare slices.
478///         unsafe { core::str::from_utf8_unchecked(&output[..len]) },
479///         "68656c6c6f20776f726c64"
480///     )
481/// );
482///
483/// // or use the `encode_str` function, which is simply a less error-prone variant of this
484/// // if `str` representation is a requirement.
485///
486/// let mut output = [0u8; 22];
487/// let str_repr = hex::encode_str(b"hello world", &mut output).unwrap();
488///
489/// // ...
490/// ```
491///
492/// # Verification
493///
494/// The correctness and safety of the `encode_into` function have been rigorously verified using a
495/// combination of formal methods and property-based testing. This multi-faceted approach ensures
496/// a high degree of confidence in the function's behavior and its adherence to the specified
497/// properties.
498///
499/// ## Formal Verification
500///
501/// We employed formal verification techniques using the Kani Rust Verifier to prove key properties
502/// of the `encode_into` function and its components.
503///
504/// ### Inductive Proof Strategy
505///
506/// The verification process follows an inductive proof strategy:
507///
508/// 1. Base Case: We prove that encoding a single byte always produces valid UTF-8.
509/// 2. Inductive Step: We prove that if encoding n bytes produces valid UTF-8, then encoding
510///    n+1 bytes also produces valid UTF-8.
511///
512/// This approach allows us to reason about the correctness of `encode_into` for inputs of arbitrary
513/// length without needing to verify all possible inputs exhaustively.
514///
515/// ### Key Proofs
516///
517/// 1. Single Byte Encoding:
518///    The `encode_byte_is_always_utf8` proof verifies that for any input byte, the output of
519///    `encode_byte` is always valid hexadecimal (and thus valid UTF-8).
520///
521/// 2. Inductive Step:
522///    - The `verify_encode_n_plus_1_bytes_symbolic` proof demonstrates that if encoding n bytes
523///      produces valid UTF-8, encoding an additional byte preserves this property.
524///    - The `verify_hex_encode_inductive_step_symbolic` proof applies this principle to the
525///      `encode_into` function itself, verifying that the UTF-8 validity is maintained for
526///      arbitrary input lengths.
527///
528/// [`encode_str`]: crate::hex::encode_str
529#[cfg_attr(llvm_ir_check, no_mangle)]
530pub fn hex_encode(input: &[u8], output: &mut [u8]) -> Result<usize, InvalidSize> {
531    let hex_len = hex_encode_len(input.len());
532
533    #[cfg(any(check, kani, test))] {
534        ensure!((hex_len == 0) <==> (input.len() == 0));
535        ensure!((hex_len != 0) <==> (input.len() != 0));
536    }
537
538    if output.len() < hex_len { return Err(InvalidSize) }
539
540    #[cfg(any(check, kani, test))]
541    let mut post_len = 0usize;
542
543    for (pos, byte) in input.iter().enumerate() {
544        let o_pos = pos.wrapping_shl(1);
545
546        #[cfg(any(check, kani, test))] {
547            ensure!((pos != 0) ==> (is_valid_hex_2(output[o_pos - 2], output[o_pos - 1])));
548            post_len = o_pos + 1;
549        }
550
551        encode_byte(*byte, &mut output[o_pos..o_pos + 2]);
552    }
553
554    #[cfg(any(check, kani, test))] {
555        ensure!((hex_len != 0) ==> (is_valid_hex_2(output[post_len - 1], output[post_len])));
556        ensure!((hex_len != 0) ==> (post_len + 1 == hex_len));
557    }
558
559    Ok(hex_len)
560}
561
562/// Constant-Time Hex Encoding to a `&str`
563///
564/// # Arguments
565///
566/// * `input`  - A slice of bytes to be hex-encoded.
567/// * `output` - A mutable byte buffer where the encoded string will be written.
568///
569/// # Returns
570///
571/// The encoded string slice (`&str`).
572///
573/// # Errors
574///
575/// If the `output` buffer is less than the encoded length (`input.len() * 2`), this returns
576/// `InvalidSize`.
577///
578/// # Notes
579///
580/// This function is a convenience wrapper around [`encode_into`], which handles the
581/// encoding process, this simply returns the `str` representation in a safe manner.
582///
583/// For full details on the underlying encoding and security/safety guarantees, see [`encode_into`].
584///
585/// # Example
586///
587/// ```
588/// use wolf_crypto::{hex, ct_eq};
589///
590/// let mut output = [0u8; 22]; // 11 * 2
591/// let str_repr = hex::encode_str(b"hello world", &mut output).unwrap();
592///
593/// assert!(ct_eq(str_repr, "68656c6c6f20776f726c64"));
594/// ```
595///
596/// [`encode_into`]: crate::hex::encode_into
597#[inline]
598pub fn hex_encode_str<'o>(input: &[u8], output: &'o mut [u8]) -> Result<&'o str, InvalidSize> {
599    hex_encode(input, output)
600        // SAFETY: See the verification section of `hex_encode` and the proofs at the bottom
601        // of this module.
602        .map(move |len| unsafe { core::str::from_utf8_unchecked(&output[..len]) })
603}
604
605#[cfg(not(llvm_ir_check))]
606alloc! {
607    /// Constant-Time Hex Encoding
608    ///
609    /// # Arguments
610    ///
611    /// * `input`  - A slice of bytes to be hex-encoded.
612    ///
613    /// # Returns
614    ///
615    /// The hex-encoded `String`
616    ///
617    /// # Example
618    ///
619    /// ```
620    /// use wolf_crypto::{hex, ct_eq};
621    ///
622    /// let encoded = hex::encode(b"hello world");
623    /// let decoded = hex::decode(encoded.as_bytes()).unwrap();
624    ///
625    /// assert!(ct_eq(b"hello world", decoded));
626    /// ```
627    #[allow(clippy::missing_panics_doc)] // this is infallible.
628    pub fn hex_encode_alloc(input: &[u8]) -> alloc::string::String {
629        let mut output = vec![0u8; hex_encode_len(input.len())];
630        hex_encode(input, output.as_mut_slice()).unwrap(/* Infallible */);
631        // We may ignore the returned output length, as we provided a vector with the exact size
632        // of the output
633        unsafe {
634            alloc::string::String::from_utf8_unchecked(output)
635        }
636    }
637}
638
639#[inline]
640const fn decode_nibble(first: u8) -> u16 {
641    let byte = first as i16;
642    let mut ret: i16 = -1;
643
644    ret = ret.wrapping_add(
645        (0x2fi16.wrapping_sub(byte) & byte.wrapping_sub(0x3a)).wrapping_shr(8)
646            & byte.wrapping_sub(47)
647    );
648
649    ret = ret.wrapping_add(
650        (0x60i16.wrapping_sub(byte) & byte.wrapping_sub(0x67)).wrapping_shr(8)
651            & byte.wrapping_sub(86)
652    );
653
654    ret as u16
655}
656
657#[must_use]
658const fn decode_predicate(inp_len: usize, out_len: usize) -> (bool, usize) {
659    let dec_len = inp_len >> 1;
660    (inp_len & 1 == 0 && out_len >= dec_len, dec_len)
661}
662
663/// Possible errors while decoding a hex string.
664pub enum HexError {
665    /// An invalid character was encountered or the length of the hex-encoded data was invalid.
666    Encoding,
667    /// The output size was not large enough.
668    Size
669}
670
671impl From<InvalidSize> for HexError {
672    fn from(_value: InvalidSize) -> Self {
673        Self::Size
674    }
675}
676
677#[cfg(not(llvm_ir_check))]
678impl From<HexError> for crate::Unspecified {
679    fn from(_value: HexError) -> Self {
680        Self
681    }
682}
683
684#[cfg(not(llvm_ir_check))]
685impl core::fmt::Display for HexError {
686    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
687        match self {
688            Self::Size => f.write_str("HexError::Size"),
689            Self::Encoding => f.write_str("HexError::Encoding")
690        }
691    }
692}
693
694#[cfg(not(llvm_ir_check))]
695impl core::fmt::Debug for HexError {
696    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
697        <Self as core::fmt::Display>::fmt(self, f)
698    }
699}
700
701#[cfg(not(llvm_ir_check))]
702std! { impl std::error::Error for HexError {} }
703
704/// Constant-Time Hex Decoding
705///
706/// # Arguments
707///
708/// * `input`  - The hex-encoded slice to decode into `output`.
709/// * `output` - The output buffer which the `input` is decoded into. This must be at least
710///   `input.len() / 2` in size.
711///
712/// # Errors
713///
714/// - [`HexError::Size`]: The output could not fit the decoded input, or the input was of invalid
715///   length.
716/// - [`HexError::Encoding`]: An invalid character was encountered.
717///
718/// # Returns
719///
720/// The amount of data which was decoded (`input.len() / 2`).
721pub fn hex_decode(input: &[u8], output: &mut [u8]) -> Result<usize, HexError> {
722    let (valid_len, dec_len) = decode_predicate(input.len(), output.len());
723    if !valid_len { return Err(HexError::Size) }
724
725    let mut err: u16 = 0;
726
727    // `take` to guard against the length being zero
728    for (pos, o_byte) in output.iter_mut().enumerate().take(dec_len) {
729        let src_pos = pos << 1;
730
731        let byte = decode_nibble(input[src_pos]).wrapping_shl(4)
732            | decode_nibble(input[src_pos + 1]);
733
734        err |= byte >> 8;
735
736        *o_byte = byte as u8;
737    }
738
739    if err == 0 {
740        Ok(dec_len)
741    } else {
742        Err(HexError::Encoding)
743    }
744}
745
746#[cfg(not(llvm_ir_check))]
747alloc! {
748    /// Constant-Time Hex Decoding
749    ///
750    /// # Arguments
751    ///
752    /// * `input`  - The hex-encoded slice to decode.
753    ///
754    /// # Errors
755    ///
756    /// - [`HexError::Size`]: The `input` length was not an even number.
757    /// - [`HexError::Encoding`]: An invalid character was encountered.
758    ///
759    /// # Returns
760    ///
761    /// The decoded bytes.
762    ///
763    /// # Example
764    ///
765    /// ```
766    /// use wolf_crypto::{hex, ct_eq};
767    ///
768    /// let encoded = hex::encode(b"hello world");
769    /// let decoded = hex::decode(encoded.as_bytes()).unwrap();
770    ///
771    /// assert!(ct_eq(b"hello world", decoded));
772    /// ```
773    pub fn hex_decode_alloc(input: &[u8]) -> Result<Vec<u8>, HexError> {
774        let mut output = vec![0u8; input.len() >> 1];
775        hex_decode(input, output.as_mut_slice()).map(move |_| output)
776    }
777}
778
779#[cfg(any(test, kani, check))]
780const fn is_valid_hex(byte: u8) -> bool {
781    matches!(
782        byte,
783        b'A'..=b'Z'
784        | b'a'..=b'z'
785        | b'0'..=b'9'
786    )
787}
788
789#[cfg(any(test, kani, check))]
790const fn is_valid_hex_2(first: u8, second: u8) -> bool {
791    is_valid_hex(first) && is_valid_hex(second)
792}
793
794#[cfg(any(kani, check))]
795const fn is_valid_hex_4(a: u8, b: u8, c: u8, d: u8) -> bool {
796    is_valid_hex_2(a, b) && is_valid_hex_2(c, d)
797}
798
799#[cfg(test)]
800mod tests {
801    use super::*;
802
803    #[test]
804    fn cmp_slice_eq_smoke() {
805        let a = [3u8; 17];
806        let b = [3u8; 17];
807
808        assert_eq!(cmp_slice(a.as_slice(), b.as_slice()), 1);
809    }
810
811    macro_rules! str {
812        ($expr:expr) => {
813            core::str::from_utf8($expr).unwrap()
814        };
815    }
816
817    #[test]
818    fn hex_encode_works() {
819        let mut out = [0u8; 22];
820        let len = hex_encode(b"hello world", &mut out).unwrap();
821        assert_eq!(len, 22);
822        assert_eq!(str!(&out), "68656c6c6f20776f726c64");
823    }
824
825    #[test]
826    fn hex_encode_to_decode() {
827        let mut out = [0u8; 22];
828        let _len = hex_encode(b"hello world", &mut out).unwrap();
829
830        let mut dec = [0u8; 11];
831        let read = hex_decode(&out, &mut dec).unwrap();
832
833        assert_eq!(read, 11);
834        assert_eq!(&dec, b"hello world");
835    }
836
837    #[test]
838    fn invalid_hex() {
839        let mut out = [0; 69];
840        assert!(hex_decode(b"hello world I am not valid hex !!!", &mut out).is_err());
841    }
842}
843
844#[cfg(test)]
845mod property_tests {
846    use super::*;
847    use proptest::prelude::*;
848    use crate::aes::test_utils::BoundList;
849
850    proptest! {
851        #![proptest_config(ProptestConfig::with_cases(100_000))]
852
853        #[test]
854        fn enusre_ct_add_no_wrap(a in any::<u32>(), b in any::<u32>()) {
855            let (out, res) = add_no_wrap(a, b);
856
857            ensure!(( res.is_err() ) <==> ( a.checked_add(b).is_none() ));
858            ensure!(( out == a )     <==> ( res.is_err() || b == 0 ));
859            ensure!(( res.is_ok() )  <==> ( out != a || b == 0 ));
860        }
861    }
862
863    proptest! {
864        #![proptest_config(ProptestConfig::with_cases(10_000))]
865
866        #[test]
867        fn ensure_cmp_slice(a in any::<BoundList<1024>>(), b in any::<BoundList<1024>>()) {
868            let ct_res = cmp_slice(a.as_slice(), b.as_slice()) == 1;
869            let res = a.as_slice() == b.as_slice();
870            
871            ensure!((ct_res) <==> (res));
872            ensure!((!ct_res) <==> (!res));
873        }
874    }
875
876    proptest! {
877        #![proptest_config(ProptestConfig::with_cases(50_000))]
878
879        #[test]
880        fn hex_encode_is_hex_crate(
881            bin in any::<BoundList<1024>>()
882        ) {
883            let output_len = hex_encode_len(bin.len());
884            let mut output = BoundList::<2048>::new_zeroes(output_len);
885
886            let res = hex::encode(bin.as_slice());
887            let len = hex_encode(bin.as_slice(), output.as_mut_slice()).unwrap();
888
889            prop_assert_eq!(res.len(), len);
890            prop_assert_eq!(res.as_bytes(), output.as_slice());
891        }
892
893        #[test]
894        fn hex_is_bijective(
895            bin in any::<BoundList<1024>>()
896        ) {
897            let output_len = hex_encode_len(bin.len());
898            let mut output = BoundList::<2048>::new_zeroes(output_len);
899
900            let len = hex_encode(bin.as_slice(), output.as_mut_slice()).unwrap();
901
902            prop_assert_eq!(len, output.len());
903
904            let mut decoded = bin.create_self();
905            let len = hex_decode(output.as_slice(), decoded.as_mut_slice()).unwrap();
906
907            prop_assert_eq!(len, bin.len());
908            prop_assert_eq!(decoded.as_slice(), bin.as_slice());
909        }
910    }
911
912    // I'd run tests in release.
913    proptest! {
914        #![proptest_config(ProptestConfig::with_cases(1_000_000))]
915
916        #[test]
917        fn hex_encode_is_valid_utf8(
918            bin in any::<BoundList<256>>()
919        ) {
920            let output_len = hex_encode_len(bin.len());
921            let mut output = BoundList::<512>::new_zeroes(output_len);
922
923            hex_encode(bin.as_slice(), output.as_mut_slice()).unwrap();
924
925            prop_assert!(core::str::from_utf8(output.as_slice()).is_ok());
926        }
927    }
928}
929
930#[cfg(kani)]
931mod verify {
932    use super::*;
933    use kani::proof;
934
935    #[proof]
936    fn check_ct_add_no_wrap() {
937        let a = kani::any();
938        let b = kani::any();
939
940        let (out, res) = add_no_wrap(a, b);
941
942        ensure!(( res.is_err() ) <==> ( a.checked_add(b).is_none() ));
943        ensure!(( out == a )     <==> ( res.is_err() || b == 0 ));
944        ensure!(( res.is_ok() )  <==> ( out != a || b == 0 ));
945    }
946
947    #[proof]
948    fn check_ct_gt() {
949        let a = kani::any();
950        let b = kani::any();
951
952        let is_gt = gt(a, b) == 1;
953
954        ensure!((is_gt) <==> (a > b));
955        ensure!((!is_gt) <==> (a <= b));
956    }
957
958    // This has crashed my computer multiple times due to kani using something like 160gb of memory.
959    // Had to dial it down significantly.
960    #[proof]
961    #[kani::unwind(12)]
962    fn check_slice_cmp() {
963        let a = kani::vec::any_vec::<u8, 7>();
964        let b = kani::vec::any_vec::<u8, 7>();
965
966        let res = a == b;
967        let ct_res = cmp_slice(a.as_slice(), b.as_slice()) == 1;
968
969        ensure!((res) <==> (ct_res));
970        ensure!((!res) <==> (!ct_res));
971    }
972
973    #[proof]
974    fn encode_byte_is_always_utf8() {
975        let byte: u8 = kani::any();
976        let mut out = [0u8; 2];
977
978        encode_byte(byte, &mut out);
979
980        kani::assert(
981            is_valid_hex_2(out[0], out[1]),
982            "For all bytes, the output must always be valid UTF8"
983        );
984    }
985
986    #[proof]
987    fn verify_encode_n_plus_1_bytes_symbolic() {
988        // This proof symbolically represents the inductive step:
989        // If encoding n bytes produces valid UTF-8, then encoding n+1 bytes also produces valid
990        // UTF-8.
991
992        let byte: u8 = kani::any();
993        let mut out = [0u8; 4];
994
995        encode_byte(byte, &mut out);
996
997        let byte: u8 = kani::any();
998        encode_byte(byte, &mut out[2..]);
999
1000        kani::assert(
1001            is_valid_hex_2(out[0], out[1]) && is_valid_hex_2(out[2], out[3]),
1002            "Encoding an additional byte preserves UTF-8 validity"
1003        );
1004    }
1005
1006    // Now, we simply re-apply what we have done using encode byte individually to symbolically
1007    // prove the inductive step to the actual hex_encode function. This function already has
1008    // postconditions which are checked via kani, so we do not need to rewrite any part of the
1009    // specification here.
1010    //
1011    // Again, this is a symbolic proof, as for all slices would crash a computer and is logically
1012    // equivalent to symbolically proving the inductive step.
1013    #[proof]
1014    fn verify_hex_encode_inductive_step_symbolic() {
1015        // no real harm in doing more as long as our PC can handle it. However, there is no benefit
1016        // to this.
1017        let input: [u8; 6] = kani::any();
1018        let mut output = [0u8; 12];
1019
1020        hex_encode(&input, &mut output).unwrap();
1021
1022        kani::assert(
1023            is_valid_hex_4(output[0], output[1], output[2], output[3])
1024                && is_valid_hex_4(output[4], output[5], output[6], output[7])
1025                && is_valid_hex_4(output[8], output[9], output[10], output[11]),
1026            "Encoding an additional byte preserves UTF-8 validity"
1027        )
1028    }
1029}