
Esteganografía lingüística demostrablemente segura que incrusta mensajes secretos en texto generado por LLM mediante codificación de rango rotativa. Incluye scripts de incrustación, extracción y verificación de ida y vuelta.
Rotation Range-Coding (RRC) Steganography — un método esteganográfico lingüístico eficiente y con seguridad demostrable que incrusta mensajes secretos en texto en lenguaje natural generado por modelos de lenguaje grandes.
Artículo: Efficient Provably Secure Linguistic Steganography via Range Coding (ACL 2026)
| Paso | Descripción |
|---|---|
| Incrustar (Algoritmo 3) | Convierte el mensaje secreto a un valor decimal y lo rota iterativamente dentro de un intervalo que se reduce, guiado por la distribución de probabilidad del LM y un desplazamiento generado por PRNG. Cada paso de rotación produce un token. |
| Extraer (Algoritmo 4) | Vuelve a ejecutar el LM sobre el estegotexto para recuperar los límites del intervalo, luego rota inversamente de vuelta al valor decimal original y lo binariza. |
El mecanismo de rotación resuelve dos problemas de la esteganografía de codificación por rango convencional:
.
├── 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
El codificador y el decodificador comparten rrc_core.py en lugar de mantener copias separadas del bucle. Ambos lados deben coincidir bit a bit, y una divergencia entre dos copias mantenidas de forma independiente solo se manifiesta como un round trip fallido, nunca en la inspección.
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
El codificador registra el secreto que incrustó y los ids de token que lo transportaron, por lo que la extracción reporta una tasa de recuperación. Se puntúan dos modelos receptores:
ids — al receptor se le entrega la secuencia de tokens. Esto aísla el esquema de codificación; cualquier valor por debajo del 100% es un fallo numérico o lógico.text — al receptor se le entrega la cadena destokenizada y la vuelve a tokenizar, como requiere un canal de texto. La retokenización no siempre reproduce la segmentación del emisor, por lo que esto es una cota inferior; la diferencia entre ambas medidas refleja la ambigüedad de tokenización, no el esquema de codificación. En Llama-2-7b a 128 bits: 100% para ids, ~91% para text.Importante: el emisor y el receptor deben compartir el modelo de lenguaje, la clave, la longitud de bits y la configuración de top-k, y también deben ejecutar el mismo hardware y las mismas versiones de librerías. Véase Requisitos numéricos.
Compartidos por RRC_embed.py y RRC_extract.py:
| Argumento | Predeterminado | Descripción |
|---|---|---|
--language_model | meta-llama/Llama-3.1-8B | Id del modelo de HuggingFace |
--bit_length | 128 | Longitud del mensaje secreto en bits |
--top_k | -1 | Truncamiento top-k; -1 = vocabulario completo |
--key | 42 | Clave simétrica K (semilla del PRNG) |
--precision_margin | 40 | Dígitos decimales más allá de los que necesita 2**bit_length |
--max_ctx_len | 3900 | Ventana de caché KV |
--device | auto | cuda o cpu |
RRC_embed.py también acepta --prompts, --output, --limit, --seed, --max_tokens, --resume, --no_verify, y --part / --part_max para fragmentar un conjunto de prompts entre procesos (--part_max 3 con --part 0,1,2, uno por GPU).
RRC_extract.py también acepta --input_file, --mode {ids,text,both}, --limit y --verbose.
RRC reduce un intervalo de ancho 2**l hasta un ancho ~1, por lo que el valor recuperado es sensible al error relativo por un factor de 2**l: a 128 bits, una perturbación relativa de 1e-16 en cualquier punto de la cadena desplaza el resultado en ~1e22 frente a una tolerancia de 0.5. La extracción no se degrada gradualmente — o devuelve el mensaje o devuelve bits no relacionados. Medido en Llama-2-7b:
| Configuración | Perturbación relativa de las probabilidades | Extracción |
|---|---|---|
| Repetir la misma decodificación batch-1 | 0 (idéntica bit a bit) | funciona |
| La misma fila en batch 2 / 8 / 16 | ~1e-2 | falla |
| Relleno por la izquierda del prompt | ~1e-2 | falla |
| Una pasada con teacher forcing en lugar de decodificación incremental | ~4e-3 | falla |
Dos consecuencias dan forma a la implementación. El batching no está disponible, por lo que las secuencias se procesan de una en una. Y el receptor reproduce la decodificación incremental del emisor paso a paso en lugar de puntuar el estegotexto en una única pasada forward, aunque disponga de todos los tokens de antemano. Ambos atajos miden ~9x más rápido y ninguno preserva las probabilidades.
Todo lo posterior a los logits es float64 y produce límites idénticos bit a bit en CPU y GPU, por lo que la pasada forward es el único paso que vincula un estegotexto a su hardware.
La precisión se deriva de la longitud del mensaje como ceil(bit_length * log10(2)) + margin y se establece antes de que se ejecute cualquier otra cosa. Escala con el mensaje: 2**256 necesita 78 dígitos y 2**8192 necesita 2466.
Una precisión por debajo de ese umbral no falla de forma limpia. Los dígitos de orden inferior del valor secreto se redondean y desaparecen, el intervalo colapsa en unos pocos tokens y la prueba de terminación se dispara antes de tiempo, por lo que una ejecución puede completarse y reportar cifras que son aritméticamente inalcanzables. Mantener la precisión en 50 dígitos independientemente de la longitud del mensaje da:
| bits | utilización a 50 dígitos | muestras completadas | con precisión derivada |
|---|---|---|---|
| 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, sin error lanzado | 100.87% |
| 8192 | 4766.49% | 1/2 | 99.07% |
--precision_margin ajusta el margen por encima del mínimo derivado; el mínimo en sí se aplica de forma obligatoria.
El Algoritmo 3 se detiene cuando (L + R) / 2 - d_s cae en (-0.5, 0.5]. Esa prueba puede dispararse mientras el intervalo es más estrecho que 1, y la ventana [mid - 0.5, mid + 0.5) es entonces más ancha que el intervalo que la contiene. La rotación inversa es una isometría módulo el ancho del intervalo, por lo que una ventana que cruza el corte deja al receptor a un intervalo completo de distancia de d_s.