ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ghash.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file ghash.h
6 * @brief GHASH (the GF(2^128) universal hash under AES-GCM, NIST SP 800-38D sec 6.3), 4-bit table.
7 *
8 * Shared by pc_aesgcm (AES-256-GCM) and aes128gcm (AES-128-GCM, also DTLS 1.3). The textbook GHASH
9 * is a 128-iteration bitwise GF(2^128) multiply per 16-byte block (~3,700 cyc/byte on an ESP32-S3),
10 * which made AES-GCM ~350x slower than raw AES-CTR and is the throughput floor of every AEAD record
11 * layer (measured, docs/FEATURE_PERFORMANCE.md). There is no hardware GF-multiply on the S3 (unlike
12 * the RSA/MPI MODMULT that accelerates curve25519), so the lever is algorithmic: the standard 4-bit
13 * table method (Shoup) - build a 16-entry table of i*H once per key, then fold four bits of the
14 * accumulator per step.
15 *
16 * The 128-bit state is held as FOUR uint32 words (z[0] most significant), NOT two uint64: on the
17 * 32-bit Xtensa LX7 a uint64 `>>4`/`<<60` compiles to a libgcc call (__lshrdi3/__ashldi3), which
18 * dominated an early uint64 version (~8,100 cyc/block); the 32-bit-word shifts are native register
19 * ops. Byte-exact versus the bitwise reference (the NIST/McGrew GCM KATs plus a direct fuzz
20 * cross-check, test_ssh_crypto). Header-only, zero heap, deterministic; the 256-byte table lives in
21 * the per-direction context (SSH, built once at key install) or on the stack (QUIC/DTLS, per packet).
22 */
23
24#ifndef PROTOCORE_GHASH_H
25#define PROTOCORE_GHASH_H
26
28#include <stddef.h>
29#include <stdint.h>
30
31/** @brief 4-bit GHASH table for a fixed subkey H = E(K, 0^128): M[i] = i*H as four big-endian
32 * uint32 words (M[i][0] most significant). 256 bytes. */
34{
35 uint32_t M[16][4];
36};
37
38/** @brief Build the 4-bit multiplication table from the 16-byte subkey @p h. Call once per key. */
39inline void ghash_key_init(GhashKey *t, const uint8_t h[16])
40{
41 // M[8] = H; M[4]=H/x, M[2]=H/x^2, M[1]=H/x^3 (one GF right-shift each, reducing by R=0xe1<<120).
42 uint32_t z0 = pc_rd32be(h);
43 uint32_t z1 = pc_rd32be(h + 4);
44 uint32_t z2 = pc_rd32be(h + 8);
45 uint32_t z3 = pc_rd32be(h + 12);
46 t->M[8][0] = z0;
47 t->M[8][1] = z1;
48 t->M[8][2] = z2;
49 t->M[8][3] = z3;
50 t->M[0][0] = 0;
51 t->M[0][1] = 0;
52 t->M[0][2] = 0;
53 t->M[0][3] = 0;
54 for (int i = 4; i > 0; i >>= 1)
55 {
56 uint32_t lsb = z3 & 1u;
57 z3 = (z3 >> 1) | (z2 << 31);
58 z2 = (z2 >> 1) | (z1 << 31);
59 z1 = (z1 >> 1) | (z0 << 31);
60 z0 = (z0 >> 1) ^ (lsb ? 0xe1000000u : 0u);
61 t->M[i][0] = z0;
62 t->M[i][1] = z1;
63 t->M[i][2] = z2;
64 t->M[i][3] = z3;
65 }
66 // Composite entries: (i + j) * H = i*H XOR j*H (i a power of two, 0 < j < i).
67 for (int i = 2; i < 16; i <<= 1)
68 {
69 for (int j = 1; j < i; j++)
70 {
71 t->M[i + j][0] = t->M[i][0] ^ t->M[j][0];
72 t->M[i + j][1] = t->M[i][1] ^ t->M[j][1];
73 t->M[i + j][2] = t->M[i][2] ^ t->M[j][2];
74 t->M[i + j][3] = t->M[i][3] ^ t->M[j][3];
75 }
76 }
77}
78
79/** @brief acc = acc * H in GF(2^128) with the GCM reduction, using the precomputed table @p t. */
80inline void ghash_mul(const GhashKey *t, uint8_t acc[16])
81{
82 // Reduction contribution (into the top 16 bits of word 0) of the low nibble shifted out per step.
83 static const uint16_t LAST4[16] = {0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
84 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0};
85 uint8_t idx = acc[15] & 0x0f;
86 uint32_t z0 = t->M[idx][0];
87 uint32_t z1 = t->M[idx][1];
88 uint32_t z2 = t->M[idx][2];
89 uint32_t z3 = t->M[idx][3];
90 for (int i = 15; i >= 0; i--)
91 {
92 uint8_t lo = acc[i] & 0x0f;
93 uint8_t hi = (acc[i] >> 4) & 0x0f;
94 if (i != 15)
95 {
96 uint32_t rem = z3 & 0x0f;
97 z3 = (z3 >> 4) | (z2 << 28);
98 z2 = (z2 >> 4) | (z1 << 28);
99 z1 = (z1 >> 4) | (z0 << 28);
100 z0 = (z0 >> 4) ^ ((uint32_t)LAST4[rem] << 16);
101 z0 ^= t->M[lo][0];
102 z1 ^= t->M[lo][1];
103 z2 ^= t->M[lo][2];
104 z3 ^= t->M[lo][3];
105 }
106 uint32_t rem = z3 & 0x0f;
107 z3 = (z3 >> 4) | (z2 << 28);
108 z2 = (z2 >> 4) | (z1 << 28);
109 z1 = (z1 >> 4) | (z0 << 28);
110 z0 = (z0 >> 4) ^ ((uint32_t)LAST4[rem] << 16);
111 z0 ^= t->M[hi][0];
112 z1 ^= t->M[hi][1];
113 z2 ^= t->M[hi][2];
114 z3 ^= t->M[hi][3];
115 }
116 pc_wr32be(acc, z0);
117 pc_wr32be(acc + 4, z1);
118 pc_wr32be(acc + 8, z2);
119 pc_wr32be(acc + 12, z3);
120}
121
122/** @brief Fold @p len bytes of @p data into @p acc: acc = (acc XOR block) * H per 16 bytes, a final
123 * short block MSB-zero-padded. */
124inline void ghash_update(const GhashKey *t, uint8_t acc[16], const uint8_t *data, size_t len)
125{
126 size_t off = 0;
127 while (off < len)
128 {
129 size_t take = len - off;
130 if (take > 16)
131 {
132 take = 16;
133 }
134 for (size_t i = 0; i < take; i++)
135 {
136 acc[i] ^= data[off + i];
137 }
138 ghash_mul(t, acc);
139 off += take;
140 }
141}
142
143#endif // PROTOCORE_GHASH_H
Fixed-width integer serializers into a raw uint8_t* buffer - one source of truth.
uint32_t pc_rd32be(const uint8_t *p)
Read a big-endian u32 at p.
Definition endian.h:119
size_t pc_wr32be(uint8_t *p, uint32_t v)
Write v big-endian at p.
Definition endian.h:93
void ghash_key_init(GhashKey *t, const uint8_t h[16])
Build the 4-bit multiplication table from the 16-byte subkey h. Call once per key.
Definition ghash.h:39
void ghash_update(const GhashKey *t, uint8_t acc[16], const uint8_t *data, size_t len)
Fold len bytes of data into acc: acc = (acc XOR block) * H per 16 bytes, a final short block MSB-zero...
Definition ghash.h:124
void ghash_mul(const GhashKey *t, uint8_t acc[16])
acc = acc * H in GF(2^128) with the GCM reduction, using the precomputed table t.
Definition ghash.h:80
4-bit GHASH table for a fixed subkey H = E(K, 0^128): M[i] = i*H as four big-endian uint32 words (M[i...
Definition ghash.h:34
uint32_t M[16][4]
Definition ghash.h:35