
Rotation Range-Coding (RRC) Steganography — an efficient and provably secure linguistic steganographic method that embeds secret messages into natural-language text generated by large language models.
Paper: Efficient Provably Secure Linguistic Steganography via Range Coding
| Step | Description |
|---|---|
| Embed (Algorithm 3) | Convert the secret message to a decimal value and iteratively rotate it within a shrinking interval guided by the LM's probability distribution and a PRNG-generated offset. Each rotation step produces one token. |
| Extract (Algorithm 4) | Re-run the LM on the stegotext to recover interval bounds, then reverse-rotate back to the original decimal value and binarize it. |
The rotation mechanism solves two issues of vanilla range-coding steganography:
.
├── RRC_embed.py # Embedding (Algorithm 3)
├── RRC_extract.py # Extraction (Algorithm 4)
├── test_roundtrip.py # Self-contained end-to-end verification script
├── requirements.txt # Python dependencies
└── README.md
Install dependencies:
pip install -r requirements.txt
The round-trip test uses meta-llama/Llama-2-7b-hf and runs on CPU/CUDA:
python test_roundtrip.py
Expected output:
>>> Step 3: Verification
Original: 10110011001010111010011100101011...
Extracted: 10110011001010111010011100101011...
✅ SUCCESS — extracted message matches perfectly!
Prepare a prompts file 0.Prompts.tsv with columns idx and text, then run:
python RRC_embed.py \
--language_model meta-llama/Llama-3.1-8B \
--bit_length 128 \
--key 42 \
--top_k -1
python RRC_extract.py \
--language_model meta-llama/Llama-3.1-8B \
--bit_length 128 \
--key 42 \
--input_file 1.RC_decimal_Llama-3.1-8B_bit128.tsv
⚠️ Important: The sender and receiver must use the same language model, key, bit length, and top-k setting for correct extraction.
RRC_embed.pyRRC_extract.pyPython's Decimal type uses truncated division for the % operator, which can return negative remainders (e.g. Decimal('-19672') % Decimal('65536') → -19672 instead of 45864). This implementation uses a decimal_mod helper that guarantees the result is always in [0, m):
def decimal_mod(a, m):
return a - m * (a / m).to_integral_value(rounding=ROUND_FLOOR)
⚠️ For reliable secret extraction, some operations (sort, cumsum) are offloaded to CPU at float64 precision to avoid CUDA non-determinism. This trades a amount of speed for guaranteed encode–decode consistency.
| Argument | Default | Description |
|---|
--language_model | meta-llama/Llama-3.1-8B | HuggingFace model identifier |
--bit_length | 128 | Secret message length in bits |
--top_k | -1 | Top-k truncation; -1 = full vocabulary |
--key | 42 | Symmetric key K (PRNG seed) |
--part / --part_max | 0 / 2 | For parallel execution on large prompt sets |
| Argument | Default | Description |
|---|
--language_model | meta-llama/Llama-3.1-8B | Must match the embedding model |
--bit_length | 128 | Must match the embedding setting |
--top_k | -1 | Must match the embedding setting |
--key | 42 | Must match the embedding key |
--input_file | (required) | Path to TSV from the encoder |