Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/polyval-0.6.2/src/backend/soft64.rs
Line
Count
Source
1
//! Constant-time software implementation of POLYVAL for 64-bit architectures.
2
//! Adapted from BearSSL's `ghash_ctmul64.c`:
3
//!
4
//! <https://bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/hash/ghash_ctmul64.c;hb=4b6046412>
5
//!
6
//! Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
7
8
use core::{
9
    num::Wrapping,
10
    ops::{Add, Mul},
11
};
12
13
use universal_hash::{
14
    consts::{U1, U16},
15
    crypto_common::{BlockSizeUser, KeySizeUser, ParBlocksSizeUser},
16
    KeyInit, Reset, UhfBackend, UniversalHash,
17
};
18
19
#[cfg(feature = "zeroize")]
20
use zeroize::Zeroize;
21
22
use crate::{Block, Key, Tag};
23
24
/// **POLYVAL**: GHASH-like universal hash over GF(2^128).
25
#[derive(Clone)]
26
pub struct Polyval {
27
    /// GF(2^128) field element input blocks are multiplied by
28
    h: U64x2,
29
30
    /// Field element representing the computed universal hash
31
    s: U64x2,
32
}
33
34
impl Polyval {
35
    /// Initialize POLYVAL with the given `H` field element and initial block
36
0
    pub fn new_with_init_block(h: &Key, init_block: u128) -> Self {
37
0
        Self {
38
0
            h: h.into(),
39
0
            s: init_block.into(),
40
0
        }
41
0
    }
42
}
43
44
impl KeySizeUser for Polyval {
45
    type KeySize = U16;
46
}
47
48
impl KeyInit for Polyval {
49
    /// Initialize POLYVAL with the given `H` field element
50
0
    fn new(h: &Key) -> Self {
51
0
        Self::new_with_init_block(h, 0)
52
0
    }
53
}
54
55
impl BlockSizeUser for Polyval {
56
    type BlockSize = U16;
57
}
58
59
impl ParBlocksSizeUser for Polyval {
60
    type ParBlocksSize = U1;
61
}
62
63
impl UhfBackend for Polyval {
64
0
    fn proc_block(&mut self, x: &Block) {
65
0
        let x = U64x2::from(x);
66
0
        self.s = (self.s + x) * self.h;
67
0
    }
68
}
69
70
impl UniversalHash for Polyval {
71
0
    fn update_with_backend(
72
0
        &mut self,
73
0
        f: impl universal_hash::UhfClosure<BlockSize = Self::BlockSize>,
74
0
    ) {
75
0
        f.call(self);
76
0
    }
77
78
    /// Get POLYVAL result (i.e. computed `S` field element)
79
0
    fn finalize(self) -> Tag {
80
0
        let mut block = Block::default();
81
82
0
        for (chunk, i) in block.chunks_mut(8).zip(&[self.s.0, self.s.1]) {
83
0
            chunk.copy_from_slice(&i.to_le_bytes());
84
0
        }
85
86
0
        block
87
0
    }
88
}
89
90
impl Reset for Polyval {
91
0
    fn reset(&mut self) {
92
0
        self.s = U64x2::default();
93
0
    }
94
}
95
96
#[cfg(feature = "zeroize")]
97
impl Drop for Polyval {
98
    fn drop(&mut self) {
99
        self.h.zeroize();
100
        self.s.zeroize();
101
    }
102
}
103
104
/// 2 x `u64` values
105
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
106
struct U64x2(u64, u64);
107
108
impl From<&Block> for U64x2 {
109
0
    fn from(bytes: &Block) -> U64x2 {
110
0
        U64x2(
111
0
            u64::from_le_bytes(bytes[..8].try_into().unwrap()),
112
0
            u64::from_le_bytes(bytes[8..].try_into().unwrap()),
113
0
        )
114
0
    }
115
}
116
117
impl From<u128> for U64x2 {
118
0
    fn from(x: u128) -> Self {
119
0
        U64x2((x >> 64) as u64, (x) as u64)
120
0
    }
121
}
122
123
#[allow(clippy::suspicious_arithmetic_impl)]
124
impl Add for U64x2 {
125
    type Output = Self;
126
127
    /// Adds two POLYVAL field elements.
128
0
    fn add(self, rhs: Self) -> Self::Output {
129
0
        U64x2(self.0 ^ rhs.0, self.1 ^ rhs.1)
130
0
    }
131
}
132
133
#[allow(clippy::suspicious_arithmetic_impl)]
134
impl Mul for U64x2 {
135
    type Output = Self;
136
137
    /// Computes carryless POLYVAL multiplication over GF(2^128) in constant time.
138
    ///
139
    /// Method described at:
140
    /// <https://www.bearssl.org/constanttime.html#ghash-for-gcm>
141
    ///
142
    /// POLYVAL multiplication is effectively the little endian equivalent of
143
    /// GHASH multiplication, aside from one small detail described here:
144
    ///
145
    /// <https://crypto.stackexchange.com/questions/66448/how-does-bearssls-gcm-modular-reduction-work/66462#66462>
146
    ///
147
    /// > The product of two bit-reversed 128-bit polynomials yields the
148
    /// > bit-reversed result over 255 bits, not 256. The BearSSL code ends up
149
    /// > with a 256-bit result in zw[], and that value is shifted by one bit,
150
    /// > because of that reversed convention issue. Thus, the code must
151
    /// > include a shifting step to put it back where it should
152
    ///
153
    /// This shift is unnecessary for POLYVAL and has been removed.
154
0
    fn mul(self, rhs: Self) -> Self {
155
0
        let h0 = self.0;
156
0
        let h1 = self.1;
157
0
        let h0r = rev64(h0);
158
0
        let h1r = rev64(h1);
159
0
        let h2 = h0 ^ h1;
160
0
        let h2r = h0r ^ h1r;
161
162
0
        let y0 = rhs.0;
163
0
        let y1 = rhs.1;
164
0
        let y0r = rev64(y0);
165
0
        let y1r = rev64(y1);
166
0
        let y2 = y0 ^ y1;
167
0
        let y2r = y0r ^ y1r;
168
0
        let z0 = bmul64(y0, h0);
169
0
        let z1 = bmul64(y1, h1);
170
171
0
        let mut z2 = bmul64(y2, h2);
172
0
        let mut z0h = bmul64(y0r, h0r);
173
0
        let mut z1h = bmul64(y1r, h1r);
174
0
        let mut z2h = bmul64(y2r, h2r);
175
176
0
        z2 ^= z0 ^ z1;
177
0
        z2h ^= z0h ^ z1h;
178
0
        z0h = rev64(z0h) >> 1;
179
0
        z1h = rev64(z1h) >> 1;
180
0
        z2h = rev64(z2h) >> 1;
181
182
0
        let v0 = z0;
183
0
        let mut v1 = z0h ^ z2;
184
0
        let mut v2 = z1 ^ z2h;
185
0
        let mut v3 = z1h;
186
187
0
        v2 ^= v0 ^ (v0 >> 1) ^ (v0 >> 2) ^ (v0 >> 7);
188
0
        v1 ^= (v0 << 63) ^ (v0 << 62) ^ (v0 << 57);
189
0
        v3 ^= v1 ^ (v1 >> 1) ^ (v1 >> 2) ^ (v1 >> 7);
190
0
        v2 ^= (v1 << 63) ^ (v1 << 62) ^ (v1 << 57);
191
192
0
        U64x2(v2, v3)
193
0
    }
194
}
195
196
#[cfg(feature = "zeroize")]
197
impl Zeroize for U64x2 {
198
    fn zeroize(&mut self) {
199
        self.0.zeroize();
200
        self.1.zeroize();
201
    }
202
}
203
204
/// Multiplication in GF(2)[X], truncated to the low 64-bits, with “holes”
205
/// (sequences of zeroes) to avoid carry spilling.
206
///
207
/// When carries do occur, they wind up in a "hole" and are subsequently masked
208
/// out of the result.
209
0
fn bmul64(x: u64, y: u64) -> u64 {
210
0
    let x0 = Wrapping(x & 0x1111_1111_1111_1111);
211
0
    let x1 = Wrapping(x & 0x2222_2222_2222_2222);
212
0
    let x2 = Wrapping(x & 0x4444_4444_4444_4444);
213
0
    let x3 = Wrapping(x & 0x8888_8888_8888_8888);
214
0
    let y0 = Wrapping(y & 0x1111_1111_1111_1111);
215
0
    let y1 = Wrapping(y & 0x2222_2222_2222_2222);
216
0
    let y2 = Wrapping(y & 0x4444_4444_4444_4444);
217
0
    let y3 = Wrapping(y & 0x8888_8888_8888_8888);
218
219
0
    let mut z0 = ((x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1)).0;
220
0
    let mut z1 = ((x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2)).0;
221
0
    let mut z2 = ((x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3)).0;
222
0
    let mut z3 = ((x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0)).0;
223
224
0
    z0 &= 0x1111_1111_1111_1111;
225
0
    z1 &= 0x2222_2222_2222_2222;
226
0
    z2 &= 0x4444_4444_4444_4444;
227
0
    z3 &= 0x8888_8888_8888_8888;
228
229
0
    z0 | z1 | z2 | z3
230
0
}
231
232
/// Bit-reverse a `u64` in constant time
233
0
fn rev64(mut x: u64) -> u64 {
234
0
    x = ((x & 0x5555_5555_5555_5555) << 1) | ((x >> 1) & 0x5555_5555_5555_5555);
235
0
    x = ((x & 0x3333_3333_3333_3333) << 2) | ((x >> 2) & 0x3333_3333_3333_3333);
236
0
    x = ((x & 0x0f0f_0f0f_0f0f_0f0f) << 4) | ((x >> 4) & 0x0f0f_0f0f_0f0f_0f0f);
237
0
    x = ((x & 0x00ff_00ff_00ff_00ff) << 8) | ((x >> 8) & 0x00ff_00ff_00ff_00ff);
238
0
    x = ((x & 0xffff_0000_ffff) << 16) | ((x >> 16) & 0xffff_0000_ffff);
239
0
    (x << 32) | (x >> 32)
240
0
}