Also known as: LoRa whitening, data whitening, LoRa PN sequence
LoRa whitening XORs the payload with a pseudo-random (PN) sequence so the transmitted bits
carry no long runs or repeating structure — a step that keeps a LoRa frame’s
spectrum flat and its symbol stream well-conditioned for timing recovery.1 The sequence
comes from an 8-bit linear-feedback shift register with polynomial 0xB8 and seed 0xFF.
Because whitening is a bit-for-bit XOR, it is its own inverse: the same routine whitens on
transmit and de-whitens on receive.2
How it works
For each payload byte, GopherTrunk XORs it with the current LFSR state, then clocks the register eight times — one shift per bit of the byte just consumed. Each clock shifts the register right and, when the bit shifted out was set, XORs the feedback polynomial back in:
const whitenSeed, whitenPoly = 0xFF, 0xB8
func Whiten(data []byte) {
lfsr := uint8(whitenSeed)
for i := range data {
data[i] ^= lfsr
for b := 0; b < 8; b++ {
lsb := lfsr & 1
lfsr >>= 1
if lsb != 0 {
lfsr ^= whitenPoly
}
}
}
}
0xB8 corresponds to x⁸ + x⁶ + x⁵ + x⁴ + 1 in the reflected convention this shift direction
uses. LoRa is a proprietary Semtech modulation, so the exact whitening sequence is
reverse-engineered; GopherTrunk’s package notes that byte-for-byte agreement with Semtech
silicon is a calibration step gated behind a captured golden packet. The sequence it produces is
deterministic and self-consistent, which is what the package’s own modulator/demodulator
round-trip requires.
Whitening versus scrambling
Whitening and scrambling are the same operation — XOR against a PN sequence — put to two different ends, and the distinction is worth keeping straight:
| Whitening | Scrambling (as a cipher) | |
|---|---|---|
| Goal | de-correlate bits (flat spectrum, no long runs) | obscure content |
| Sequence | fixed, public polynomial + seed | secret / keyed |
| Reversibility | trivial — same public LFSR | needs the key |
| Security | none | the point |
LoRa whitening is purely the first kind: a fixed, public LFSR whose only job is to break up structure in the data so the physical layer behaves well. It provides no confidentiality — that is the job of the LoRaWAN AES layer, not the PHY.
The payload CRC
Integrity is checked separately, after de-whitening, by a CRC-16 with polynomial 0x1021
and initial value 0x0000 computed over the payload bytes (excluding the trailing CRC field).
This is the XMODEM-style member of the CRC-16/CCITT family — same
polynomial as many of GopherTrunk’s other framers, differing only in its init and reflection
settings. As with the whitening sequence, byte-order and seed compatibility with Semtech silicon
is validated against a golden packet rather than assumed.