diff --git a/src/sm2/ec.rs b/src/sm2/ec.rs index 318d5f1..f02d517 100644 --- a/src/sm2/ec.rs +++ b/src/sm2/ec.rs @@ -222,10 +222,9 @@ impl JacobianPoint { result } - /// 基点标量乘 k·G(密钥生成和签名专用) + /// 基点标量乘 k·G(密钥生成和签名专用,使用 w=4 固定窗口加速) pub fn scalar_mul_g(k: &U256) -> JacobianPoint { - let g = JacobianPoint::from_affine(&AffinePoint { x: GX, y: GY }); - Self::scalar_mul(k, &g) + scalar_mul_g_window(k) } } @@ -242,6 +241,118 @@ fn double2(a: &Fp) -> Fp { double1(&t) } +// ── 混合 Jacobian-仿射加法(q.Z = 1 优化)──────────────────────────────────── + +/// 混合点加 P(Jacobian)+ Q(Affine,Z=1) +/// +/// 相比标准 Jacobian+Jacobian 加法,利用 Z_Q=1 省去: +/// - Z2² 计算(1 次 fp_square) +/// - X1·Z2² 简化为 X1(0 次乘法) +/// - Y1·Z2³ 简化为 Y1(0 次乘法) +/// - Z3 中的 Z2 乘法(Z3 = H·Z1,而非 H·Z1·Z2) +/// 共节省约 3~4 次域乘法,用于预计算表构建和 multi_scalar_mul 内循环。 +/// +/// # 安全性 +/// 完全常量时间,退化情况处理与 `JacobianPoint::add` 相同。 +fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint { + use subtle::ConstantTimeEq; + + // Z_Q = 1,故 u1 = X1,s1 = Y1(无需额外乘法) + let z1sq = fp_square(&p.z); // Z1² + let z1cu = fp_mul(&p.z, &z1sq); // Z1³ + let u2 = fp_mul(&q.x, &z1sq); // X2·Z1² + let s2 = fp_mul(&q.y, &z1cu); // Y2·Z1³ + + let h = fp_sub(&u2, &p.x); + let r = fp_sub(&s2, &p.y); + + let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]); + let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]); + + let h2 = fp_square(&h); + let h3 = fp_mul(&h, &h2); + let u1h2 = fp_mul(&p.x, &h2); + + let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2)); + let y3 = fp_sub( + &fp_mul(&r, &fp_sub(&u1h2, &x3)), + &fp_mul(&p.y, &h3), + ); + // Reason: Z_Q = 1,故 Z3 = H·Z1·Z2 = H·Z1,节省一次乘法 + let z3 = fp_mul(&h, &p.z); + let normal = JacobianPoint { x: x3, y: y3, z: z3 }; + + let double_p = p.double(); + + let result = normal; + let result = JacobianPoint::conditional_select( + &result, &JacobianPoint::INFINITY, h_is_zero & !r_is_zero, + ); + let result = JacobianPoint::conditional_select( + &result, &double_p, h_is_zero & r_is_zero, + ); + // P = INFINITY → 返回 Q(注:预计算表中 Q 绝不是无穷远点, + // 但在通用调用中仍需正确处理) + let q_jac = JacobianPoint::from_affine(q); + JacobianPoint::conditional_select(&result, &q_jac, p.ct_is_infinity()) +} + +// ── SM2 基点固定窗口标量乘(w=4)───────────────────────────────────────────── + +/// 基点固定窗口标量乘 k·G(w=4,预计算 15 个点,常量时间) +/// +/// 原理:将 256-bit 标量按 4-bit 切分为 64 个窗口。 +/// 每个窗口先执行 4 次倍点,再常量时间查表做一次加法。 +/// 共需 256 次 double + 64 次 add,相比双倍-加法的 256 次 add 节省约 75%。 +/// +/// Reason: 预计算表仅含 G 的已知倍数(公开常量基点),不依赖秘密输入; +/// 窗口值为秘密标量位,但表查找通过 15 次 `conditional_select` 实现, +/// 不含任何数据依赖分支,保持常量时间性质。 +fn scalar_mul_g_window(k: &U256) -> JacobianPoint { + use subtle::ConstantTimeEq; + + let g_aff = AffinePoint { x: GX, y: GY }; + let g_jac = JacobianPoint::from_affine(&g_aff); + + // 预计算表:table[i] = i·G,i = 0..=15(table[0] = INFINITY,占位不用) + // Reason: 使用 add_mixed 构建表,g_aff 始终 Z=1,节省约 3 次域乘/步 + let mut table = [JacobianPoint::INFINITY; 16]; + table[1] = g_jac; + for i in 2..=15usize { + table[i] = add_mixed(&table[i - 1], &g_aff); + } + + let mut result = JacobianPoint::INFINITY; + for byte in &k.to_be_bytes() { + // ── 高 4 位窗口 ───────────────────────────────────────────────────── + for _ in 0..4 { + result = result.double(); + } + let window = (byte >> 4) as u8; + // 常量时间表查找:遍历 1..=15,用 ct_eq 选出 table[window] + let mut sel = JacobianPoint::INFINITY; + for j in 1u8..=15 { + let eq = window.ct_eq(&j); + sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq); + } + // window=0 时 sel 仍为 INFINITY,add(result, INFINITY) = result + result = JacobianPoint::add(&result, &sel); + + // ── 低 4 位窗口 ───────────────────────────────────────────────────── + for _ in 0..4 { + result = result.double(); + } + let window = byte & 0xF; + let mut sel = JacobianPoint::INFINITY; + for j in 1u8..=15 { + let eq = window.ct_eq(&j); + sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq); + } + result = JacobianPoint::add(&result, &sel); + } + result +} + // ── AffinePoint 公开接口 ────────────────────────────────────────────────────── impl AffinePoint { @@ -345,10 +456,13 @@ impl AffinePoint { /// Shamir's trick 预计算 {P, Q, P+Q},每位只需 1 次 double + 最多 1 次 add, /// 比两次独立标量乘(各 256 次 double + 平均 128 add)快约 25%。 pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result { + // Reason: u、v 均为验签公开值,non-CT 的 match 分支不泄露秘密; + // 使用 add_mixed(Jacobian, Affine) 替代全量 Jacobian add, + // 节省约 3 次域乘/步,g 和 q 已是仿射坐标直接传入。 let g = AffinePoint::generator(); - let g_jac = JacobianPoint::from_affine(&g); let q_jac = JacobianPoint::from_affine(q); - // 预计算 P+Q(G+Q) + let g_jac = JacobianPoint::from_affine(&g); + // 预计算 G+Q(Jacobian,含退化处理) let gq_jac = JacobianPoint::add(&g_jac, &q_jac); let u_bytes = u.to_be_bytes(); @@ -363,15 +477,13 @@ pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result> b) & 1; let vi = (vb >> b) & 1; - // Reason: 根据两个标量位的组合,选择加哪个预计算点 - let addend = match (ui, vi) { - (1, 0) => Some(&g_jac), - (0, 1) => Some(&q_jac), - (1, 1) => Some(&gq_jac), - _ => None, - }; - if let Some(p) = addend { - result = JacobianPoint::add(&result, p); + // Reason: u、v 公开,match 分支安全;add_mixed 对仿射 g/q 节省域乘, + // gq 为 Jacobian 仍用全量 add(无额外求逆开销) + match (ui, vi) { + (1, 0) => result = add_mixed(&result, &g), + (0, 1) => result = add_mixed(&result, q), + (1, 1) => result = JacobianPoint::add(&result, &gq_jac), + _ => {} } } } diff --git a/src/sm3/compress.rs b/src/sm3/compress.rs index 839f0d1..b380763 100644 --- a/src/sm3/compress.rs +++ b/src/sm3/compress.rs @@ -8,25 +8,23 @@ pub(super) const IV: [u32; 8] = [ 0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E, ]; -/// 布尔函数 FF_j(GB/T 32905 §4.4) -#[inline(always)] -fn ff(x: u32, y: u32, z: u32, j: usize) -> u32 { - if j < 16 { - x ^ y ^ z - } else { - (x & y) | (x & z) | (y & z) +/// 轮常量 T_j 预计算表(GB/T 32905 §4.2) +/// +/// Reason: 消除 t_j() 中的 `if j < 16` 运行时分支, +/// 常量折叠后编译器直接嵌入立即数,无运行时旋转开销。 +const T: [u32; 64] = { + let mut t = [0u32; 64]; + let mut j = 0usize; + while j < 16 { + t[j] = 0x79CC4519u32.rotate_left(j as u32); + j += 1; } -} - -/// 布尔函数 GG_j(GB/T 32905 §4.4) -#[inline(always)] -fn gg(x: u32, y: u32, z: u32, j: usize) -> u32 { - if j < 16 { - x ^ y ^ z - } else { - (x & y) | (!x & z) + while j < 64 { + t[j] = 0x7A879D8Au32.rotate_left((j % 32) as u32); + j += 1; } -} + t +}; /// 置换函数 P0(GB/T 32905 §4.5) #[inline(always)] @@ -40,19 +38,16 @@ fn p1(x: u32) -> u32 { x ^ x.rotate_left(15) ^ x.rotate_left(23) } -/// SM3 轮常量 T_j(GB/T 32905 §4.2) -#[inline(always)] -fn t_j(j: usize) -> u32 { - if j < 16 { - 0x79CC4519u32.rotate_left(j as u32) - } else { - 0x7A879D8Au32.rotate_left((j % 32) as u32) - } -} - /// SM3 压缩函数:处理一个 64 字节消息块,更新 state(GB/T 32905 §5.3.2) +/// +/// 实现说明: +/// - 轮函数分两段(j=0..15 和 j=16..63),消除 ff/gg 中的 `if j < 16` 运行时分支 +/// - T_j 常量使用预计算表,消除旋转运算 +/// - W' 数组内联为 w[j] ^ w[j+4],避免额外分配 pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { - // 消息扩展:将 64 字节分解为 16 个 u32(大端),再扩展到 W[0..67] 和 W'[0..63] + // ── 消息扩展 ───────────────────────────────────────────────────────────── + // W[0..15]: 直接从块加载(大端) + // W[16..67]: 用 P1 展开 let mut w = [0u32; 68]; for i in 0..16 { w[i] = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap()); @@ -61,45 +56,33 @@ pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { let v = w[i - 16] ^ w[i - 9] ^ w[i - 3].rotate_left(15); w[i] = p1(v) ^ w[i - 13].rotate_left(7) ^ w[i - 6]; } - // W' 数组(W'_j = W_j XOR W_{j+4}),内联避免分配 - // w1[j] = w[j] ^ w[j+4],在循环中直接计算 - // 压缩:64 轮 + // ── 压缩:64 轮 ────────────────────────────────────────────────────────── let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state; - for j in 0..64 { - let ss1 = a - .rotate_left(12) - .wrapping_add(e) - .wrapping_add(t_j(j)) - .rotate_left(7); + // Reason: 将 64 轮分两段展开,消除 ff/gg/T 中的 if 分支。 + // j = 0..15:FF = x^y^z,GG = x^y^z + for j in 0..16 { + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7); let ss2 = ss1 ^ a.rotate_left(12); - let w_j = w[j]; - let w_j4 = w[j + 4]; - let tt1 = ff(a, b, c, j) - .wrapping_add(d) - .wrapping_add(ss2) - .wrapping_add(w_j ^ w_j4); - let tt2 = gg(e, f, g, j) - .wrapping_add(h) - .wrapping_add(ss1) - .wrapping_add(w_j); - d = c; - c = b.rotate_left(9); - b = a; - a = tt1; - h = g; - g = f.rotate_left(19); - f = e; - e = p0(tt2); + let tt1 = (a ^ b ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]); + let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); } - state[0] ^= a; - state[1] ^= b; - state[2] ^= c; - state[3] ^= d; - state[4] ^= e; - state[5] ^= f; - state[6] ^= g; - state[7] ^= h; + // j = 16..63:FF = majority(x,y,z),GG = choice(x,y,z) + for j in 16..64 { + let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7); + let ss2 = ss1 ^ a.rotate_left(12); + let tt1 = ((a & b) | (a & c) | (b & c)) + .wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]); + let tt2 = ((e & f) | (!e & g)) + .wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j]); + d = c; c = b.rotate_left(9); b = a; a = tt1; + h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + } + + state[0] ^= a; state[1] ^= b; state[2] ^= c; state[3] ^= d; + state[4] ^= e; state[5] ^= f; state[6] ^= g; state[7] ^= h; } diff --git a/src/sm4/cipher.rs b/src/sm4/cipher.rs index 2bd0043..7e0ae31 100644 --- a/src/sm4/cipher.rs +++ b/src/sm4/cipher.rs @@ -175,14 +175,114 @@ pub(crate) fn sbox_ct(x: u8) -> u8 { | (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7) } -/// SM4 τ 变换:对 u32 的 4 个字节分别做 S-box(常量时间) +/// SM4 τ 变换:4 字节 u32 一次性位切片 S-box(常量时间,4-way 并行) +/// +/// # 实现原理 +/// +/// 将 4 字节同一位位置的 4 个 bit 打包到一个 u32 的低 4 位, +/// 单次执行布尔电路(同 `sbox_ct`),等效并行处理所有 4 个字节。 +/// +/// 与原方案(4 次独立 `sbox_ct(u8)`,每次 ~120 ops × 4 = ~480 ops)相比, +/// 此方案仅需 ~120 次 u32 位运算 + 打包/解包开销,约 **3~4x 提速**。 +/// +/// # 安全性 +/// +/// 继承 `sbox_ct` 的全部安全属性:零内存访问、无条件分支。 +/// u32 各位位置相互独立,常量 `0xF`(低 4 位全 1)用于取反。 #[inline] fn tau(a: u32) -> u32 { - let b0 = sbox_ct((a >> 24) as u8) as u32; - let b1 = sbox_ct((a >> 16) as u8) as u32; - let b2 = sbox_ct((a >> 8) as u8) as u32; - let b3 = sbox_ct(a as u8) as u32; - (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 + let bytes = a.to_be_bytes(); + + // ── 打包:bits[i] 低 4 位 = [byte0, byte1, byte2, byte3] 的第 i 位 ── + // Reason: 打包后每个 u32 变量的 bit-j 对应第 j 个字节的该位面, + // XOR/AND/OR 在 4 个独立"通道"上并行执行,语义不变。 + let mut bits = [0u32; 8]; + for i in 0..8usize { + bits[i] = ((bytes[0] >> i) & 1) as u32 + | (((bytes[1] >> i) & 1) as u32) << 1 + | (((bytes[2] >> i) & 1) as u32) << 2 + | (((bytes[3] >> i) & 1) as u32) << 3; + } + let [b0, b1, b2, b3, b4, b5, b6, b7] = bits; + + // ── S-box 布尔电路(与 sbox_ct 完全相同,1 → 0xF)──────────────────── + // Reason: sbox_ct 用 `1 ^ x` 表示 NOT;此处 4 通道并行故改为 `0xF ^ x`, + // 使 4 个 bit 位置都被正确取反,其余位运算(^/&/|)无需修改。 + let t1 = b7 ^ b5; + let t2 = 0xF ^ (b5 ^ b1); + let g5 = 0xF ^ b0; + let t3 = 0xF ^ (b0 ^ t2); + let t4 = b6 ^ b2; + let t5 = b3 ^ t3; + let t6 = b4 ^ t1; + let t7 = b1 ^ t5; + let t8 = b1 ^ t4; + let t9 = t6 ^ t8; + let t10 = t6 ^ t7; + let t11 = 0xF ^ (b3 ^ t1); + let t12 = 0xF ^ (b6 ^ t9); + + let g0 = t10; let g1 = t7; let g2 = t4 ^ t10; let g3 = t5; + let g4 = t2; let g6 = t11 ^ t2; let g7 = t12 ^ (t11 ^ t2); + let m0 = t6; let m1 = t3; let m2 = t8; let m3 = t3 ^ t12; + let m4 = t4; let m5 = t11; let m6 = b1; let m7 = t11 ^ m3; + let m8 = t9; let m9 = t12; + + let t2t = m0 & m1; let t3t = g0 & g4; let t4t = g3 & g7; + let t7t = g3 | g7; let t11t = m4 & m5; let t10t = m3 & m2; + let t12t = m3 | m2; let t6t = g6 | g2; let t9t = m6 | m7; + let t5t = m8 & m9; let t8t = m8 | m9; + let t14t = t3t ^ t2t; let t16t = t5t ^ t14t; let t20t = t16t ^ t7t; + let t17t = t9t ^ t10t; let t18t = t11t ^ t12t; + let p2 = t20t ^ t18t; let p0 = t6t ^ t16t; + let t1t = g5 & g1; let t13t = t1t ^ t2t; let t15t = t13t ^ t4t; + let p3 = (t6t ^ t15t) ^ t17t; let p1 = t8t ^ t15t; + + let t0m = p1 & p2; let t1m = p3 & p0; let t2m = p0 & p2; + let t3m = p1 & p3; let t4m = t0m & t2m; let t5m = t1m ^ t3m; + let t6m = t5m | p0; let t7m = t2m | p3; + let l3 = t4m ^ t6m; let t9m = t7m ^ t3m; let l0 = t0m ^ t9m; + let t11m = p2 | t5m; let l1 = t11m ^ t1m; + let t12m = p1 | t2m; let l2 = t12m ^ t5m; + + let k4 = l2 ^ l3; let k3 = l1 ^ l3; let k2 = l0 ^ l2; + let k0 = l0 ^ l1; let k1 = k2 ^ k3; + + let e0 = m1 & k0; let e1 = g5 & l1; let r0 = e0 ^ e1; + let e2 = g4 & l0; let r1 = e2 ^ e1; + let e3 = m7 & k3; let e4 = m5 & k2; let r2 = e3 ^ e4; + let e5 = m3 & k1; let r3 = e5 ^ e4; + let e6 = m9 & k4; let e7 = g7 & l3; let r4 = e6 ^ e7; + let e8 = g6 & l2; let r5 = e8 ^ e7; + let e9 = m0 & k0; let e10 = g1 & l1; let r6 = e9 ^ e10; + let e11 = g0 & l0; let r7 = e11 ^ e10; + let e12 = m6 & k3; let e13 = m4 & k2; let r8 = e12 ^ e13; + let e14 = m2 & k1; let r9 = e14 ^ e13; + let e15 = m8 & k4; let e16 = g3 & l3; let r10 = e15 ^ e16; + let e17 = g2 & l2; let r11 = e17 ^ e16; + + let t1o = r7 ^ r9; let t2o = r1 ^ t1o; let t3o = r3 ^ t2o; + let t4o = r5 ^ r3; let t5o = r4 ^ t4o; let t6o = r0 ^ r4; + let t7o = r11 ^ r7; + let b5o = t1o ^ t4o; let b2o = t1o ^ t6o; let t10o = r2 ^ t5o; + let b3o = r10 ^ r8; + let b1o = 0xF ^ (t3o ^ b3o); + let b6o = t10o ^ b1o; + let b4o = 0xF ^ (t3o ^ t7o); + let b0o = t6o ^ b4o; + let b7o = 0xF ^ (r10 ^ r6); + + // ── 解包:8 个 u32 低 4 位 → 4 个输出字节 ────────────────────────────── + let ob = [b0o, b1o, b2o, b3o, b4o, b5o, b6o, b7o]; + let mut out = [0u8; 4]; + for i in 0..8usize { + let v = ob[i]; + out[0] |= ((v & 1) as u8) << i; + out[1] |= (((v >> 1) & 1) as u8) << i; + out[2] |= (((v >> 2) & 1) as u8) << i; + out[3] |= (((v >> 3) & 1) as u8) << i; + } + u32::from_be_bytes(out) } /// SM4 加密轮函数 T(GB/T 32907 §6.2.1) diff --git a/src/sm4/modes.rs b/src/sm4/modes.rs index 567f5e2..d972a6c 100644 --- a/src/sm4/modes.rs +++ b/src/sm4/modes.rs @@ -186,37 +186,55 @@ pub fn sm4_crypt_ctr(key: &[u8; 16], nonce: &[u8; 16], data: &[u8]) -> Vec { // ── GCM ────────────────────────────────────────────────────────────────────── -/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间) +/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化) /// /// # 安全性 /// 使用掩码算术替代秘密依赖的条件分支,消除时序侧信道: -/// - `mask_xi`:由当前标量位生成的 0x00/0xFF 掩码,替代 `if bit == 1` -/// - `reduce_mask`:由 LSB 生成的 0x00/0xFF 掩码,替代 `if lsb == 1` +/// - `mask_xi`:由当前标量位生成的 u64 全掩码,替代 `if bit == 1` +/// - `reduce_mask`:由 LSB 生成的 u64 全掩码,替代 `if lsb == 1` +/// +/// # 性能优化 +/// 将内部状态从 `[u8; 16]` 改为 `[u64; 2]`(大端),使每次迭代的 +/// XOR/移位/规约从 16 次字节操作降至 ~6 次 64 位操作,约 4-6× 提速。 /// /// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;原条件分支泄露 H 的汉明重量, /// 是 cache-timing 和 branch-timing 攻击的经典目标(参见 Bricout 等 2016)。 +/// u64 向量化保持完全常量时间,同时大幅减少指令数。 fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] { - let mut z = [0u8; 16]; - let mut v = *y; - for byte_xi in x.iter() { + // Reason: 将 16 字节表示为 2 个大端 u64,便于用 64 位操作替代逐字节循环, + // XOR/移位从 16 次字节操作缩减至 2 次 u64 操作,指令数降低约 8×。 + let mut z = [0u64; 2]; + let mut v = [ + u64::from_be_bytes(y[0..8].try_into().unwrap()), + u64::from_be_bytes(y[8..16].try_into().unwrap()), + ]; + + for &byte_xi in x.iter() { for bit_idx in (0..8).rev() { - // Reason: 0u8.wrapping_sub(1) = 0xFF,wrapping_sub(0) = 0x00 - // 用掩码代替 if,确保两条路径执行时间完全相同 - let mask_xi = 0u8.wrapping_sub((byte_xi >> bit_idx) & 1); - for j in 0..16 { - z[j] ^= v[j] & mask_xi; - } - let lsb = v[15] & 1; - for j in (1..16).rev() { - v[j] = (v[j] >> 1) | (v[j - 1] << 7); - } + // Reason: 0u64.wrapping_sub(1) = 0xFFFF...,wrapping_sub(0) = 0x0000... + // 单次 u64 掩码覆盖原来 16 次 u8 掩码操作 + let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64); + z[0] ^= v[0] & mask; + z[1] ^= v[1] & mask; + + // GF(2^128) 右移 1 位(= 乘以 x),带规约多项式 x^128+x^7+x^2+x+1 + // Reason: v[0] 的 bit 0(= 大端第 64 位)移入 v[1] 的 bit 63, + // v[1] 的 bit 0(= GF 元素 x^0 系数)移出后触发规约。 + let lsb = v[1] & 1; + let carry = v[0] & 1; v[0] >>= 1; - // Reason: 同上,掩码替代 if lsb == 1,消除 GF 规约的秘密依赖分支 - let reduce_mask = 0u8.wrapping_sub(lsb); - v[0] ^= 0xE1 & reduce_mask; + v[1] = (v[1] >> 1) | (carry << 63); + // Reason: 规约项 0xE1_00...00 对应 x^7+x^2+x+1 写入最高字节(v[0] MSB 端), + // 掩码替代 if lsb,执行路径完全相同 + let reduce_mask = 0u64.wrapping_sub(lsb); + v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask; } } - z + + let mut out = [0u8; 16]; + out[0..8].copy_from_slice(&z[0].to_be_bytes()); + out[8..16].copy_from_slice(&z[1].to_be_bytes()); + out } /// GHASH 认证函数(NIST SP 800-38D §6.4)