
Steganografia linguistica provabilmente sicura che incorpora messaggi segreti in testo generato da LLM tramite rotation range-coding. Include script per embed, extract e verifica round-trip.
Rotation Range-Coding (RRC) Steganography — un metodo steganografico linguistico efficiente e dimostrabilmente sicuro che incorpora messaggi segreti in testo in linguaggio naturale generato da modelli linguistici di grandi dimensioni.
Paper: Efficient Provably Secure Linguistic Steganography via Range Coding (ACL 2026)
| Passo | Descrizione |
|---|---|
| Embed (Algoritmo 3) | Converte il messaggio segreto in un valore decimale e lo ruota iterativamente all'interno di un intervallo che si restringe, guidato dalla distribuzione di probabilità del LM e da un offset generato dal PRNG. Ogni passo di rotazione produce un token. |
| Extract (Algoritmo 4) | Esegue nuovamente il LM sullo stegotesto per recuperare i limiti dell'intervallo, quindi esegue la rotazione inversa per tornare al valore decimale originale e lo binarizza. |
Il meccanismo di rotazione risolve due problemi della steganografia range-coding vanilla:
.
├── rrc_core.py # Algorithms 3 and 4, shared by every entry point
├── RRC_embed.py # Embedding CLI
├── RRC_extract.py # Extraction CLI (reports recovery accuracy)
├── test_roundtrip.py # Round-trip test across several message lengths
├── fastest_embed.py # Embedding-speed benchmark
├── 0.Prompts.tsv # 1000 prompts (first ~10 words of C4 sequences)
└── requirements.txt
L'encoder e il decoder condividono rrc_core.py invece di mantenere copie
separate del loop. I due lati devono concordare bit per bit, e una divergenza
tra due copie mantenute indipendentemente si manifesta solo come un round trip
fallito, mai a un'ispezione.
pip install -r requirements.txt
python test_roundtrip.py # GPT-2, 32/128/512 bits
python test_roundtrip.py --model meta-llama/Llama-2-7b-hf
python RRC_embed.py \
--language_model meta-llama/Llama-2-7b-hf \
--bit_length 128 --key 42 --top_k -1
python RRC_extract.py \
--language_model meta-llama/Llama-2-7b-hf \
--bit_length 128 --key 42 \
--input_file 1.RC_decimal_Llama-2-7b-hf_bit128.tsv
L'encoder registra il segreto che ha incorporato e gli id dei token che lo hanno trasportato, quindi l'estrazione riporta un tasso di recupero. Vengono valutati due modelli di ricevitore:
ids — al ricevitore viene data la sequenza di token. Questo isola lo schema
di codifica; qualsiasi valore sotto il 100% è un difetto numerico o logico.text — al ricevitore viene data la stringa detokenizzata e la tokenizza di
nuovo, come richiede un canale testuale. La ri-tokenizzazione non riproduce
sempre la segmentazione del mittente, quindi questo è un limite inferiore; la
differenza tra le due misure quantifica l'ambiguità di tokenizzazione
piuttosto che lo schema di codifica. Su Llama-2-7b a 128 bit: 100% per ids,
~91% per text.Importante: mittente e ricevitore devono condividere il modello linguistico, la chiave, la lunghezza in bit e l'impostazione top-k, e devono anche eseguire le stesse versioni di hardware e librerie. Vedi Requisiti numerici.
Condivisi da RRC_embed.py e RRC_extract.py:
| Argomento | Default | Descrizione |
|---|---|---|
--language_model | meta-llama/Llama-3.1-8B | HuggingFace model id |
--bit_length | 128 | Secret message length in bits |
--top_k | -1 | Top-k truncation; -1 = full vocabulary |
--key | 42 | Symmetric key K (PRNG seed) |
--precision_margin | 40 | Decimal digits beyond those 2**bit_length needs |
--max_ctx_len | 3900 | KV-cache window |
--device | auto | cuda or cpu |
RRC_embed.py accetta anche --prompts, --output, --limit, --seed,
--max_tokens, --resume, --no_verify, e --part / --part_max per lo
sharding di un insieme di prompt tra processi (--part_max 3 con --part 0,1,2,
uno per GPU).
RRC_extract.py accetta anche --input_file, --mode {ids,text,both},
--limit e --verbose.
RRC restringe un intervallo di larghezza 2**l fino a una larghezza ~1, quindi
il valore recuperato è sensibile all'errore relativo per un fattore di 2**l:
a 128 bit una perturbazione relativa di 1e-16 in qualsiasi punto della catena
sposta il risultato di ~1e22 rispetto a una tolleranza di 0.5. L'estrazione non
degrada gradualmente — o restituisce il messaggio o restituisce bit non
correlati. Misurato su Llama-2-7b:
| Impostazione | Perturbazione relativa delle probabilità | Estrazione |
|---|---|---|
| Ripetere la stessa decodifica batch-1 | 0 (bitwise identical) | funziona |
| La stessa riga a batch 2 / 8 / 16 | ~1e-2 | fallisce |
| Left-padding del prompt | ~1e-2 | fallisce |
| Un singolo passaggio teacher-forced invece della decodifica incrementale | ~4e-3 | fallisce |
Due conseguenze plasmano l'implementazione. Il batching non è disponibile, quindi le sequenze vengono elaborate una alla volta. E il ricevitore ripete la decodifica incrementale del mittente passo per passo invece di valutare lo stegotesto in un singolo forward pass, anche se possiede in anticipo ogni token. Entrambe le scorciatoie misurano ~9x più veloce e nessuna delle due preserva le probabilità.
Tutto ciò che segue i logits è in float64 e produce limiti bitwise identici su CPU e GPU, quindi il forward pass è l'unico passo che lega uno stegotesto al suo hardware.
La precisione è derivata dalla lunghezza del messaggio come
ceil(bit_length * log10(2)) + margin e impostata prima che qualsiasi altra
cosa venga eseguita. Scala con il messaggio: 2**256 richiede 78 cifre e
2**8192 ne richiede 2466.
Una precisione inferiore a quella soglia non fallisce in modo pulito. Le cifre di ordine inferiore del valore segreto vengono arrotondate via, l'intervallo collassa entro pochi token e il test di terminazione scatta in anticipo, quindi un'esecuzione può completarsi e riportare cifre aritmeticamente irraggiungibili. Mantenere la precisione a 50 cifre indipendentemente dalla lunghezza del messaggio dà:
| bits | utilisation at 50 digits | samples completing | with derived precision |
|---|---|---|---|
| 256 | 157.05% | 8/20 | 100.17% |
| 512 | 299.67% | 9/20 | 102.48% |
| 1024 | 660.91% | 7/10 | 102.58% |
| 2048 | 1046.40% | 1/5 | 98.18% |
| 4096 | 2483.92% | 3/3, no error raised | 100.87% |
| 8192 | 4766.49% | 1/2 | 99.07% |
--precision_margin regola il margine al di sopra del minimo derivato; il
minimo stesso è imposto.
L'Algoritmo 3 si ferma quando (L + R) / 2 - d_s cade in (-0.5, 0.5]. Quel
test può scattare mentre l'intervallo è più stretto di 1, e la finestra
[mid - 0.5, mid + 0.5) è quindi più ampia dell'intervallo che la contiene. La
rotazione inversa è un'isometria modulo la larghezza dell'intervallo, quindi una
finestra che scavalca il taglio lascia il ricevitore a un intervallo intero di
distanza da d_s.
Il mittente quindi ripete la rotazione inversa del ricevitore prima di
impegnarsi a fermarsi ed emette un altro token se la ripetizione non restituisce
il segreto. La ripetizione è puro lavoro Decimal (~0.05 ms contro ~20 ms per un
forward pass), quindi la guardia non costa nulla di misurabile. --no_verify la
disabilita.
Misurato su Llama-2-7b: