Field Guide · algorithm

Also known as: TETRA scrambling sequence, TETRA LFSR scrambler

The TETRA scrambler is the linear-feedback shift register whose pseudo-random output is XORed onto the coded bits of every TETRA logical channel except the initial BSCH.1 It is an additive scrambler: the transmitter turns type-4 channel bits into type-5 on-air bits by XOR with a bit sequence p(k), and the receiver recovers the type-4 bits by XOR with the identical sequence. Crucially the LFSR is seeded by the cell’s 30-bit extended colour code, so a receiver must first learn that identity — from the unscrambled synchronisation channel — before any other channel becomes readable.2

32-tap LFSR · mask 0x82608EDB taps → XOR → new bit p(k) shifted in at bit 0 coded bit on-air seed: extended colour code e(1)..e(30) in state bits, fill 0xC0000000
The LFSR feeds tapped state bits through XOR to generate p(k); each output bit is XORed with a coded bit to scramble it. The register is seeded from the extended colour code, so descrambling requires the cell identity.

The generator

The connection polynomial (§8.2.5.2 eq. 8.40) has 14 feedback taps, which GopherTrunk packs into a 32-bit mask so each step is a masked-AND plus a parity:

// internal/radio/framing/scramble_tetra.go — ETSI EN 300 392-2 §8.2.5.
// Taps at i = 1,2,4,5,7,8,10,11,12,16,22,23,26,32.
const scrambleTetraTapMask uint32 = 0x82608EDB

func (s *ScramblerTetra) Next() byte {
    v := s.state & scrambleTetraTapMask
    bit := byte(popcount32(v) & 1)     // new p(k) = XOR of tapped bits
    s.state = (s.state << 1) | uint32(bit) // shift left, insert at bit 0
    return bit
}

The register is initialised (§8.2.5.2 eq. 8.42) with the extended colour code in its low 30 state bits and a constant 0xC0000000 fill in the top two (p(−30) = p(−31) = 1); output begins at p(1). For the BSCH and BSCH-Q the colour-code bits are all zero, so a cold receiver can always descramble the synchronisation channel with a zero seed.

Two invisible bugs

The scrambler is a cautionary tale in why round-trip tests are not enough (issue #925). Two independent bugs each broke every real, externally-scrambled burst while passing every synthetic test. First, the LFSR originally shifted the wrong direction — right, inserting at bit 31 — which reversed the register relative to the tap convention and diverged from the spec sequence starting at p(2). Second, the 30-bit seed is carried MSB-first (e(1) in the high bit) but the LFSR wants e(i+1) in state bit i, so the low 30 bits must be bit-reversed on the way in. Both faults are self-consistent: because scrambling and descrambling share the same generator, a wrong-but-matching sequence cancels perfectly in any encode-then-decode round-trip, and the zero-seeded BSCH is a fixed point of bit-reversal. The only symptom was on real air — the receiver would lock off the unscrambled synchronisation training sequence, yet no BNCH or SCH message would ever decode. This is the general TETRA trap: validate the scrambled path against a real capture, not just a self-round-trip.

Relevance to SDR

ScrambleTetra / DescrambleTetra are the XOR-symmetric entry points the whole channel stack calls after colour-code learning. The traffic extractor descrambles each raw type-5 block before the TCH/S decoder sees it, and the AACH, BNCH, and SCH decoders all descramble first. A soft-decision variant, DescrambleTetraSoft, applies the same per-bit sign flips to LLRs so soft-decision decoding works end to end. Getting the tap mask, shift direction, and seed orientation all exactly right is the difference between a control channel that locks and decodes and one that only locks.

Sources

  1. Scrambler — Wikipedia, on additive (synchronous) scrambling by XOR with a pseudo-random sequence. 

  2. Linear-feedback shift register — Wikipedia, on the register and feedback-polynomial construction the scrambler uses. 

See also