1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! Low-level utility functions and constants shared across the crate.
//!
//! This module provides:
//!
//! - Constants [`BLOCKBYTES`] and [`BYTES`] describing SHA-512 block and output sizes.
//! - Big-endian byte-order conversion helpers [`load_be`] and [`store_be`].
//! - A non-short-circuiting comparison function [`verify`] that aims to reduce
//! timing side-channel leakage.
//!
//! All functions are `#[inline(always)]` (or close to it) to ensure zero-cost
//! abstraction in the hot paths of hash and HMAC computations.
/// SHA-512 block size in bytes.
///
/// The SHA-512 algorithm processes messages in 1024-bit (128-byte) blocks.
/// This constant is used throughout the crate to dimension internal buffers
/// and to verify HKDF/HMAC input constraints.
pub const BLOCKBYTES: usize = 128;
/// SHA-512 output size in bytes.
///
/// A full SHA-512 digest is 512 bits = 64 bytes. HMAC-SHA-512 and HKDF-SHA-512
/// also produce 64-byte outputs. For SHA-384, the output is truncated to 48 bytes
/// (`BYTES` still represents the underlying SHA-512 length).
pub const BYTES: usize = 64;
/// Loads a 64-bit unsigned integer from a big-endian byte slice at a given offset.
///
/// This function is the fundamental building block for reading message words and
/// state values. It uses the standard library's [`u64::from_be_bytes`], which is
/// compiled to an efficient byte-swap if the target is little-endian, and a plain
/// load on big-endian platforms.
///
/// # Why this is `#[inline]`
///
/// The function is always inlined to guarantee that the bounds check on
/// `base[offset..offset + 8]` is eliminated when the caller ensures the slice is
/// at least `offset + 8` bytes long. In release builds, this becomes a single
/// `mov` + `bswap` on x86-64.
///
/// # Panics
///
/// Panics if `base` is shorter than `offset + 8` bytes.
///
/// # Examples
///
/// ```
/// # use libvctrl_sha512::utils::load_be;
/// let data: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00];
/// let value = load_be(&data, 0);
/// assert_eq!(value, 256);
/// ```
///
/// Demonstrating the roundtrip with [`store_be`]:
///
/// ```
/// # use libvctrl_sha512::utils::{load_be, store_be};
/// let original: u64 = 0x0123456789abcdef;
/// let mut buf = [0u8; 8];
/// store_be(&mut buf, 0, original);
/// assert_eq!(load_be(&buf, 0), original);
/// ```
/// Stores a 64-bit unsigned integer into a byte slice at a given offset in big-endian order.
///
/// This is the inverse of [`load_be`]. It writes the 8 bytes of `x` in big-endian
/// format starting at `base[offset]`. The caller must ensure that `base` is at
/// least `offset + 8` bytes long.
///
/// # Design
///
/// The method uses [`u64::to_be_bytes`] and [`copy_from_slice`], which allows the
/// compiler to generate optimal store sequences (e.g., a `bswap` + `mov` on
/// little-endian).
///
/// # Examples
///
/// ```
/// # use libvctrl_sha512::utils::store_be;
/// let mut buf = [0u8; 8];
/// store_be(&mut buf, 0, 0xdeadbeef);
/// assert_eq!(buf, [0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef]);
/// ```
/// Compares two byte slices in a way that aims to resist timing side-channel attacks.
///
/// Standard `==` for slices short-circuits on the first differing byte, which
/// can leak information through timing. This function accumulates the XOR
/// differences of all bytes and returns `true` only if all bytes are equal.
/// Additionally, on WASM targets an extra hash-based accumulator is used to
/// further hinder timing analysis, because WASM linear memory does not guarantee
/// constant-time access patterns.
///
/// # How it works
///
/// 1. Length mismatch returns `false` immediately (length is not secret).
/// 2. A 32-bit accumulator `v` is initialised to 0.
/// 3. **WASM only:** two independent hashes (`h1` and `h2`) mix the bytes of
/// each slice using a simple 5-bit left-rotate + XOR scheme. The XOR of
/// `h1` and `h2` is OR-ed into `v`. This scrambles the intermediate
/// accumulator so that even small differences spread across many bits.
/// 4. For every byte pair `(a, b)`, `v |= (a ^ b)`.
/// 5. A [`core::ptr::read_volatile`] is used to read `v` at the end. This
/// prevents the compiler from optimising away the accumulator chain or
/// reducing it to a short-circuit comparison.
/// 6. Returns `true` if `v == 0`.
///
/// # Limitations
///
/// This is **not** a fully constant-time implementation on all targets. It
/// significantly raises the bar for timing attacks but does not provide the
/// guarantees of formally verified constant-time code. For high-security
/// applications, prefer a dedicated constant-time library.
///
/// # Examples
///
/// Comparing two equal slices:
///
/// ```
/// # use libvctrl_sha512::utils::verify;
/// let a = [1, 2, 3];
/// let b = [1, 2, 3];
/// assert!(verify(&a, &b));
/// ```
///
/// Different slices:
///
/// ```
/// # use libvctrl_sha512::utils::verify;
/// let a = [1, 2, 3];
/// let b = [1, 2, 4];
/// assert!(!verify(&a, &b));
/// ```
///
/// Length mismatch:
///
/// ```
/// # use libvctrl_sha512::utils::verify;
/// assert!(!verify(&[1, 2], &[1, 2, 3]));
/// ```