Notes

One byte, then eight shifts

· algorithms

The register still holds rem_G of x^r times the prefix. I XOR the next byte into the top eight bits, then apply T_G eight times.

Last time, after kk message bits, the register held

Ck=rem⁡G(xrMk),C_{k}=\operatorname{rem}_{G}(x^{r}M_{k}),

and one new bit entered at xr−1x^{r-1}:

Ck+1=TG(Ck+bk+1xr−1),TG(P)=rem⁡G(xP).C_{k+1}=T_{G}(C_{k}+b_{k+1}x^{r-1}), \qquad T_{G}(P)=\operatorname{rem}_{G}(x P).

TGT_{G} is defined for polynomials of degree less than rr. The input now comes in bytes. I still want this CkC_{k}. XOR the next byte into the top eight bits, then apply TGT_{G} eight times, and the register should be eight bits further on.

The prefix grows by a byte

The bits are still b1,b2,…b_{1}, b_{2}, \ldots, high bit first. Byte jj, for j=0,1,2,…j=0,1,2,\ldots, is bits b8j+1b_{8j+1} through b8j+8b_{8j+8}:

Bj(x)=∑i=18b8j+i x8−i.B_{j}(x)=\sum_{i=1}^{8} b_{8j+i}\, x^{8-i}.

The first of those bits sits on x7x^{7}. The byte 1011011010110110 is x7+x5+x4+x2+xx^{7}+x^{5}+x^{4}+x^{2}+x, which is B0B_{0} when it is the first byte.

One bit grew the prefix by Mk+1=xMk+bk+1M_{k+1}=x M_{k}+b_{k+1}. Eight bits, from a prefix whose length is a multiple of eight, are that same growth:

M8(j+1)(x)=x8M8j(x)+Bj(x).\boxed{M_{8(j+1)}(x)=x^{8} M_{8j}(x)+B_{j}(x).}

The byte meets the top of the register

C8(j+1)C_{8(j+1)} is the same remainder on the longer prefix:

C8(j+1)=rem⁡G(xrM8(j+1))=rem⁡G(xr(x8M8j+Bj))=rem⁡G(x8(xrM8j)+xrBj).\begin{aligned} C_{8(j+1)} &= \operatorname{rem}_{G}(x^{r} M_{8(j+1)}) \\ &= \operatorname{rem}_{G}\bigl(x^{r}(x^{8} M_{8j}+B_{j})\bigr) \\ &= \operatorname{rem}_{G}\bigl(x^{8}(x^{r} M_{8j})+x^{r} B_{j}\bigr). \end{aligned}

xrM8jx^{r} M_{8j} and C8jC_{8j} differ by a multiple of GG, so x8x^{8} times either of them has the same remainder. Adding xrBjx^{r} B_{j} keeps that:

C8(j+1)=rem⁡G(x8C8j+xrBj).C_{8(j+1)}=\operatorname{rem}_{G}(x^{8} C_{8j}+x^{r} B_{j}).

A byte fits in the register when r≥8r\ge 8. Then xrBj=x8(xr−8Bj)x^{r} B_{j}=x^{8}(x^{r-8} B_{j}), and both terms carry x8x^{8}:

C8(j+1)=rem⁡G(x8(C8j+xr−8Bj)).\boxed{C_{8(j+1)} =\operatorname{rem}_{G}\bigl(x^{8}(C_{8j}+x^{r-8} B_{j})\bigr).}

deg⁡Bj≤7\deg B_{j}\le 7, so xr−8Bjx^{r-8} B_{j} uses degrees r−8r-8 through r−1r-1: the top eight coefficients. Adding it to C8jC_{8j} is the XOR of the byte into those bits.

Eight times TGT_{G}

Last time a single step was

TG(P)=(xP mod xr)+[xr−1]P⋅g(x).T_{G}(P)=(x P\bmod x^{r})+[x^{r-1}]P\cdot g(x).

msb reads [xr−1][x^{r-1}] of the register. (crc_register << 1) & register_mask is xP mod xrx P\bmod x^{r}. When that coefficient is 11, XOR gg, which is truncated_generator. On C8j+xr−8BjC_{8j}+x^{r-8} B_{j} the byte is already in the polynomial, so these eight steps only multiply by xx and reduce.

Applied mm times, TGT_{G} multiplies by xmx^{m} and reduces:

TGm(S)=rem⁡G(xmS).T_{G}^{m}(S)=\operatorname{rem}_{G}(x^{m} S).

The case m=1m=1 is the definition of TGT_{G}. If the formula holds for mm, then TGm(S)T_{G}^{m}(S) and xmSx^{m} S differ by a multiple of GG, so

TGm+1(S)=TG(TGm(S))=rem⁡G(x⋅TGm(S))=rem⁡G(xm+1S).\begin{aligned} T_{G}^{m+1}(S) &= T_{G}(T_{G}^{m}(S)) \\ &= \operatorname{rem}_{G}(x\cdot T_{G}^{m}(S)) \\ &= \operatorname{rem}_{G}(x^{m+1} S). \end{aligned}

Eight applications give the remainder above:

C8(j+1)=TG8(C8j+xr−8Bj).\boxed{C_{8(j+1)}=T_{G}^{8}(C_{8j}+x^{r-8} B_{j}).}

Byte boundaries

The message is a sequence of bytes. At each multiple of eight bits the register is C8j=rem⁡G(xrM8j)C_{8j}=\operatorname{rem}_{G}(x^{r} M_{8j}).

The empty prefix is M0=0M_{0}=0, and the register starts at C0=0C_{0}=0. Byte jj replaces C8jC_{8j} by TG8(C8j+xr−8Bj)T_{G}^{8}(C_{8j}+x^{r-8} B_{j}), which is C8(j+1)C_{8(j+1)}.

During those eight steps the register began as

C~j=C8j+xr−8Bj,\widetilde{C}_{j}=C_{8j}+x^{r-8} B_{j},

so C8(j+1)=TG8(C~j)C_{8(j+1)}=T_{G}^{8}(\widetilde{C}_{j}). C~j\widetilde{C}_{j} already holds bits a one-bit loop would still be waiting to read. It is the register between byte boundaries. Once the byte has been fully shifted, the register is C8(j+1)C_{8(j+1)} again.

After the last byte, MnM_{n} is the whole message and the register is the CRC,

Cn=rem⁡G(xrM).C_{n}=\operatorname{rem}_{G}(x^{r} M).

That last byte enters as C~j\widetilde{C}_{j} for the last jj. Eight applications of TGT_{G} turn it into CnC_{n}.

One byte, two registers

Take G(x)=x8+x2+x+1G(x)=x^{8}+x^{2}+x+1, so r=8r=8 and g=00000111g=00000111. The message is the single byte 1011011010110110, and C0=00000000C_{0}=00000000.

Here r−8=0r-8=0, so the top eight bits are the whole register and C~0=B0=10110110\widetilde{C}_{0}=B_{0}=10110110.

The left column feeds that byte through last time’s update, one bit at a time: each bit is TG(Ck+bk+1xr−1)T_{G}(C_{k}+b_{k+1}x^{r-1}). The first bit is 11, the top of 0000000000000000 is 00, the feedback is 11, and XOR with gg after the shift leaves 0000011100000111.

The right column XORs the byte in before any application of TGT_{G}. The first one reads a top coefficient of 11, so xP mod x8x P\bmod x^{8} is XOR’d with gg:

01101100
^00000111
---------
01101011
StepsBit by bitByte XOR’d in first
00000000010110110
10000011101101011
20000111011010110
30001101110101011
40011000101010001
50110001010100010
61100001101000011
71000011010000110
80000101100001011

For most of those steps the two registers hold different bits. After eight steps both hold 0000101100001011. That is C8C_{8}, the CRC of this byte.

The program

The loop is the boxed update. The assert is r≥8r\ge 8, the same bound as xr−8x^{r-8}. top_byte_shift is that exponent.

// One byte at a time. The byte is mixed into the top 8 bits, then the
// register shifts once per bit. `width` must be at least 8.
[[nodiscard]] uint32_t byte_crc(const std::vector<uint8_t>& bytes,
uint32_t generator) {
constexpr int kBitsPerByte = 8;
const int width = polynomial_degree(generator);
assert(width >= kBitsPerByte);
uint32_t crc_register = 0;
const uint32_t register_mask = (1U << width) - 1;
const uint32_t truncated_generator = generator & register_mask;
const auto top_byte_shift = static_cast<unsigned>(width - kBitsPerByte);
for (const uint8_t byte : bytes) {
crc_register ^= static_cast<uint32_t>(byte) << top_byte_shift;
for (int bit_index = 0; bit_index < kBitsPerByte; ++bit_index) {
const uint32_t msb = (crc_register >> (width - 1)) & 1U;
crc_register = (crc_register << 1) & register_mask;
if (msb == 1) {
crc_register ^= truncated_generator;
}
}
}
return crc_register;
}
In the formulaIn the program
BjB_{j}byte
xr−8Bjx^{r-8} B_{j}byte << top_byte_shift
C~j\widetilde{C}_{j}the XOR into crc_register
[xr−1][x^{r-1}] of the registermsb
xP mod xrx P\bmod x^{r}(crc_register << 1) & register_mask
g(x)g(x)truncated_generator
TGT_{G}one pass of the inner loop
TG8(C~j)T_{G}^{8}(\widetilde{C}_{j})eight passes
CnC_{n}return crc_register