polarstego

Crates.iopolarstego
lib.rspolarstego
version0.3.0
created_at2026-01-05 10:43:47.159192+00
updated_at2026-01-25 21:22:58.080093+00
descriptionRust implementation of Steganographic Polar Codes
homepage
repositoryhttps://gitlab.com/harrose/polarstego
max_upload_size
id2023605
size27,035
(harrose67)

documentation

README

polarstego

crates.io

Rust implementation of Steganographic Polar Codes (SPC). It's used to embed a secret message in a cover signal with near-optimal distortion.

Based on Designing Near-Optimal Steganographic Codes in Practice Based on Polar Codes.

Part of the Rooks project.

Roadmap:

  • SC Decoder
  • Embedding (Algorithm 2)
  • Extraction (Algorithm 3)
  • Embedding (Algorithm 4)
  • Extraction (Algorithm 5)
  • Brent's method for finding lambda
  • SCL Decoder

Algorithms 2 and 3 are removed because they are special cases of Algorithms 4 and 5.

Notes

The input is supposed to be scrambled by a secret key. There are two reasons for this:

  • Scrambling allows to distribute all bits evenly which results in lower distortion and higher chance of successful embedding. We can tentatively divide the bits into these types; the first three are determined by the cost map, the fourth is a feature of SPC:
    • cheap — should be modified first
    • expensive — can be modified only if it's necessary to embed message and can't be avoided
    • wet — bits with extremely high costs (e.g. 1e10), their modification is highly discouraged, but theoretically can happen
    • wet padding — can't be modified because it breaks the algorithm. If cover length is not equal 2^n, we pad it with zeros to the closest power of two. The same thing is done during extraction. The padding bits must remain zeros and their modification probability is set to 0.0. If SC Decoder flips some of them anyway, it means that embedding failed. We won't be able to extract our message. In this case, embed returns Err(PolarStegoError::WetPaddingModified(amount)), where amount is the number of flipped bits. To fix this, you should shorten the message.
  • Using a secret key allows to hide headers, like message length or checksum, if they are present.

If you don't specify scrambling_key, a fixed zero key [0; 32] will be used. Providing a custom key is optional. In my opinion, it's more useful to implement scrambling by a secret key on a higher level. But scrambling itself is necessary for SPC to work properly.

Examples

use polarstego::{embed, extract};

// `x` is usually an array of the least significant bits of image pixels
let x = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0];
// Cost map `rho`: how "expensive" it is to modify each bit
// High costs (like 10.0) mark "wet" bits that shouldn't be touched
// They are usually called "wet pixels" because image steganography is the most common
let rho = [0.2, 10.0, 10.0, 0.5, 10.0, 10.0, 0.2, 0.2, 0.2, 5.0];
// Our secret message
let m = [1, 1, 0];

let y = embed(&x, &rho, &m, None).unwrap();
println!("{:?}", y);
// y = [1, 0, 1, 0, 1, 0, 1, 1, 0, 0]
//                           ^  ^ these bits were changed
// Total distortion: 0.4

// The message can be extracted back if you know it's length
let m_extracted = extract(&y, m.len(), None);
assert_eq!(m.to_vec(), m_extracted);
Commit count: 6

cargo fmt