detect

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package steganalysis provides statistical detection of F5 steganography in JPEG images.

This package implements the Fridrich et al. "Breaking F5" attack, a statistical method for detecting hidden messages in JPEG images encoded using the F5 steganographic algorithm. The attack estimates the modification rate (beta) by analyzing DCT coefficient histograms and comparing them to estimated cover-image histograms.

This file contains:

  • Core interfaces following SOLID principles (ISP, DIP, SRP)
  • Package logger and configuration
  • Functional options for Analyze()
  • Public API functions (Analyze, DetectF5)
  • Internal analyzer pipeline
  • Quality reliability assessment
  • JPEG validation (validateInput, hasEOIMarker, markers, MinImageSize)
  • Codec adapter wrappers (JPEGCodec, CoefficientExtractor, ImageProcessor)
  • Estimator adapter wrappers (BetaEstimator, HistogramEstimator, MessageLengthEstimator)
  • Calibration adapter wrappers (HistogramCalibrator)
  • Validation helpers (safeMul, dimension checks, epsilon/quality/crop validation)
  • Factory functions for creating wrapped sub-package instances

Reference: Fridrich, J., Goljan, M., & Hogea, D. (2002). "Steganalysis of JPEG Images: Breaking the F5 Algorithm." 5th Information Hiding Workshop, LNCS 2578, 310-323. See docs/Breaking-F5.pdf.

Package steganalysis provides statistical detection of F5 steganography in JPEG images.

This package implements the Fridrich et al. "Breaking F5" attack, a statistical method for detecting hidden messages in JPEG images encoded using the F5 steganographic algorithm. The attack estimates the modification rate (beta) by analyzing DCT coefficient histograms and comparing them to estimated cover-image histograms.

Quick Start

Basic F5 detection:

result, err := steganalysis.Analyze(jpegData)
if err != nil {
    log.Fatal(err)
}
if result.IsClean {
    fmt.Println("Image appears clean")
} else {
    fmt.Printf("Detected hidden message: ~%d bytes\n", result.MessageLength.MessageBytes)
}

Simple detection with boolean result:

detected, confidence, err := steganalysis.DetectF5(jpegData)
if detected {
    fmt.Printf("F5 steganography detected (confidence: %.2f)\n", confidence)
}

Edge Cases and Boundary Conditions

This section documents the edge cases handled by the steganalysis package. Understanding these cases is important for proper error handling and result interpretation.

## Empty and Nil Inputs

All functions validate nil and empty inputs:

  • nil image data: returns ErrImageNil
  • empty image data (len=0): returns ErrImageEmpty
  • nil histograms: returns ErrEmptyHistograms
  • empty histogram maps: treated as zero counts for all values

## Numeric Edge Cases

Beta estimation handles several numeric edge cases:

  • Division by zero: returns beta=0.0 with confidence=0.0 (denominator < 1e-10)
  • Negative beta: clamped to 0.0 (indicates estimation error)
  • Beta > 1.0: clamped to 1.0 (theoretically impossible)
  • NaN/Inf results: validated and return appropriate errors

## Image Validation

The package validates JPEG images against these constraints:

  • Minimum size: 10 bytes (MinImageSize constant)
  • Maximum size: 100 MB (MaxImageSize constant)
  • SOI marker: must be present at bytes 0-1 (0xFF 0xD8)
  • EOI marker: must be present near end of file (0xFF 0xD9)
  • Baseline JPEG (SOF0): analyzed directly
  • Progressive JPEG (SOF2): losslessly transcoded to baseline before analysis (the jpegtran "-optimize" reversal — bit-exact in the DCT coefficient domain, so β is unchanged). This handles carriers that have been repackaged into progressive form, e.g. by social platforms that losslessly re-encode uploads. Disable via WithProgressiveSupport(false).
  • Maximum dimensions: 65535 x 65535 pixels (MaxImageDimension)

## Quality Factor Detection

Quality factor detection has these limitations:

  • Quality 0: cannot be detected (treated as invalid)
  • Quality 1: cannot be reliably detected (pattern collisions)
  • Quality 100: cannot be reliably detected (pattern collisions)
  • Non-standard tables: returns quality=0 with error

## DCT Coefficient Range

The Fridrich attack analyzes coefficients in range [-5, +5]:

  • CoeffRangeMin = -5
  • CoeffRangeMax = 5
  • Coefficients outside this range are ignored in histograms
  • Reading absent histogram entries returns 0 (Go map semantics)

## Double Compression Detection

Double compression is detected when:

  • Best calibration quality differs from detected quality by >= 5
  • This triggers recalibration with the detected first-pass quality
  • DoubleCompressionThreshold = 3

## Shrinkage Probability

Shrinkage probability is clamped to [0.0, 1.0]:

  • Negative shrinkage (estimation error): clamped to 0.0
  • Shrinkage > 1.0 (impossible): clamped to 1.0

## Matrix Embedding Parameter (k)

The matrix embedding parameter k must be in range [1, 9] per the F5 spec (Westfeld 2001, Table 1; Liu et al. 2020, Table 2):

  • k=1: W(1) = 2.00 (1 bit per change, n=1)
  • k=2: W(2) = 2.67 (2.67 bits per change, n=3)
  • k=3: W(3) = 3.43 (3.43 bits per change, n=7)
  • k=4: W(4) = 4.27 (4.27 bits per change, n=15)
  • k=5: W(5) = 5.16 (5.16 bits per change, n=31)
  • k=6: W(6) = 6.09 (6.09 bits per change, n=63)
  • k=7: W(7) = 7.06 (7.06 bits per change, n=127)
  • k=8: W(8) = 8.03 (8.03 bits per change, n=255)
  • k=9: W(9) = 9.02 (9.02 bits per change, n=511)
  • k < 1 or k > 9: returns ErrMatrixKOutOfRange

## Crop and Blur Parameters

Default crop and blur parameters from the Fridrich paper:

  • DefaultCropPixels = 4 (origin shift from the top-left, per Fridrich §3.2 "crop the image by 4 columns / shift by 4 pixels in both directions" — NOT a symmetric four-sided trim)
  • DefaultBlurEpsilon = 0.01 (blur kernel weight, ε in the 3×3 kernel where central = 1−4ε and edge-neighbour = ε)
  • Crop size >= image dimension/2: returns ErrCropTooLarge
  • Epsilon must be in [0, 1] and finite

## Integer Overflow Prevention

The package prevents integer overflow in dimension calculations:

  • width * height is checked for overflow before calculation
  • width * height * channels is checked for overflow
  • Maximum dimensions prevent overflow in all supported operations

## Quality Factor Reliability

The Fridrich steganalysis method's accuracy depends heavily on JPEG quality factor. The QualityReliability field in AnalysisResult indicates the reliability tier:

  • ReliabilityOptimal (Q=60-91): Paper's validated working range. Detection and beta estimation are reliable.
  • ReliabilityDegraded (Q=33-59): Detection works but with reduced accuracy. The paper notes that quality factors below 60 produce less accurate results.
  • ReliabilityUnreliable (Q=92-100): Near-lossless quantization means the crop-and-recompress calibration cannot effectively distinguish stego from cover histograms. Results should be treated with extreme caution.
  • ReliabilityInsufficient (Q=1-32): Aggressive quantization zeros out nearly all AC coefficients, leaving insufficient capacity for F5 embedding.
  • ReliabilityUnknown (Q=0): Quality could not be determined.

The Warnings field contains structured warnings when quality is outside the optimal range. Each warning has a Code (for programmatic handling) and a human-readable Message.

Confidence is automatically scaled by quality reliability: full confidence (multiplier=1.0) is only granted for the optimal range. Degraded quality reduces confidence proportionally.

## Confidence Values

Confidence values are always in [0.0, 1.0]:

  • Derived from the denominator magnitude in the least-squares formula
  • Normalized using log10 scale and scaled by quality reliability
  • Higher values indicate more reliable estimates
  • Very low confidence (< 0.5) suggests unreliable results

## Thread Safety

All implementations are safe for concurrent use:

  • No mutable shared state in core components
  • Each analysis operation is independent
  • Logger configuration is protected by mutex

Algorithm Constants

Key constants from the Fridrich paper are defined in interfaces.go:

  • DefaultBetaThreshold = 0.125 (for 10^-8 false positive rate)
  • DefaultCropPixels = 4 (top-left origin shift, not a four-sided trim)
  • DefaultBlurEpsilon = 0.01 (blur strength)
  • FrequencyBandMode12 = 1 (paper mode (1,2); 0-indexed (0,1))
  • FrequencyBandMode21 = 8 (paper mode (2,1); 0-indexed (1,0))
  • FrequencyBandMode22 = 9 (paper mode (2,2); 0-indexed (1,1))

The StegoKit toolkit provides several related packages:

F5 steganography implementation:

  • github.com/0verkilll/f5stegokit - F5 embedding and extraction

Other JPEG steganography packages:

  • github.com/0verkilll/jsteg - JSteg algorithm (simpler, less secure)
  • github.com/0verkilll/outguess - OutGuess with histogram preservation
  • github.com/0verkilll/jphide - JPHide with Blowfish encryption

Detection frameworks:

  • github.com/0verkilll/stegdetect - Unified detection interface
  • github.com/0verkilll/stegdetectclone - stegdetect tool clone
  • github.com/0verkilll/stegexposeclone - StegExpose tool clone

Pure Go JPEG codec:

  • github.com/0verkilll/jpeg - JPEG encoding/decoding with DCT access

Reference

Fridrich, J., Goljan, M., & Hogea, D. (2002). "Steganalysis of JPEG Images: Breaking the F5 Algorithm." 5th Information Hiding Workshop.

Example

Example demonstrates beta (modification rate) concepts.

This example shows how beta values indicate embedding levels. Beta represents the proportion of coefficients modified during embedding.

package main

import (
	"fmt"
)

func main() {
	// Example beta values and their interpretation
	betas := []float64{0.0, 0.05, 0.10, 0.15, 0.25}

	fmt.Println("Beta interpretation:")
	for _, beta := range betas {
		var interpretation string
		switch {
		case beta < 0.05:
			interpretation = "Clean image (no embedding detected)"
		case beta < 0.10:
			interpretation = "Low embedding rate"
		case beta < 0.20:
			interpretation = "Moderate embedding rate"
		default:
			interpretation = "High embedding rate"
		}
		fmt.Printf("  beta=%.2f: %s\n", beta, interpretation)
	}

}
Output:
Beta interpretation:
  beta=0.00: Clean image (no embedding detected)
  beta=0.05: Low embedding rate
  beta=0.10: Moderate embedding rate
  beta=0.15: Moderate embedding rate
  beta=0.25: High embedding rate

Index

Examples

Constants

View Source
const (
	// MaxImageSize is the maximum allowed image data size in bytes.
	// This prevents memory exhaustion attacks when processing untrusted JPEG files.
	//
	// Rationale:
	//   - 100MB allows for very large JPEG files (most are under 20MB)
	//   - Prevents OOM attacks from malicious size claims
	//   - Users requiring larger images should process in chunks or use streaming
	//
	// If image data exceeds this limit, analysis functions return an error.
	MaxImageSize = 100 * 1024 * 1024 // 100 MB

	// MaxHistogramRange defines the maximum DCT coefficient value range for histograms.
	// The Fridrich attack focuses on coefficients in the range [-5, +5], but we allow
	// a wider range for edge cases and alternative analysis methods.
	//
	// Rationale:
	//   - DCT coefficients are typically small integers after quantization
	//   - Values outside [-1024, +1024] are extremely rare in normal images
	//   - Limiting range prevents memory exhaustion from sparse histogram attacks
	//
	// Coefficients outside this range are ignored in histogram building.
	MaxHistogramRange = 1024

	// MaxQualityFactor is the maximum JPEG quality factor (standard range).
	// Quality factors range from 1 (worst) to 100 (best).
	MaxQualityFactor = 100

	// MinQualityFactor is the minimum JPEG quality factor (standard range).
	MinQualityFactor = 1

	// DefaultBetaThreshold is the default threshold for classifying images as clean.
	// Images with beta below this threshold are considered clean (no steganographic content).
	//
	// Rationale:
	//   - Value 0.125 from Fridrich paper for 10^-8 false positive rate
	//   - Lower values are more conservative (fewer false positives, more false negatives)
	//   - Higher values are more aggressive (more false positives, fewer false negatives)
	DefaultBetaThreshold = 0.125
)

Security limits to prevent resource exhaustion attacks. These constants define hard limits on input parameters to protect against malicious or accidental resource exhaustion when processing untrusted input.

View Source
const (
	// DefaultCropPixels is the number of pixels to crop from each edge.
	// The crop breaks the 8x8 DCT block alignment, removing quantization artifacts
	// that would otherwise bias the histogram estimation.
	//
	// From Section 3.2: "We first decompress the stego-image to the spatial domain,
	// then crop the image by 4 pixels..."
	DefaultCropPixels = 4

	// DefaultBlurEpsilon is the blur strength for the 3x3 low-pass filter.
	// The blur reduces discontinuities at block boundaries after cropping.
	//
	// From Section 3.2: "We have experimented with several spatial blocking-removing
	// algorithms, but the best results were obtained using a simple uniform blurring
	// operation with a 3x3 kernel B, B22=1-4e, B21 = B23 = B12 = B32 = e"
	//
	// Small epsilon (0.01) provides mild smoothing without excessive blurring.
	DefaultBlurEpsilon = 0.01

	// CoeffRangeMin is the minimum DCT coefficient value analyzed in histograms.
	// The Fridrich attack focuses on small coefficient values where F5 modifications
	// are most concentrated.
	//
	// From Section 3.1: The analysis uses histogram values h(d) for d = 0, 1, 2, ...
	// and we limit to [-5, +5] for computational efficiency.
	CoeffRangeMin = -5

	// CoeffRangeMax is the maximum DCT coefficient value analyzed in histograms.
	CoeffRangeMax = 5

	// DoubleCompressionThreshold is the quality factor difference that indicates
	// double compression. If the best calibration quality differs from the detected
	// quality by this amount or more, the image is likely double-compressed.
	//
	// From Section 4: "If best quality differs from stego quality by at least 3,
	// it's likely double-compressed"
	DoubleCompressionThreshold = 3

	// NumLowFrequencyModes is the number of DCT frequency modes used in analysis.
	// The Fridrich attack uses modes (1,2), (2,1), and (2,2) where F5 modifications
	// are most concentrated.
	//
	// From Section 3.1: "The final value of the parameter beta is calculated as an
	// average over selected low-frequency DCT coefficients (k, l) in {(1,2),(2,1),(2,2)}"
	NumLowFrequencyModes = 3

	// FrequencyBandMode12 is the frequency band index for the Fridrich paper's
	// mode (1,2) — the first horizontal AC coefficient. The paper uses 1-indexed
	// (k,l) notation where (1,1) is the DC coefficient, so paper mode (1,2)
	// corresponds to 0-indexed array position (0,1).
	// FrequencyBand = row*8 + col = 0*8 + 1 = 1
	FrequencyBandMode12 = 1

	// FrequencyBandMode21 is the frequency band index for the Fridrich paper's
	// mode (2,1) — the first vertical AC coefficient. 0-indexed position (1,0).
	// FrequencyBand = 1*8 + 0 = 8
	FrequencyBandMode21 = 8

	// FrequencyBandMode22 is the frequency band index for the Fridrich paper's
	// mode (2,2) — the first diagonal AC coefficient. 0-indexed position (1,1).
	// FrequencyBand = 1*8 + 1 = 9
	FrequencyBandMode22 = 9

	// BetaVarianceCVThreshold is the coefficient of variation threshold above
	// which per-mode beta estimates are considered high-variance. When the CV
	// exceeds this threshold, the message length estimator uses the minimum
	// per-mode beta instead of the average to reduce overestimation.
	//
	// For well-calibrated photographic images, per-mode betas typically agree
	// within CV < 0.15. A CV > 0.30 indicates significant disagreement,
	// suggesting calibration inaccuracy (common in cartoons, illustrations).
	BetaVarianceCVThreshold = 0.30
)

These constants define the parameters for the Fridrich steganalysis attack. Reference: Fridrich, J., Goljan, M., & Hogea, D. (2002). "Steganalysis of JPEG Images: Breaking the F5 Algorithm." 5th Information Hiding Workshop, LNCS 2578, 310-323. See docs/Breaking-F5.pdf.

View Source
const (
	// QualityInsufficientMax is the maximum quality where F5 embedding is
	// impossible due to insufficient non-zero AC coefficients. At Q<=32,
	// aggressive quantization zeros out nearly all AC coefficients.
	QualityInsufficientMax = 32

	// QualityDegradedMin is the minimum quality for the degraded detection range.
	// Detection works at Q=33-59 but with reduced accuracy compared to the
	// paper's validated range.
	QualityDegradedMin = 33

	// QualityOptimalMin is the minimum quality for reliable detection.
	// The Fridrich paper validates detection accuracy for Q>=60.
	QualityOptimalMin = 60

	// QualityOptimalMax is the maximum quality for reliable detection.
	// Above this, near-lossless quantization makes the low-frequency DCT
	// histograms so sparse that the crop-and-recompress cover estimate is
	// dominated by quantization noise — it over-produces |1| coefficients and
	// the β estimate saturates. This was lowered from 91 to 88 after a
	// confirmed F5 stego at Q=91 (true β≈0.04) saturated to β≈1.2: empirically
	// Q≈89+ is not reliable, so it must not be rated "optimal".
	QualityOptimalMax = 88

	// QualityUnreliableMin is the minimum quality where the cover-histogram
	// estimate becomes unreliable (sparse high-frequency histograms; small
	// quantization steps). At and above this the statistical signal is
	// insufficient for accurate estimation.
	QualityUnreliableMin = 89
)

Quality reliability boundaries based on the Fridrich paper and empirical testing across the full Q=1-100 range. These define the quality factor thresholds where detection accuracy changes significantly.

Empirical validation (TestPaperValidation_FullQualityRange):

  • Q=1-32: F5 embedding fails -- too few non-zero AC coefficients
  • Q=33-59: Detection works, accuracy reduced (paper says Q<60 problematic)
  • Q=60-91: 100% detection, all within +/-0.15 beta tolerance
  • Q=92-100: Detection degrades, calibration fails for most images
View Source
const DefaultReliableBetaFloor = 0.05

DefaultReliableBetaFloor is the β reliability floor at the paper's default 4-pixel crop. Below it, message-length and matrix-k estimates should be treated as order-of-magnitude guesses (see WarningLowBetaFloor and MessageLengthEstimate.BelowReliableFloor).

View Source
const MaxImageDimension = codec.MaxImageDimension

MaxImageDimension is the maximum supported image dimension (width or height). Re-exported from codec for root-level access.

View Source
const MaxPixelCount = codec.MaxPixelCount

MaxPixelCount is the maximum total number of pixels (width * height) allowed. Re-exported from codec for root-level access.

View Source
const MaxPlausibleF5Beta = 0.6

MaxPlausibleF5Beta is the largest modification rate a genuine F5 embed can produce. Fridrich Fig. 2 places the "full capacity" peak at β=0.5: at k=1 with the message filling every usable coefficient, matrix encoding still only forces a change in ~half of them. Shrinkage adds a little, but real embeds do not exceed ~0.5. A measured β above this ceiling therefore signals that the crop-recompress cover-histogram reconstruction has DIVERGED for the image (small/high-Q/app-processed carriers), not a heavier-than-full embed. We set the threshold slightly above 0.5 so a legitimate near-full-capacity embed is not falsely flagged.

View Source
const MinImageSize = 10

MinImageSize is the minimum valid JPEG image size in bytes.

Variables

This section is empty.

Functions

func ClampBeta

func ClampBeta(beta float64) float64

ClampBeta is a specialized clamp for beta values (modification rate). Beta must be in range [0.0, 1.0] as it represents a probability/rate.

Parameters:

  • beta: Beta value to clamp

Returns:

  • Beta clamped to [0.0, 1.0]

func ClampConfidence

func ClampConfidence(confidence float64) float64

ClampConfidence is a specialized clamp for confidence values. Confidence must be in range [0.0, 1.0].

Parameters:

  • confidence: Confidence value to clamp

Returns:

  • Confidence clamped to [0.0, 1.0]

func ClampFloat64

func ClampFloat64(v, minVal, maxVal float64) float64

ClampFloat64 constrains a float64 value to the range [minVal, maxVal]. This is used throughout the package and its sub-packages to ensure values stay within valid bounds.

Parameters:

  • v: Value to clamp
  • minVal: Minimum allowed value
  • maxVal: Maximum allowed value

Returns:

  • The value clamped to [minVal, maxVal]

func ClampShrinkage

func ClampShrinkage(shrinkage float64) float64

ClampShrinkage is a specialized clamp for shrinkage probability. Shrinkage must be in range [0.0, 1.0] as it represents a probability.

Parameters:

  • shrinkage: Shrinkage probability to clamp

Returns:

  • Shrinkage clamped to [0.0, 1.0]

func CountNonZeroACCoefficients

func CountNonZeroACCoefficients(coeffs []DCTCoefficient) (totalNonZero, totalAbsH1 int)

CountNonZeroACCoefficients counts all non-zero luminance AC coefficients. Delegates to estimator.CountNonZeroACCoefficients with type conversion.

func DetectF5

func DetectF5(imageData []byte) (detected bool, confidence float64, err error)

DetectF5 performs simplified F5 detection, returning only detection status.

COURT-SAFE SEMANTICS: detected is true ONLY when the full analysis returns VerdictStego — i.e. β crossed the threshold AND the cover-histogram reconstruction was reliable. An inconclusive result (the reconstruction diverged, or the quality factor is outside the method's reliable range) is reported as detected=false, NOT as a detection. This is the fix for the dominant false-positive source: previously any saturated/divergent clean image (most re-saved web images) returned detected=true.

Because the boolean cannot distinguish "clean" from "inconclusive", callers who must tell those apart — or who need the reason an image was inconclusive — should use Detect (which returns the tri-state Verdict and warnings) or the full Analyze.

Example

ExampleDetectF5 demonstrates the simplified F5 detection API.

This example shows the quick detection function that returns a simple boolean result with confidence score.

package main

import (
	"fmt"

	"github.com/0verkilll/jpeg"

	"github.com/0verkilll/f5/detect"
)

func main() {
	// Create a test JPEG
	jpegData := createTestJPEG()
	if jpegData == nil {
		fmt.Println("Failed to create test JPEG")
		return
	}

	// Quick detection
	_, confidence, err := detect.DetectF5(jpegData)
	if err != nil {
		fmt.Printf("Detection error: %v\n", err)
		return
	}

	fmt.Printf("Detection complete\n")
	fmt.Printf("Has confidence: %v\n", confidence >= 0)

}

// createTestJPEG creates a valid JPEG image for testing.
// Returns a 64x64 RGB JPEG with sufficient size for calibration-based analysis.
func createTestJPEG() []byte {

	width, height := 64, 64
	img := jpeg.NewImageData(width, height, jpeg.ColorSpaceRGB)

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			idx := (y*width + x) * 3
			img.Pixels[idx] = uint8((x * 255) / width)
			img.Pixels[idx+1] = uint8((y * 255) / height)
			img.Pixels[idx+2] = uint8(128)
		}
	}

	opts := jpeg.DefaultEncoderOptions()
	opts.Quality = 75

	encoder, err := jpeg.NewEncoder(jpeg.FormatBaselineJPEG, opts)
	if err != nil {
		return nil
	}

	data, err := encoder.Encode(img)
	if err != nil {
		return nil
	}

	return data
}
Output:
Detection complete
Has confidence: true

func FoldedCount

func FoldedCount(hist DCTHistogram, d int) int

FoldedCount returns the absolute-value folded histogram count for distance d. For d == 0, it returns counts[0] (the zero-coefficient count). For d > 0, it returns counts[d] + counts[-d], folding positive and negative coefficients at the same distance from zero.

This implements the h(d) notation from Fridrich et al. Equation 3, where h(d) represents the count of coefficients with |value| = d.

Go map zero-value semantics ensure that missing keys return 0, so this function is safe to call even when one side of the fold has no entries.

Parameters:

  • hist: DCT coefficient histogram
  • d: Absolute coefficient distance (must be >= 0)

Returns:

  • The folded count: counts[0] for d=0, counts[d]+counts[-d] for d>0

func GetLogger

func GetLogger() logger.Logger

GetLogger returns the package logger, or NopLogger if not set.

func GetSupportedLocales

func GetSupportedLocales() []string

GetSupportedLocales returns the list of locales supported by this package.

It reads the list of embedded locale files and returns their locale codes. The returned array is sorted alphabetically for consistency.

If the embedded filesystem cannot be read (which should never happen in normal operation), this function returns a fallback array containing only "en-US" to ensure graceful degradation.

func SetLogger

func SetLogger(l logger.Logger)

SetLogger sets the logger for the steganalysis package. Pass nil to disable logging and reset to the default NopLogger. The logger is shared across all goroutines and is thread-safe.

func SetTranslator

func SetTranslator(translator TranslatorProvider)

SetTranslator sets the global translator for this package and propagates it to all sub-packages (estimator, calibration, codec). Pass nil to disable translations and use default English messages. The translator is shared across all goroutines and is thread-safe.

func SqrtFloat64

func SqrtFloat64(v float64) float64

SqrtFloat64 returns the square root of a non-negative float64 value. Returns 0 for negative inputs (defensive; should not occur in practice). This helper is exported for sub-package use where importing math directly is undesirable to keep source files import-free.

func Subsampling

func Subsampling(imageData []byte) (string, error)

Subsampling returns the chroma subsampling label (e.g. "4:2:0", "4:2:2", "grayscale", "unknown") by parsing only the frame header — no coefficient decode or transcode. It is a cheap pre-filter for callers that want to rule a frame in or out before the expensive statistical/neural passes. It errors only when the input is not a JPEG.

Types

type AnalysisResult

type AnalysisResult struct {
	// MessageLength is the estimated hidden message size, if detected.
	// Nil if the image is classified as clean.
	MessageLength *MessageLengthEstimate `json:"message_length,omitempty"`

	// PerModeBetas contains the beta estimate for each DCT frequency mode.
	// Useful for detailed analysis and debugging.
	PerModeBetas map[string]float64 `json:"per_mode_betas,omitempty"`

	// Calibration contains calibration results if multi-QF analysis was performed.
	// Nil if single-QF analysis was used.
	Calibration *CalibrationResult `json:"calibration,omitempty"`

	// Signatures holds the structural detection signatures (JPEG format, chroma
	// subsampling, James/f5.jar marker fingerprints, quantization tables,
	// embedding capacity) for the analyzed image. These are independent of the
	// statistical Beta estimate and together describe whether the image is a
	// plausible F5 carrier and what encoder produced it. Nil if signature
	// analysis could not run.
	Signatures *SignatureReport `json:"signatures,omitempty"`

	// QualityReliability indicates how reliable the analysis results are based
	// on the detected JPEG quality factor. The Fridrich method works best at
	// Q=60-91; results outside this range should be interpreted with caution.
	QualityReliability QualityReliability `json:"quality_reliability"`

	// Verdict is the tri-state classification: VerdictClean, VerdictStego, or
	// VerdictInconclusive. This is the field court-defensible callers should
	// branch on. A hidden payload is asserted ONLY by VerdictStego;
	// VerdictInconclusive means the method could not decide for this image (the
	// reconstruction diverged or quality is out of range) and must never be
	// reported as a detection.
	Verdict Verdict `json:"verdict"`

	// InconclusiveReason is a short human-readable explanation populated only
	// when Inconclusive is true (e.g. "beta saturated", "beta above F5
	// capacity ceiling", "quality factor out of reliable range"). Empty
	// otherwise.
	InconclusiveReason string `json:"inconclusive_reason,omitempty"`

	// Warnings contains non-fatal issues detected during analysis that may
	// affect result reliability. Empty when quality is in the optimal range
	// and no issues are detected.
	Warnings []Warning `json:"warnings,omitempty"`

	// Beta is the estimated modification rate in range [0, 1].
	// Higher values indicate more steganographic content.
	Beta float64 `json:"beta"`

	// RawBeta is the unclamped β estimate before the [0, 1] clamp. Values
	// outside [0, 1] indicate the cover-image histogram reconstruction has
	// diverged (small images, very high quality factors, double JPEG
	// compression). When RawBeta differs substantially from Beta, the
	// downstream message-length estimate is unreliable; check Saturated
	// before trusting it.
	RawBeta float64 `json:"raw_beta"`

	// Confidence indicates the overall reliability of the analysis in [0.0, 1.0].
	// Derived from the least-squares denominator magnitude (normalized via log10)
	// and scaled by quality reliability (full confidence only for Q=60-91).
	Confidence float64 `json:"confidence"`

	// Quality is the detected JPEG compression quality factor (1-100).
	Quality int `json:"quality"`

	// Saturated reports whether the RawBeta fell outside [0, 1] before the
	// public clamp. True is a strong signal that downstream estimates
	// (message length, k inference) are unreliable for this image.
	Saturated bool `json:"saturated"`

	// IsClean indicates whether the image is classified as clean.
	// True only when Verdict == VerdictClean (β below threshold AND the
	// reconstruction was reliable). False for both VerdictStego and
	// VerdictInconclusive — so a false IsClean no longer implies "stego". Use
	// Verdict for the three-way distinction; treat IsClean as "confidently
	// clean" only.
	IsClean bool `json:"is_clean"`

	// Inconclusive is a convenience boolean equal to (Verdict ==
	// VerdictInconclusive). When true, Beta / MessageLength / k are unreliable
	// and InconclusiveReason explains why.
	Inconclusive bool `json:"inconclusive"`
}

AnalysisResult contains the complete results of F5 steganalysis.

This is the primary result type returned by the high-level Analyze() function. It combines results from beta estimation, message length estimation, and quality detection into a single comprehensive report.

JSON serialization is supported for reporting and interoperability.

func Analyze

func Analyze(imageData []byte, opts ...Option) (result *AnalysisResult, err error)

Analyze performs complete F5 steganalysis on JPEG image data.

Example

ExampleAnalyze demonstrates basic F5 detection.

This example shows how to analyze a JPEG image for F5 steganography using the Fridrich et al. statistical method.

package main

import (
	"fmt"

	"github.com/0verkilll/jpeg"

	"github.com/0verkilll/f5/detect"
)

func main() {
	// Create a test JPEG image
	// In real use, this would be loaded from a file
	jpegData := createTestJPEG()
	if jpegData == nil {
		fmt.Println("Failed to create test JPEG")
		return
	}

	// Analyze the image with default options
	result, err := detect.Analyze(jpegData)
	if err != nil {
		fmt.Printf("Analysis error: %v\n", err)
		return
	}

	fmt.Printf("Analysis complete\n")
	fmt.Printf("Has result: %v\n", result != nil)
	fmt.Printf("Beta estimated: %v\n", result.Beta >= 0)

}

// createTestJPEG creates a valid JPEG image for testing.
// Returns a 64x64 RGB JPEG with sufficient size for calibration-based analysis.
func createTestJPEG() []byte {

	width, height := 64, 64
	img := jpeg.NewImageData(width, height, jpeg.ColorSpaceRGB)

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			idx := (y*width + x) * 3
			img.Pixels[idx] = uint8((x * 255) / width)
			img.Pixels[idx+1] = uint8((y * 255) / height)
			img.Pixels[idx+2] = uint8(128)
		}
	}

	opts := jpeg.DefaultEncoderOptions()
	opts.Quality = 75

	encoder, err := jpeg.NewEncoder(jpeg.FormatBaselineJPEG, opts)
	if err != nil {
		return nil
	}

	data, err := encoder.Encode(img)
	if err != nil {
		return nil
	}

	return data
}
Output:
Analysis complete
Has result: true
Beta estimated: true
Example (WithOptions)

ExampleAnalyze_withOptions demonstrates analysis with custom options.

This example shows how to configure the analysis parameters for specific detection scenarios using functional options.

package main

import (
	"fmt"

	"github.com/0verkilll/jpeg"

	"github.com/0verkilll/f5/detect"
)

func main() {
	// Create a test JPEG
	jpegData := createTestJPEG()
	if jpegData == nil {
		fmt.Println("Failed to create test JPEG")
		return
	}

	// Analyze with custom threshold (higher = fewer false positives)
	result, err := detect.Analyze(jpegData,
		detect.WithThreshold(0.15),
	)
	if err != nil {
		fmt.Printf("Analysis error: %v\n", err)
		return
	}

	fmt.Printf("Analysis with custom options complete\n")
	fmt.Printf("Has result: %v\n", result != nil)

}

// createTestJPEG creates a valid JPEG image for testing.
// Returns a 64x64 RGB JPEG with sufficient size for calibration-based analysis.
func createTestJPEG() []byte {

	width, height := 64, 64
	img := jpeg.NewImageData(width, height, jpeg.ColorSpaceRGB)

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			idx := (y*width + x) * 3
			img.Pixels[idx] = uint8((x * 255) / width)
			img.Pixels[idx+1] = uint8((y * 255) / height)
			img.Pixels[idx+2] = uint8(128)
		}
	}

	opts := jpeg.DefaultEncoderOptions()
	opts.Quality = 75

	encoder, err := jpeg.NewEncoder(jpeg.FormatBaselineJPEG, opts)
	if err != nil {
		return nil
	}

	data, err := encoder.Encode(img)
	if err != nil {
		return nil
	}

	return data
}
Output:
Analysis with custom options complete
Has result: true

func (AnalysisResult) MarshalJSON

func (r AnalysisResult) MarshalJSON() ([]byte, error)

MarshalJSON emits AnalysisResult as JSON with all float64 fields serialized as floating-point numbers (e.g. "0.0" rather than "0"). The value receiver is required to satisfy json.Marshaler for both AnalysisResult and *AnalysisResult values; a pointer receiver would skip the custom marshaling when a value (not a pointer) is encoded.

type BetaEstimationResult

type BetaEstimationResult struct {
	// PerModeBetas contains the beta estimate for each DCT frequency mode.
	// Key format: "row,col" (e.g., "1,2", "2,1", "2,2")
	// Value: beta estimate for that mode
	//
	// Per-mode analysis is more sensitive than aggregate analysis because
	// F5 modifications are concentrated in specific frequency bands.
	//
	// Note: Reading from a nil PerModeBetas map returns 0 (Go map semantics).
	PerModeBetas map[string]float64 `json:"per_mode_betas"`

	// PerModeRawBetas mirrors PerModeBetas but holds the unclamped values
	// per DCT mode. Useful for diagnosing which mode is driving saturation.
	PerModeRawBetas map[string]float64 `json:"per_mode_raw_betas,omitempty"`

	// Beta is the overall estimated modification rate in range [0, 1].
	// This is the average of valid per-mode betas.
	//
	// Interpretation:
	//   - Beta = 0: No modifications detected (clean image)
	//   - Beta = 0.1: ~10% of usable coefficients were modified
	//   - Beta = 0.5: ~50% of usable coefficients were modified
	//   - Beta = 1.0: Maximum theoretical modification rate
	Beta float64 `json:"beta"`

	// Confidence indicates the reliability of the beta estimate.
	// Higher values indicate more reliable estimates.
	// Based on the denominator magnitude in the least-squares formula.
	Confidence float64 `json:"confidence"`

	// MinBeta is the minimum of positive per-mode betas.
	// When per-mode beta variance is high (BetaCV > BetaVarianceCVThreshold),
	// MinBeta may be more accurate than the averaged Beta for message length
	// estimation, especially for non-photographic images (cartoons, illustrations).
	MinBeta float64 `json:"min_beta"`

	// BetaCV is the coefficient of variation (std/mean) of positive per-mode betas.
	// A high CV (>0.30) indicates that the per-mode betas disagree significantly,
	// suggesting the crop-and-recompress calibration may be inaccurate.
	// This typically occurs with non-photographic images.
	BetaCV float64 `json:"beta_cv"`

	// RawBeta is the unclamped estimator output before the [0, 1] clamp
	// applied to Beta. Values outside [0, 1] indicate the cover-image
	// histogram reconstruction has diverged — typically caused by small
	// images, very high quality factors (Q > 91), or double JPEG
	// compression. When RawBeta differs substantially from Beta, the
	// downstream message-length estimate is unreliable; check Saturated
	// before trusting it.
	RawBeta float64 `json:"raw_beta"`

	// IsClean indicates whether the image is classified as clean (no steganography).
	// True if Beta < threshold (default threshold: 0.125 from Fridrich paper).
	IsClean bool `json:"is_clean"`

	// Saturated reports whether the RawBeta fell outside [0, 1] before the
	// public clamp. True is a strong signal that downstream estimates
	// (message length, k inference) are unreliable for this image.
	Saturated bool `json:"saturated"`
}

BetaEstimationResult contains the results of beta (modification rate) estimation.

Beta represents the fraction of DCT coefficients modified by steganography. A clean image has beta near 0, while a steganographic image has beta > 0.

JSON serialization is supported for reporting and interoperability.

func (BetaEstimationResult) MarshalJSON

func (r BetaEstimationResult) MarshalJSON() ([]byte, error)

MarshalJSON emits BetaEstimationResult as JSON with all float64 fields serialized as floating-point numbers (e.g. "0.0" rather than "0").

type BetaEstimator

type BetaEstimator interface {
	// EstimateBeta calculates beta by analyzing multiple DCT frequency modes.
	//
	// The method:
	//  1. Calculates beta for each of the low-frequency modes (1,2), (2,1), (2,2)
	//  2. Filters out negative betas (indicating estimation error for that mode)
	//  3. Averages the valid (positive) betas to get the final estimate
	//
	// Parameters:
	//   - stegoHistograms: DCT coefficient histograms from the suspected stego-image
	//   - coverHistograms: DCT coefficient histograms from the estimated cover-image
	//
	// Returns:
	//   - *BetaEstimationResult: Contains overall beta, per-mode betas, confidence, and clean classification
	//   - error: If histograms are empty or invalid
	//
	// Example:
	//   result, err := estimator.EstimateBeta(stegoHists, coverHists)
	//   if err != nil { return err }
	//   if result.IsClean {
	//       fmt.Println("Image appears clean")
	//   } else {
	//       fmt.Printf("Detected modification rate: %.2f%%\n", result.Beta * 100)
	//   }
	EstimateBeta(stegoHistograms, coverHistograms []DCTHistogram) (*BetaEstimationResult, error)

	// EstimateBetaForMode implements the Fridrich least-squares formula for a single DCT mode.
	//
	// Formula (Equation 3 from paper):
	//   beta = [h(1)*[H(0) - h(0)] + [H(1) - h(1)]*[h(2) - h(1)]] / [h(1)^2 + [h(2) - h(1)]^2]
	//
	// Where:
	//   - H(d) = stego-image histogram at coefficient value d
	//   - h(d) = estimated cover-image histogram at coefficient value d
	//
	// Parameters:
	//   - stegoHist: DCT coefficient histogram from stego-image for this mode
	//   - coverHist: DCT coefficient histogram from estimated cover-image for this mode
	//
	// Returns:
	//   - beta: Estimated modification rate in range [0, 1]
	//   - confidence: Estimation confidence (based on denominator magnitude)
	//   - error: If calculation cannot be performed
	EstimateBetaForMode(stegoHist, coverHist DCTHistogram) (beta, confidence float64, err error)
}

BetaEstimator estimates the modification rate (beta) in a suspected stego-image.

The modification rate beta represents the fraction of DCT coefficients that have been modified by the F5 steganographic algorithm. A clean image has beta near 0, while an image with hidden data has beta > 0.

This interface implements the Fridrich et al. least-squares estimation formula for calculating beta from stego and estimated cover histograms.

Implementations must be safe for concurrent use.

func NewBetaEstimator

func NewBetaEstimator() BetaEstimator

NewBetaEstimator creates a new beta estimator with the default threshold. The implementation lives in the estimator sub-package; this wrapper converts between root and estimator types.

func NewBetaEstimatorWithThreshold

func NewBetaEstimatorWithThreshold(threshold float64) BetaEstimator

NewBetaEstimatorWithThreshold creates a beta estimator with a custom threshold. The implementation lives in the estimator sub-package; this wrapper converts between root and estimator types.

type CalibrationResult

type CalibrationResult struct {
	// QualityDistances maps each tested quality factor to its L2 distance.
	// Lower distance indicates better fit.
	//
	// Note: Reading from a nil QualityDistances map returns 0 (Go map semantics).
	QualityDistances map[int]float64 `json:"quality_distances"`

	// BestQuality is the quality factor that produced the best (lowest) L2 distance.
	// This may differ from the detected quality if the image was double-compressed.
	BestQuality int `json:"best_quality"`

	// CalibratedBeta is the beta estimate using the best quality factor.
	// This is typically more accurate than UncalibratedBeta for double-compressed images.
	CalibratedBeta float64 `json:"calibrated_beta"`

	// UncalibratedBeta is the beta estimate assuming single compression.
	// This uses only the detected quality factor without calibration.
	UncalibratedBeta float64 `json:"uncalibrated_beta"`

	// Confidence indicates the reliability of the calibration.
	// Based on the separation between best and second-best distances.
	Confidence float64 `json:"confidence"`

	// IsDoubleCompressed indicates if double compression was detected.
	// True if BestQuality differs from the detected quality by at least DoubleCompressionThreshold (3).
	IsDoubleCompressed bool `json:"is_double_compressed"`
}

CalibrationResult contains the results of multi-quality-factor calibration.

Calibration is necessary for double-compressed images where the original quality factor differs from the current quality factor.

JSON serialization is supported for reporting and interoperability.

func (CalibrationResult) MarshalJSON

func (c CalibrationResult) MarshalJSON() ([]byte, error)

MarshalJSON emits CalibrationResult as JSON with all float64 fields serialized as floating-point numbers (e.g. "0.0" rather than "0").

type CoefficientExtractor

type CoefficientExtractor interface {
	// Extract retrieves all DCT coefficients from a JPEG file.
	//
	// The returned coefficients include position information for per-mode analysis.
	// Coefficients are returned for all color components (Y, Cb, Cr).
	//
	// Parameters:
	//   - data: Complete JPEG file data
	//
	// Returns:
	//   - coefficients: Slice of DCT coefficients with position metadata
	//   - error: If extraction fails or data is not valid JPEG
	//
	// Note: This is a computationally expensive operation. For large images,
	// consider caching results if multiple analyzes are needed.
	Extract(data []byte) ([]DCTCoefficient, error)
}

CoefficientExtractor extracts DCT coefficients from JPEG data.

DCT coefficient access is essential for steganalysis but not available through standard JPEG libraries. This interface abstracts coefficient extraction to support different JPEG parsers.

Implementations must be safe for concurrent use.

func NewCoefficientExtractor

func NewCoefficientExtractor() CoefficientExtractor

NewCoefficientExtractor creates a new CoefficientExtractor wrapping the codec sub-package. Converts codec.DCTCoefficient to steganalysis.DCTCoefficient and codec errors to *steganalysis.Error.

type DCTCoefficient

type DCTCoefficient struct {
	// Row is the row position in the 8x8 DCT block (0-7).
	// Row 0 contains the DC coefficient (at col 0) and low-frequency AC coefficients.
	Row int `json:"row"`

	// Col is the column position in the 8x8 DCT block (0-7).
	// Col 0 contains the DC coefficient (at row 0) and low-frequency AC coefficients.
	Col int `json:"col"`

	// Component indicates which color component this coefficient belongs to.
	// 0 = Y (luminance), 1 = Cb (blue chrominance), 2 = Cr (red chrominance)
	Component int `json:"component"`

	// Value is the quantized DCT coefficient value.
	// Typical range after quantization: [-1024, +1024] for most images.
	// F5 modifications affect only non-zero AC coefficients.
	Value int16 `json:"value"`
}

DCTCoefficient represents a single DCT coefficient with its position information.

This struct carries the coefficient value along with its location in the DCT block structure, enabling per-frequency-mode analysis.

JSON serialization is supported for interoperability with external tools.

type DCTHistogram

type DCTHistogram struct {
	// Counts maps DCT coefficient values to their occurrence count.
	// Key: coefficient value (e.g., -5 to +5)
	// Value: number of coefficients with that value
	//
	// Note: Reading from a nil Counts map returns 0 (Go map semantics).
	Counts map[int]int `json:"counts"`

	// Component indicates which color component this histogram represents.
	// 0 = Y (luminance), 1 = Cb (blue chrominance), 2 = Cr (red chrominance)
	//
	// The Fridrich attack focuses on the luminance component (Y = 0) because
	// F5 steganography only modifies luminance coefficients.
	Component int `json:"component"`

	// FrequencyBand encodes the DCT frequency position as row*8 + col using
	// 0-indexed (row, col) in the 8x8 DCT block. Examples:
	//   - (0,0) = DC coefficient, FrequencyBand = 0
	//   - (0,1) = paper mode (1,2), FrequencyBand = 1  (first horizontal AC)
	//   - (1,0) = paper mode (2,1), FrequencyBand = 8  (first vertical AC)
	//   - (1,1) = paper mode (2,2), FrequencyBand = 9  (first diagonal AC)
	//
	// The Fridrich attack uses paper modes (1,2), (2,1), and (2,2) — the three
	// lowest AC frequencies — where the statistical signal of F5 modifications
	// is strongest. Paper notation is 1-indexed; this field uses 0-indexed.
	FrequencyBand int `json:"frequency_band"`
}

DCTHistogram represents a histogram of DCT coefficient values for a specific frequency band and color component.

The histogram counts how many coefficients have each integer value within the analysis range. The Fridrich attack typically analyzes values in [-5, +5].

JSON serialization is supported for interoperability with external tools.

type DCTMode

type DCTMode string

DCTMode represents a DCT frequency mode identifier. The mode is represented as "row,col" (e.g., "1,2", "2,1", "2,2").

Using a typed string instead of raw string provides:

  • Type safety: prevents accidental use of arbitrary strings
  • Self-documentation: clarifies the expected format
  • Tooling support: IDE autocompletion for mode constants

The Fridrich attack uses three low-frequency modes where F5 modifications are most concentrated.

const (
	// Mode12 is the Fridrich paper's mode (1,2) — first horizontal AC.
	// Paper uses 1-indexed (k,l); 0-indexed array position (0,1), band = 1.
	Mode12 DCTMode = "1,2"

	// Mode21 is the Fridrich paper's mode (2,1) — first vertical AC.
	// 0-indexed array position (1,0), band = 8.
	Mode21 DCTMode = "2,1"

	// Mode22 is the Fridrich paper's mode (2,2) — first diagonal AC.
	// 0-indexed array position (1,1), band = 9.
	Mode22 DCTMode = "2,2"
)

DCT frequency mode constants used in the Fridrich steganalysis attack. These represent positions in the 8x8 DCT block: (row, col).

The attack focuses on low-frequency AC coefficients where F5 modifications have the strongest statistical signature.

func AllModes

func AllModes() []DCTMode

AllModes returns all DCT modes used in the Fridrich analysis. This is useful for iterating over modes in a consistent order.

func FrequencyBandToMode

func FrequencyBandToMode(band int) DCTMode

FrequencyBandToMode maps a frequency band index to its DCT mode. Returns an empty string if the band doesn't correspond to a known mode.

type Detection

type Detection struct {
	// Verdict is the tri-state classification (clean / stego_detected /
	// inconclusive). Assert a hidden payload ONLY on VerdictStego.
	Verdict Verdict `json:"verdict"`

	// InconclusiveReason explains why Verdict is inconclusive; empty otherwise.
	InconclusiveReason string `json:"inconclusive_reason,omitempty"`

	// Warnings carries every non-fatal issue surfaced during analysis (quality,
	// saturation, variance, inconclusive cause). This is the "surface warnings"
	// path the bare DetectF5 boolean cannot provide.
	Warnings []Warning `json:"warnings,omitempty"`

	// Beta is the (clamped) modification-rate estimate. Meaningful only when
	// Verdict == VerdictStego; for inconclusive results it is unreliable.
	Beta float64 `json:"beta"`

	// Confidence is the overall reliability of the estimate in [0,1].
	Confidence float64 `json:"confidence"`
}

Detection is the tri-state result of Detect: it carries the court-defensible Verdict plus the reliability context a caller needs to act on it. Unlike the boolean returned by DetectF5, it distinguishes "clean" from "inconclusive" and explains why an image could not be decided.

func Detect

func Detect(imageData []byte) (detection *Detection, err error)

Detect runs the full analysis and returns the tri-state Detection: the court-defensible Verdict, the β estimate, confidence, and the complete warnings list. Use this instead of DetectF5 when you need to tell "clean" from "inconclusive" or need the warnings that explain a verdict.

type Error

type Error struct {
	// Cause is the underlying error that caused this error, if any.
	// Used for error chain support via Unwrap().
	Cause error

	// Message is the human-readable error description.
	// This is the default English message used when no translator is configured.
	Message string

	// I18nKey is the translation key for localized error messages.
	// Keys follow the pattern: "error.<error_type>"
	// For example: "error.empty_histogram", "error.invalid_quality"
	I18nKey string

	// Code is the error category for programmatic handling.
	Code ErrorCode
}

Error represents an error from the steganalysis package.

This error type provides:

  • Structured error codes for programmatic handling
  • Human-readable messages with i18n support
  • Error chain support via Unwrap() for root cause analysis
  • i18n key for localized error messages

Example usage:

err := steganalysis.NewError(steganalysis.ErrEmptyHistogram, "histogram is empty")
if stegErr, ok := err.(*steganalysis.Error); ok {
    switch stegErr.Code {
    case steganalysis.ErrEmptyHistogram:
        // Handle empty histogram
    }
}

func ErrApplyBlur

func ErrApplyBlur(cause error) *Error

ErrApplyBlur wraps an error from blur application.

func ErrBetaOutOfRange

func ErrBetaOutOfRange() *Error

ErrBetaOutOfRange returns an error for beta value out of range.

func ErrCoeffExtraction

func ErrCoeffExtraction(cause error) *Error

ErrCoeffExtraction wraps a coefficient extraction error.

func ErrConvertToRGB

func ErrConvertToRGB(cause error) *Error

ErrConvertToRGB wraps an error from YCbCr to RGB conversion.

func ErrConvertToYCbCr

func ErrConvertToYCbCr(cause error) *Error

ErrConvertToYCbCr wraps an error from RGB to YCbCr conversion.

func ErrCropImage

func ErrCropImage(cause error) *Error

ErrCropImage wraps an error from image cropping.

func ErrCropTooLarge

func ErrCropTooLarge(cropSize, width, height int) *Error

ErrCropTooLarge returns an error when crop size is too large for the image.

func ErrDecodeFailed

func ErrDecodeFailed(cause error) *Error

ErrDecodeFailed wraps a JPEG decode error.

func ErrDecodeStegoImage

func ErrDecodeStegoImage(cause error) *Error

ErrDecodeStegoImage wraps an error from decoding stego image.

func ErrDimensionOverflow

func ErrDimensionOverflow(width, height int) *Error

ErrDimensionOverflow returns an error when dimension calculations would overflow.

func ErrDimensionTooLarge

func ErrDimensionTooLarge(value int, dimension string, maxValue int) *Error

ErrDimensionTooLarge returns an error when a dimension exceeds the maximum.

func ErrEmptyHistograms

func ErrEmptyHistograms() *Error

ErrEmptyHistograms returns an error for empty histogram input.

func ErrEncoderQualityFailed

func ErrEncoderQualityFailed(cause error) *Error

ErrEncoderQualityFailed wraps an encoder quality setting error.

func ErrEncodingFailed

func ErrEncodingFailed(cause error) *Error

ErrEncodingFailed wraps a JPEG encoding error.

func ErrEstimateBeta

func ErrEstimateBeta(cause error) *Error

ErrEstimateBeta wraps an error from beta estimation.

func ErrEstimateCoverHistogram

func ErrEstimateCoverHistogram(cause error) *Error

ErrEstimateCoverHistogram wraps an error from cover histogram estimation.

func ErrExtractCoverCoefficients

func ErrExtractCoverCoefficients(cause error) *Error

ErrExtractCoverCoefficients wraps an error from extracting cover coefficients.

func ErrExtractStegoCoefficients

func ErrExtractStegoCoefficients(cause error) *Error

ErrExtractStegoCoefficients wraps an error from extracting stego coefficients.

func ErrFinalQualityOutOfRange

func ErrFinalQualityOutOfRange() *Error

ErrFinalQualityOutOfRange returns an error for final quality out of range.

func ErrImageEmpty

func ErrImageEmpty() *Error

ErrImageEmpty returns an error for empty image data.

func ErrImageNil

func ErrImageNil() *Error

ErrImageNil returns an error for nil image data.

func ErrImageTooLarge

func ErrImageTooLarge(size, maxSize int) *Error

ErrImageTooLarge returns an error when image exceeds maximum size.

func ErrImageTooSmall

func ErrImageTooSmall(size, minSize int) *Error

ErrImageTooSmall returns an error when image data is smaller than minimum size.

func ErrIntermediateCompress

func ErrIntermediateCompress(cause error) *Error

ErrIntermediateCompress wraps an error from intermediate compression in the double compression calibration pipeline. This is the compress(Q_i) step.

func ErrIntermediateDecode

func ErrIntermediateDecode(cause error) *Error

ErrIntermediateDecode wraps an error from decoding the intermediate JPEG in the double compression calibration pipeline. This is the decompress step after compress(Q_i).

func ErrInvalidBetaValue

func ErrInvalidBetaValue() *Error

ErrInvalidBetaValue returns an error for invalid beta values (NaN/Inf).

func ErrInvalidChannels

func ErrInvalidChannels(channels int) *Error

ErrInvalidChannels returns an error for invalid channel count.

func ErrInvalidConfidence

func ErrInvalidConfidence() *Error

ErrInvalidConfidence returns an error for invalid confidence values (NaN/Inf).

func ErrInvalidDimensionsOrChannels

func ErrInvalidDimensionsOrChannels() *Error

ErrInvalidDimensionsOrChannels returns an error for invalid dimensions or channels.

func ErrInvalidEpsilon

func ErrInvalidEpsilon(epsilon float64) *Error

ErrInvalidEpsilon returns an error for invalid epsilon parameter.

func ErrInvalidImageDimensions

func ErrInvalidImageDimensions() *Error

ErrInvalidImageDimensions returns an error for invalid image dimensions.

func ErrInvalidMessageLength

func ErrInvalidMessageLength() *Error

ErrInvalidMessageLength returns an error for invalid message length values.

func ErrMatrixKOutOfRange

func ErrMatrixKOutOfRange() *Error

ErrMatrixKOutOfRange returns an error for matrix k parameter out of range.

func ErrMissingEOI

func ErrMissingEOI() *Error

ErrMissingEOI returns an error for missing EOI (End of Image) marker.

func ErrMissingSOI

func ErrMissingSOI() *Error

ErrMissingSOI returns an error for missing SOI marker.

func ErrNegativeCropSize

func ErrNegativeCropSize() *Error

ErrNegativeCropSize returns an error for negative crop size.

func ErrNilEncoder

func ErrNilEncoder() *Error

ErrNilEncoder returns an error when the JPEG encoder is nil.

func ErrNilPixelData

func ErrNilPixelData() *Error

ErrNilPixelData returns an error for nil pixel data.

func ErrNilRGBPixels

func ErrNilRGBPixels() *Error

ErrNilRGBPixels returns an error for nil RGB pixel data.

func ErrNilYCbCrPixels

func ErrNilYCbCrPixels() *Error

ErrNilYCbCrPixels returns an error for nil YCbCr pixel data.

func ErrNoQualityFactors

func ErrNoQualityFactors() *Error

ErrNoQualityFactors returns an error when no quality factors are provided.

func ErrNotBaselineJPEG

func ErrNotBaselineJPEG() *Error

ErrNotBaselineJPEG returns an error when JPEG is not baseline encoded. F5 steganalysis requires baseline JPEG (SOF0).

func ErrPanicInAnalyze

func ErrPanicInAnalyze() *Error

ErrPanicInAnalyze returns an error when a panic is recovered in Analyze().

func ErrPanicInDetectF5

func ErrPanicInDetectF5() *Error

ErrPanicInDetectF5 returns an error when a panic is recovered in DetectF5().

func ErrPixelCountTooLarge

func ErrPixelCountTooLarge(pixels, maxPixels int) *Error

ErrPixelCountTooLarge returns an error when total pixel count exceeds maximum.

func ErrPixelLengthMismatch

func ErrPixelLengthMismatch(actual, expected, width, height int) *Error

ErrPixelLengthMismatch returns an error when pixel data length doesn't match dimensions.

func ErrProgressiveTranscode

func ErrProgressiveTranscode(cause error) *Error

ErrProgressiveTranscode wraps a failure to losslessly transcode a progressive JPEG to baseline before analysis.

func ErrQualityOutOfRange

func ErrQualityOutOfRange() *Error

ErrQualityOutOfRange returns an error for quality factor out of range.

func ErrRecompress

func ErrRecompress(cause error) *Error

ErrRecompress wraps an error from image recompression.

func ErrStegoQualityOutOfRange

func ErrStegoQualityOutOfRange() *Error

ErrStegoQualityOutOfRange returns an error for stego quality out of range.

func ErrTestQualityOutOfRange

func ErrTestQualityOutOfRange() *Error

ErrTestQualityOutOfRange returns an error for test quality out of range.

func NewError

func NewError(code ErrorCode, message string) *Error

NewError creates a new Error with the given code and message. The i18n key is automatically derived from the error code.

Parameters:

  • code: Error category for programmatic handling
  • message: Human-readable error description (default English)

Returns:

  • *Error: The constructed error

Example:

err := NewError(ErrEmptyHistogram, "histogram contains no data")

func NewLocalizedError

func NewLocalizedError(code ErrorCode, i18nKey, defaultMsg string) *Error

NewLocalizedError creates an Error using an i18n key for the message. The key is looked up in the translation files; if not found, defaultMsg is used.

Parameters:

  • code: Error category for programmatic handling
  • i18nKey: Translation key (without "steganalysis." prefix)
  • defaultMsg: Fallback message if translation is not available

Returns:

  • *Error: The constructed error with translated message

func NewLocalizedErrorf

func NewLocalizedErrorf(code ErrorCode, i18nKey, defaultMsg string, args ...interface{}) *Error

NewLocalizedErrorf creates an Error with format arguments. The key is looked up in the translation files and formatted with args.

Parameters:

  • code: Error category for programmatic handling
  • i18nKey: Translation key (without "steganalysis." prefix)
  • defaultMsg: Fallback message format if translation is not available
  • args: Format arguments for the message

Returns:

  • *Error: The constructed error with translated and formatted message

func WrapError

func WrapError(code ErrorCode, message string, cause error) *Error

WrapError creates a new Error that wraps an underlying error. This preserves the error chain for debugging and root cause analysis.

Parameters:

  • code: Error category for programmatic handling
  • message: Human-readable error description (default English)
  • cause: The underlying error that caused this error

Returns:

  • *Error: The constructed error with wrapped cause

Example:

rootErr := jpeg.Decode(data)
if rootErr != nil {
    return WrapError(ErrExtractionFailed, "failed to decode JPEG", rootErr)
}

func WrapLocalizedError

func WrapLocalizedError(code ErrorCode, i18nKey, defaultMsg string, cause error) *Error

WrapLocalizedError wraps an underlying error with a localized message.

Parameters:

  • code: Error category for programmatic handling
  • i18nKey: Translation key (without "steganalysis." prefix)
  • defaultMsg: Fallback message if translation is not available
  • cause: The underlying error that caused this error

Returns:

  • *Error: The constructed error with wrapped cause

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface. Returns the translated message if a translator is configured, otherwise returns the default English message.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause of this error. This enables error chain inspection using errors.Is() and errors.As().

Example:

rootCause := errors.New("JPEG parse error")
err := steganalysis.WrapError(steganalysis.ErrExtractionFailed, "extraction failed", rootCause)
if errors.Is(err, rootCause) {
    // Handle root cause
}

type ErrorCode

type ErrorCode int

ErrorCode represents a category of steganalysis error. Error codes enable programmatic error handling and i18n key mapping.

const (
	// ErrEmptyHistogram indicates that a histogram contains no data.
	// This typically occurs when:
	//   - The image has no DCT coefficients in the analyzed range
	//   - The coefficient extraction failed silently
	//   - The histogram was not properly initialized
	ErrEmptyHistogram ErrorCode = iota + 1

	// ErrInvalidQuality indicates an invalid JPEG quality factor.
	// Valid quality factors are in the range [1, 100].
	// This error occurs when:
	//   - Quality factor is 0 or negative
	//   - Quality factor exceeds 100
	//   - Quality detection returns an implausible value
	ErrInvalidQuality

	// ErrInvalidBeta indicates an invalid modification rate (beta) value.
	// Valid beta values are in the range [0, 1].
	// This error occurs when:
	//   - Beta is negative (indicates estimation error)
	//   - Beta exceeds 1.0 (theoretically impossible)
	//   - Beta calculation produced NaN or Inf
	ErrInvalidBeta

	// ErrCalibrationFailed indicates that multi-QF calibration could not be completed.
	// This error occurs when:
	//   - No valid quality factors could be tested
	//   - All L2 distance calculations failed
	//   - The calibration histogram estimation pipeline failed
	ErrCalibrationFailed

	// ErrExtractionFailed indicates that DCT coefficient extraction failed.
	// This error occurs when:
	//   - The input is not a valid JPEG image
	//   - JPEG decoding failed
	//   - DCT coefficient access failed
	//   - Image data is corrupted
	ErrExtractionFailed

	// ErrValidationFailed indicates input validation failed.
	// This error occurs when:
	//   - Input parameters are invalid (nil, out of range, etc.)
	//   - Integer overflow would occur in dimension calculations
	//   - Buffer lengths don't match expected sizes
	ErrValidationFailed

	// ErrPanicRecovered indicates that a panic was recovered during processing.
	// This error occurs when:
	//   - An unexpected panic occurred during analysis
	//   - The panic was caught by a recovery handler
	//   - Internal state may be inconsistent
	ErrPanicRecovered

	// ErrInvalidResult indicates that computation produced invalid numeric results.
	// This error occurs when:
	//   - Beta calculation produced NaN or Inf
	//   - Confidence calculation produced NaN or Inf
	//   - Message length estimation produced negative values
	ErrInvalidResult
)

func (ErrorCode) String

func (e ErrorCode) String() string

String returns a human-readable name for the error code.

type HistogramCalibrator

type HistogramCalibrator interface {
	// CalculateL2Distance computes the L2 distance between observed stego histogram
	// and expected histogram based on cover estimate and modification rate.
	//
	// Formula (Section 4 from paper):
	//   E = sum_j[(H(0) - h(0) - beta*h(1))^2 + (H(j) - (1-beta)*h(j) - beta*h(j+1))^2]
	//
	// Parameters:
	//   - stegoHist: Observed stego-image histogram
	//   - coverHist: Estimated cover-image histogram
	//   - beta: Modification rate to test
	//
	// Returns:
	//   - distance: L2 distance (lower = better fit)
	//   - error: If beta is out of range [0, 1]
	CalculateL2Distance(stegoHist, coverHist DCTHistogram, beta float64) (float64, error)

	// CalibrateWithMultipleQF tests multiple quality factors to find the best beta estimate.
	//
	// This handles double-compressed images where the original quality factor
	// differs from the current quality factor.
	//
	// Parameters:
	//   - stegoImage: Complete JPEG file data
	//   - stegoQuality: Detected quality factor of the stego-image
	//   - stegoHistograms: Pre-computed stego-image histograms
	//   - qualityFactors: List of quality factors to test
	//
	// Returns:
	//   - *CalibrationResult: Contains best quality, calibrated beta, and confidence
	//   - error: If calibration fails
	CalibrateWithMultipleQF(
		stegoImage []byte,
		stegoQuality int,
		stegoHistograms []DCTHistogram,
		qualityFactors []int,
	) (*CalibrationResult, error)
}

HistogramCalibrator implements double compression detection and calibration.

JPEG images that have been compressed multiple times exhibit characteristic histogram artifacts. This interface detects double compression and adjusts the beta estimation accordingly by testing multiple quality factors.

The calibration process:

  1. For each test quality factor Q_i: a. Estimate cover histogram using Q_i calibration b. Calculate beta_i using least-squares c. Calculate L2 distance between stego and expected histograms
  2. Select the quality factor that minimizes L2 distance
  3. Return the calibrated beta for that quality factor

Implementations must be safe for concurrent use.

func NewHistogramCalibrator

func NewHistogramCalibrator(
	codecInst JPEGCodec,
	imageProc ImageProcessor,
	coeffExtract CoefficientExtractor,
) HistogramCalibrator

NewHistogramCalibrator creates a new histogram calibrator.

Parameters:

  • jpegCodec: JPEG encoder/decoder (also provides quality detection)
  • imageProc: Image processing operations
  • coeffExtract: DCT coefficient extractor

Returns:

  • HistogramCalibrator: A new histogram calibrator instance wrapped to root types

type HistogramEstimator

type HistogramEstimator interface {
	// EstimateCoverHistogram implements the complete crop-and-recompress pipeline.
	//
	// Parameters:
	//   - stegoImage: Complete JPEG file data of the suspected stego-image
	//
	// Returns:
	//   - stegoHistograms: DCT coefficient histograms from the original stego-image (3 low-frequency modes)
	//   - coverHistograms: DCT coefficient histograms from the recompressed (estimated cover) image (3 low-frequency modes)
	//   - quality: Detected JPEG quality factor of the original image
	//   - coverTotalP: Total non-zero luminance AC coefficients across ALL frequency modes in the estimated cover image.
	//     This is the global P needed for message length estimation per the Fridrich paper.
	//   - coverTotalAbsH1: Total |value|=1 luminance AC coefficients across ALL frequency modes in the estimated cover image.
	//     This is the global h(1) needed for shrinkage probability computation.
	//   - error: If any step of the pipeline fails
	//
	// Example:
	//   stegoHists, coverHists, quality, coverP, coverH1, err := estimator.EstimateCoverHistogram(jpegData)
	//   if err != nil { return err }
	//   fmt.Printf("Detected quality: %d, global P: %d\n", quality, coverP)
	EstimateCoverHistogram(stegoImage []byte) (
		stegoHistograms []DCTHistogram,
		coverHistograms []DCTHistogram,
		quality int,
		coverTotalP int,
		coverTotalAbsH1 int,
		err error,
	)
}

HistogramEstimator estimates the cover-image DCT histogram from a suspected stego-image.

This interface implements the Fridrich et al. crop-and-recompress method:

  1. Decode the stego-image to pixel domain
  2. Crop edges (removes block boundary artifacts)
  3. Apply slight blur (reduces quantization noise)
  4. Recompress with the same quality factor
  5. Extract DCT coefficients from recompressed image

The recompressed image serves as an estimate of what the original cover-image histogram would look like.

Implementations must be safe for concurrent use.

func NewHistogramEstimator

func NewHistogramEstimator(
	jpegCodec JPEGCodec,
	imageProc ImageProcessor,
	coeffExtract CoefficientExtractor,
) HistogramEstimator

NewHistogramEstimator creates a new histogram estimator with the required dependencies. The implementation lives in the estimator sub-package; this wrapper converts between root and estimator types.

type ImageProcessor

type ImageProcessor interface {
	// ConvertToYCbCr converts RGB pixels to YCbCr color space.
	//
	// Parameters:
	//   - rgbPixels: Raw RGB pixel data (row-major, 3 bytes per pixel)
	//   - width: Image width in pixels
	//   - height: Image height in pixels
	//
	// Returns:
	//   - ycbcrPixels: YCbCr pixel data (row-major, 3 bytes per pixel: Y, Cb, Cr)
	//   - error: If conversion fails
	ConvertToYCbCr(rgbPixels []byte, width, height int) (ycbcrPixels []byte, err error)

	// ConvertToRGB converts YCbCr pixels to RGB color space.
	//
	// Parameters:
	//   - ycbcrPixels: YCbCr pixel data (row-major, 3 bytes per pixel)
	//   - width: Image width in pixels
	//   - height: Image height in pixels
	//
	// Returns:
	//   - rgbPixels: Raw RGB pixel data (row-major, 3 bytes per pixel)
	//   - error: If conversion fails
	ConvertToRGB(ycbcrPixels []byte, width, height int) (rgbPixels []byte, err error)

	// CropImage shifts the image origin by cropSize pixels from the top-left
	// and returns the trimmed pixel buffer (the right and bottom edges are
	// preserved). This implements the Fridrich/Goljan/Hogea (2002) §3.2
	// crop-by-4-pixels step that misaligns the recompression DCT grid with
	// the original 8x8 block structure.
	//
	// Parameters:
	//   - pixels: Raw pixel data (row-major)
	//   - width: Current image width
	//   - height: Current image height
	//   - channels: Number of color channels (typically 3 for RGB or YCbCr)
	//   - cropSize: Number of pixels to shift the origin by (4 for the paper)
	//
	// Returns:
	//   - croppedPixels: Cropped pixel data
	//   - newWidth: Width after cropping (width - cropSize)
	//   - newHeight: Height after cropping (height - cropSize)
	//   - error: If crop size is too large or dimensions are invalid
	CropImage(pixels []byte, width, height, channels, cropSize int) (
		croppedPixels []byte, newWidth, newHeight int, err error,
	)

	// ApplyBlur applies a smoothing blur to reduce quantization noise.
	//
	// Parameters:
	//   - pixels: Raw pixel data (row-major)
	//   - width: Image width
	//   - height: Image height
	//   - channels: Number of color channels
	//   - epsilon: Blur strength parameter (typical value: 0.01)
	//
	// Returns:
	//   - blurredPixels: Blurred pixel data
	//   - error: If dimensions are invalid
	ApplyBlur(pixels []byte, width, height, channels int, epsilon float64) (
		blurredPixels []byte, err error,
	)
}

ImageProcessor provides image manipulation operations.

These operations are used in the crop-and-recompress pipeline to estimate the cover-image histogram. The interface abstracts image processing to enable different implementations and testing.

Implementations must be safe for concurrent use.

func NewImageProcessor

func NewImageProcessor() ImageProcessor

NewImageProcessor creates a new ImageProcessor wrapping the codec sub-package. Errors from the underlying codec are converted to *steganalysis.Error.

type JPEGCodec

type JPEGCodec interface {
	// Decode decompresses JPEG data to raw pixel values.
	//
	// The returned pixels are in RGB format, row-major order:
	//   - pixels[y*width*3 + x*3 + 0] = Red
	//   - pixels[y*width*3 + x*3 + 1] = Green
	//   - pixels[y*width*3 + x*3 + 2] = Blue
	//
	// Parameters:
	//   - data: Complete JPEG file data
	//
	// Returns:
	//   - pixels: Raw RGB pixel data
	//   - width: Image width in pixels
	//   - height: Image height in pixels
	//   - error: If decoding fails or data is not valid JPEG
	Decode(data []byte) (pixels []byte, width, height int, err error)

	// Encode compresses raw pixel data to JPEG format.
	//
	// The input pixels must be in RGB format, row-major order.
	//
	// Parameters:
	//   - pixels: Raw RGB pixel data
	//   - width: Image width in pixels
	//   - height: Image height in pixels
	//   - quality: JPEG quality factor (1-100)
	//
	// Returns:
	//   - data: Complete JPEG file data
	//   - error: If encoding fails or dimensions are invalid
	Encode(pixels []byte, width, height, quality int) (data []byte, err error)

	// QualityFactor returns the detected quality factor of the last decoded image.
	//
	// This method must be called after Decode() to get meaningful results.
	// The quality factor is estimated from the quantization tables.
	//
	// Returns:
	//   - quality: Estimated quality factor (1-100), or 0 if not detected
	QualityFactor() int

	// IsBaseline returns whether the last decoded image was baseline JPEG.
	//
	// This method must be called after Decode() to get meaningful results.
	// F5 steganalysis only works on baseline JPEGs (SOF0).
	//
	// Returns:
	//   - baseline: True if baseline JPEG, false otherwise
	IsBaseline() bool
}

JPEGCodec provides JPEG encoding, decoding, and analysis operations.

This interface abstracts the JPEG codec to enable dependency injection and testing with mock implementations. It combines encoding/decoding with quality detection to provide a unified JPEG processing interface.

Implementations must be safe for concurrent use.

func NewJPEGCodec

func NewJPEGCodec() JPEGCodec

NewJPEGCodec creates a new JPEGCodec adapter wrapping the codec sub-package. Errors from the underlying codec are converted to *steganalysis.Error.

type MatrixKCandidate

type MatrixKCandidate struct {
	K                    int  `json:"k"`
	MessageBits          int  `json:"message_bits"`
	MessageBytes         int  `json:"message_bytes"`
	PayloadBits          int  `json:"payload_bits"`
	PayloadBytes         int  `json:"payload_bytes"`
	ModifiedCoefficients int  `json:"modified_coefficients"`
	SelfConsistent       bool `json:"self_consistent"`
	FitsCapacity         bool `json:"fits_capacity"`
}

MatrixKCandidate is a per-k message-size hypothesis emitted when running in auto-k mode. Compare candidates by SelfConsistent (Fridrich §3.3: embed-side and analysis-side agree on this k) and FitsCapacity (the cover image actually has enough usable coefficients for the predicted message at this k).

type MessageLengthEstimate

type MessageLengthEstimate struct {
	// Candidates holds per-k message-size hypotheses produced when the
	// caller used WithAutoMatrixK. Each element mirrors the headline
	// fields above for one candidate k value, plus a SelfConsistent flag
	// indicating whether F5's own optimal-k selection algorithm would
	// pick that k for the predicted message size. Nil when auto-k mode
	// is not enabled.
	Candidates []MatrixKCandidate `json:"candidates,omitempty"`

	// MessageBits is the estimated total bit length from Fridrich §3.3:
	// M̂ = W(k) · β · (P − h(1)). The F5 header (32 bits — encrypted k
	// and message length, Westfeld 2001 §3) is embedded via direct
	// coefficient modification, not matrix encoding, but its modifications
	// are indistinguishably counted in β, so M̂ may be slightly inflated.
	// Use this to validate against the paper formula or against capacity;
	// use PayloadBits for the user-visible plaintext length.
	MessageBits int `json:"message_bits"`

	// MessageBytes = MessageBits / 8. Includes header.
	MessageBytes int `json:"message_bytes"`

	// PayloadBits is the user-visible plaintext bit length: MessageBits
	// minus the 32-bit F5 header. Use this when comparing the estimate
	// against a known plaintext file size. Clamped to zero when M < 32.
	PayloadBits int `json:"payload_bits"`

	// PayloadBytes = PayloadBits / 8. Excludes header.
	PayloadBytes int `json:"payload_bytes"`

	// UsableCoefficients is the total number of non-zero coefficients.
	// These are the coefficients that can potentially carry hidden data.
	UsableCoefficients int `json:"usable_coefficients"`

	// ModifiedCoefficients is the estimated number of coefficients that were modified.
	// Calculated as: ModificationRate × UsableCoefficients × (1 − ShrinkageProbability)
	ModifiedCoefficients int `json:"modified_coefficients"`

	// MatrixEmbeddingK is the matrix embedding parameter k used in the estimate
	// (valid F5 range 1–7). A value of 0 means UNDETERMINED: β diverged or
	// saturated for this image, so there is no reliable k or message length —
	// MessageBits/Bytes and PayloadBits/Bytes are 0 and BelowReliableFloor is
	// true. Inspect Candidates for the per-k hypothesis space in that case. Do
	// not treat k=0 as "no matrix encoding"; treat it as "not recoverable."
	MatrixEmbeddingK int `json:"matrix_embedding_k"`

	// MatrixEmbeddingEfficiency is W(k) = k * 2^k / (2^k - 1).
	// This represents how many bits are embedded per coefficient modification.
	MatrixEmbeddingEfficiency float64 `json:"matrix_embedding_efficiency"`

	// ShrinkageProbability is the estimated probability that a modified coefficient
	// "shrinks" to zero. This happens when a coefficient with value +/-1 is modified.
	ShrinkageProbability float64 `json:"shrinkage_probability"`

	// ModificationRate is the beta value used for this estimate.
	ModificationRate float64 `json:"modification_rate"`

	// ReliableFloor is the β value below which this estimate is considered
	// unreliable: the Fridrich cover-histogram reconstruction error (the
	// "phantom β floor", ~0.05 at the default 4-px crop, lower with
	// WithCoverCrop(1)) dominates the true modification signal. It is a
	// property of the crop setting, not the image.
	ReliableFloor float64 `json:"reliable_floor"`

	// BelowReliableFloor is true when ModificationRate ≤ ReliableFloor. When
	// set, MessageBits/Bytes, PayloadBits/Bytes and MatrixEmbeddingK are still
	// populated but should be treated as an order-of-magnitude guess, not a
	// trustworthy figure — the same condition raises WarningLowBetaFloor. This
	// is the honest signal for "β is too small for this method to resolve a
	// length or k," which otherwise manifests as a confidently-wrong number.
	BelowReliableFloor bool `json:"below_reliable_floor"`
}

MessageLengthEstimate contains the estimated hidden message size.

This estimate is based on the modification rate (beta), matrix embedding efficiency, and shrinkage probability.

JSON serialization is supported for reporting and interoperability.

func (MessageLengthEstimate) MarshalJSON

func (m MessageLengthEstimate) MarshalJSON() ([]byte, error)

MarshalJSON emits MessageLengthEstimate as JSON with all float64 fields serialized as floating-point numbers (e.g. "0.0" rather than "0"). The value receiver is required to satisfy json.Marshaler for both MessageLengthEstimate and *MessageLengthEstimate values; a pointer receiver would skip the custom marshaling when a value is encoded.

type MessageLengthEstimator

type MessageLengthEstimator interface {
	// EstimateMessageLength calculates the estimated hidden message size.
	//
	// The formula: M = W(k) * m, where:
	//   - M = message length in bits
	//   - W(k) = matrix embedding efficiency = k * 2^k / (2^k - 1)
	//   - m = number of modified coefficients = beta * P * (1 - P_S)
	//   - P = total usable (non-zero) AC coefficients from ALL modes in the estimated cover image
	//   - P_S = shrinkage probability = h(1) / P, where h(1) is from ALL AC modes
	//
	// Parameters:
	//   - beta: Estimated modification rate from BetaEstimator
	//   - stegoHistograms: DCT coefficient histograms from stego-image (3 low-frequency modes, for validation)
	//   - coverHistograms: DCT coefficient histograms from estimated cover (3 low-frequency modes, for validation)
	//   - matrixK: Matrix embedding parameter k (1-9 per F5 spec, Westfeld 2001)
	//   - coverTotalP: Total non-zero luminance AC coefficients across ALL frequency modes in the estimated cover image
	//   - coverTotalAbsH1: Total |value|=1 luminance AC coefficients across ALL frequency modes in the estimated cover image
	//
	// Returns:
	//   - *MessageLengthEstimate: Contains message size in bits/bytes and intermediate values
	//   - error: If inputs are invalid
	EstimateMessageLength(
		beta float64,
		stegoHistograms, coverHistograms []DCTHistogram,
		matrixK int,
		coverTotalP int,
		coverTotalAbsH1 int,
	) (*MessageLengthEstimate, error)

	// CalculateMatrixEfficiency returns the matrix embedding efficiency W(k).
	//
	// Formula: W(k) = k * 2^k / (2^k - 1)
	//
	// Values for common k:
	//   - k=1: W(1) = 2.0 (1 change embeds 1 bit)
	//   - k=2: W(2) = 8/3 = 2.67 (1 change embeds ~2.67 bits)
	//   - k=3: W(3) = 24/7 = 3.43 (1 change embeds ~3.43 bits)
	//   - k=4: W(4) = 64/15 = 4.27 (1 change embeds ~4.27 bits)
	//   - k=9: W(9) = 4608/511 = 9.02 (1 change embeds ~9.02 bits)
	//
	// Parameters:
	//   - k: Matrix embedding parameter (must be 1-9, per F5 spec)
	//
	// Returns:
	//   - efficiency: Matrix embedding efficiency
	//   - error: If k is out of valid range
	CalculateMatrixEfficiency(k int) (float64, error)
}

MessageLengthEstimator estimates the hidden message length from beta and histograms.

Once the modification rate (beta) is known, we can estimate how many bits of hidden data are embedded in the image. This takes into account:

  • Matrix embedding efficiency (F5 uses (1, 2^k-1, k) codes)
  • Shrinkage probability (coefficients that become zero after modification)
  • Total usable coefficients in the image

The P (total non-zero coefficients) and h(1) (total |value|=1 coefficients) values used in the formula are computed from ALL AC frequency modes in the estimated cover image, not just the 3 modes used for beta estimation. This matches the Fridrich paper's definition.

Implementations must be safe for concurrent use.

func NewMessageLengthEstimator

func NewMessageLengthEstimator() MessageLengthEstimator

NewMessageLengthEstimator creates a new message length estimator. The implementation lives in the estimator sub-package; this wrapper converts between root and estimator types.

type Option

type Option func(*analyzerConfig)

Option is a function that configures the analyzer. Options are passed to Analyze() to customize behavior.

func WithAutoMatrixK

func WithAutoMatrixK() Option

WithAutoMatrixK enables F5 optimal-k inference per Fridrich/Goljan/Hogea (2002) §3.3 and Liu et al. (2020) Eq.(8). The analyzer iterates k = 1..8, computes the implied message size at each k from the estimated β, and selects the k that F5's own SelectOptimalK algorithm would have chosen at embed time for that message size. The per-k candidate list is exposed via AnalysisResult.MessageLength.Candidates so callers can see all hypotheses, not just the headline pick.

Auto-k is ON by default. This option exists to re-enable it after a WithMatrixK call (WithMatrixK pins k literally and turns auto-k off); calling it on a fresh analyzer is a no-op.

When β has saturated (BetaEstimationResult.Saturated == true) the message length is not reported at all (the formula diverges), but the candidate list is still surfaced via AnalysisResult.MessageLength.Candidates for manual inspection.

func WithBackend

func WithBackend(b HistogramEstimator) Option

WithBackend installs a custom HistogramEstimator implementation as the crop-and-recompress backend. This is the integration point for alternate backends — for example a GPU/nvJPEG implementation that batches many images per call, or a fixture-playback backend used in tests.

The supplied backend MUST be safe for concurrent use: Analyze and Stream may invoke it from many goroutines.

When this option is not supplied OR is called with a nil backend, the default CPU implementation built on the in-tree codec/estimator/imageprocessor stack is used. The nil-passthrough lets callers wire up a GPU backend conditionally without an extra branch:

gpuBackend, _ := gpu.New() // nil on stub builds or no-device hosts
result, err := steganalysis.Analyze(jpeg, steganalysis.WithBackend(gpuBackend))

func WithCoverBlurEpsilon

func WithCoverBlurEpsilon(epsilon float64) Option

WithCoverBlurEpsilon sets the 3×3 lowpass strength applied to the cropped cover estimate before recompression (paper §3.2's B matrix, B22=1−4ε). The package default is estimator.DefaultBlurEpsilon (0.01). ε=0 skips blurring.

func WithCoverCrop

func WithCoverCrop(pixels int) Option

WithCoverCrop sets the edge crop (in pixels) used by the Fridrich §3.2 cover-histogram estimator. The package default is estimator.DefaultCropPixels (4), which the paper specifies and which maximises clean-vs-stego DETECTION separation.

Pass WithCoverCrop(1) when you want the most accurate β MAGNITUDE — and hence the tightest message-length / matrix-k estimate — on an image you already believe carries an F5 payload. Crop=1 measurably lowers the positive β floor and roughly halves the β-magnitude error (median |β̂−β_true| 0.110→0.065 across a Q75/85/95 sweep), trading ~32% of the detection separation. crop=0 disables the edge crop entirely (preserves block alignment); values >4 are accepted but rarely help.

func WithLogger

func WithLogger(l logger.Logger) Option

WithLogger sets a per-call logger for this analysis.

func WithMatrixK

func WithMatrixK(k int) Option

WithMatrixK sets the matrix embedding parameter for message length estimation and disables auto-k inference. Use this when you know the exact k F5 used.

Auto-k inference (Fridrich §3.3 + Liu Eq.(8)) is on by default; calling WithMatrixK pins k to the supplied value and turns auto-k off. If you want the literal k to act as a hint while still running auto-k, set both WithMatrixK and WithAutoMatrixK in that order — WithAutoMatrixK flips auto-k back on.

func WithProgressiveSupport

func WithProgressiveSupport(enabled bool) Option

WithProgressiveSupport controls whether a progressive (SOF2) JPEG is losslessly transcoded to baseline (SOF0) before analysis.

It is enabled by default. Passing false restores the historical strict behavior in which progressive input is rejected with ErrNotBaselineJPEG. The transcode preserves every quantized DCT coefficient and the quantization tables, so detection on a normalized progressive carrier yields the identical β as on the baseline original.

func WithQualityFactors

func WithQualityFactors(qualityFactors []int) Option

WithQualityFactors enables the Fridrich §4 multi-quality double-compression calibrator and supplies the candidate PRIMARY quality factors to sweep.

When the cover was already a JPEG before F5 recompressed it (the common case for images pulled off the web), the single-compression β estimate is catastrophically biased — it typically saturates toward 1. The calibrator inserts a "compress at candidate-primary-Q / decompress" step before the table-matched recompress, computes the L2 fit of Fridrich Eq.(1) against the observed stego histogram for each candidate, and adopts the β from the best-fitting primary Q. On Q75→Q92 double-compressed stego this cuts the median β error from ~0.94 to ~0.08 (measured); the result's Calibration field carries the per-Q L2 distances, the recovered BestQuality, and the IsDoubleCompressed flag, and a WarningDoubleCompression is emitted when it fires.

Pass a sweep that brackets the plausible primary qualities, e.g. []int{60,65,70,75,80,85,90}. The cost is one extra crop/recompress per candidate, so keep the list focused. Nil/empty keeps the fast single-compression path (no calibration).

func WithSimpleBeta

func WithSimpleBeta() Option

WithSimpleBeta switches the per-mode β estimation from Fridrich Eq.(3) (full 2D least-squares projection) to the H(0)-only simple formula β = (H(0)-h(0))/h(1) with optional shrinkage correction.

When to use

Always, on natural-photo JPEGs. The simple formula is the dominant term of Eq.(3) in this regime (weight w₀ ≈ 0.83 on a Q=85 photographic image, > 0.99 on more deeply-quantized images), so dropping the y₁/v₁ correction loses very little. In our experiments it's actually a tad MORE accurate than Eq.(3) — the v₁ term tends to amplify cover-histogram- estimate noise more than it suppresses it.

When NOT to use

On synthetic / textureless / non-photographic covers where h(1) and h(2) might be of comparable magnitude, the v₁ correction in Eq.(3) matters more. Stick with the default in that case (or run both and compare via AnalysisResult.PerModeBetas).

Speed

The β formula is not the steganalysis hot path — the cover-histogram reconstruction (crop + lowpass + recompress) dominates by ~99%. So this option is more about REASONING about β (the simple formula is much easier to inspect) than about wall-clock speed. Where it does pay off is in batch screening of many images where you care about the per-mode β rather than the headline single number.

func WithThreshold

func WithThreshold(threshold float64) Option

WithThreshold sets a custom beta threshold for clean/stego classification.

type QualityReliability

type QualityReliability string

QualityReliability indicates how reliable the analysis results are based on the detected JPEG quality factor. The Fridrich steganalysis method's accuracy depends heavily on quality factor because the crop-and-recompress calibration requires sufficient quantization to produce distinguishable histograms.

The reliability tiers are based on the Fridrich paper's observations and empirical testing across Q=1-100.

const (
	// ReliabilityOptimal indicates the quality factor is within the paper's
	// validated working range (Q=60-91). Detection and beta estimation are
	// reliable at these quality levels.
	ReliabilityOptimal QualityReliability = "optimal"

	// ReliabilityDegraded indicates the quality factor is below the paper's
	// recommended range (Q=33-59). Detection works but with reduced accuracy.
	// The paper notes that quality factors below 60 produce less accurate results.
	ReliabilityDegraded QualityReliability = "degraded"

	// ReliabilityUnreliable indicates the quality factor is too high for
	// reliable calibration (Q=92-100). Near-lossless quantization means the
	// crop-and-recompress pipeline cannot effectively distinguish stego
	// histograms from cover histograms.
	ReliabilityUnreliable QualityReliability = "unreliable"

	// ReliabilityInsufficient indicates the quality factor is too low for
	// F5 steganography to function (Q=1-32). At these quality levels,
	// aggressive quantization zeros out nearly all AC coefficients, leaving
	// insufficient capacity for embedding.
	ReliabilityInsufficient QualityReliability = "insufficient"

	// ReliabilityUnknown indicates the quality factor could not be determined
	// (Q=0 from quality detection). Results should be treated with caution.
	ReliabilityUnknown QualityReliability = "unknown"
)

type Signature

type Signature struct {
	// Name is a stable machine-readable identifier (e.g. "weeks_com").
	Name string `json:"name"`
	// Description explains what the signature checks for.
	Description string `json:"description"`
	// Detail is a human-readable note about this image's result.
	Detail string `json:"detail"`
	// Weight is this signature's contribution to the report Score when matched.
	Weight float64 `json:"weight"`
	// Matched is true when the signature fired for this image.
	Matched bool `json:"matched"`
}

Signature is a single structural indicator checked against an image. A match is not by itself proof of F5; the signatures are additive clues that, taken together with the statistical β estimate, describe whether an image is a plausible F5 carrier and what encoder produced it.

type SignatureReport

type SignatureReport struct {
	// Subsampling is the detected chroma subsampling label (e.g. "4:2:0").
	Subsampling string `json:"subsampling"`
	// Signatures lists every checked signature with its result.
	Signatures []Signature `json:"signatures"`
	// Score is the sum of matched signature weights, in [0,1]. Higher means the
	// image looks more like a James/f5.jar-produced JPEG that could carry F5.
	Score float64 `json:"score"`
	// StandardQuality is the IJG quality factor matched by the luminance
	// quantization table, or 0 if the table is non-standard.
	StandardQuality int `json:"standard_quality"`
	// CarrierViable is true only if F5 could physically be present: the format
	// is decodable and (for baseline) there is real embedding capacity. When
	// false, any positive β is noise — F5 cannot live here.
	CarrierViable bool `json:"carrier_viable"`
	// Baseline is true for a baseline (SOF0) frame. A baseline frame still carries
	// its original encoder's markers, so a low Score on a baseline frame is strong
	// evidence the file is NOT f5.jar/PixelKnot output. A progressive frame may be
	// a losslessly transcoded carrier whose marker fingerprint was rewritten, so a
	// low Score there is not conclusive.
	Baseline bool `json:"baseline"`
}

SignatureReport is the aggregate of all structural signatures for one image.

func Signatures

func Signatures(imageData []byte) (*SignatureReport, error)

Signatures runs the structural signature checks over a JPEG byte stream. It parses only the marker structure and (for the capacity check) the quantized coefficients; it does not run the statistical β estimator. It returns an error only when the input is not a JPEG at all.

type Stream

type Stream struct {
	In  chan<- StreamRequest
	Out <-chan StreamResult
}

Stream is a parallel streaming analyzer. Callers send StreamRequests on In and receive StreamResults on Out. The two channels run independently: results may arrive in a different order than requests were sent, so always match by StreamRequest.ID / StreamResult.ID.

Lifecycle:

  • Close In to signal "no more requests". Workers drain remaining items, then Out is closed automatically.
  • Cancel the context passed to NewStream to abort. In-flight requests finish (they're not interruptible), the input channel is drained without processing the rest, and Out closes.

Stream is safe for many producers writing to In and many consumers reading from Out concurrently.

func NewStream

func NewStream(ctx context.Context, workers int, opts ...Option) *Stream

NewStream creates a Stream backed by `workers` parallel goroutines, each calling Analyze on incoming requests. The supplied opts are passed to every Analyze call (they configure per-image behavior such as the backend, threshold, k inference, etc.).

If workers <= 0, runtime.NumCPU() workers are spawned. The internal channel buffer is workers*2 in each direction — small enough to give callers natural backpressure when downstream consumption stalls.

The returned Stream's In channel is owned by the caller: close it when done sending. The Out channel is owned by the Stream and will be closed after the worker pool has drained.

type StreamRequest

type StreamRequest struct {
	ID    string
	Image []byte
}

StreamRequest is one image submitted to a Stream for analysis.

ID is opaque to the analyzer — it is echoed back unchanged on the StreamResult so callers can correlate input/output without relying on channel ordering. Image is the JPEG byte slice; the analyzer does not retain a reference to it after the result is sent.

type StreamResult

type StreamResult struct {
	Err    error
	Result *AnalysisResult
	ID     string
}

StreamResult is the outcome of analyzing one StreamRequest.

ID matches the originating StreamRequest.ID. Exactly one of Result or Err is non-nil.

type TranslatorProvider

type TranslatorProvider interface {
	// Translate returns the translated string for the given key.
	Translate(key string) string

	// TranslateWithArgs returns the translated string with format arguments.
	TranslateWithArgs(key string, args ...interface{}) string

	// HasKey returns true if the translation key exists.
	HasKey(key string) bool

	// SetLocale changes the current locale.
	SetLocale(locale string)

	// GetLocale returns the current locale.
	GetLocale() string
}

TranslatorProvider defines the interface for translation providers. This allows loose coupling - the package works with or without i18n. The interface matches github.com/0verkilll/i18n.Translator.

func GetTranslator

func GetTranslator() TranslatorProvider

GetTranslator returns the global translator, or nil if not set.

func NewTranslator

func NewTranslator(locale string) (TranslatorProvider, error)

NewTranslator creates a new translator configured for the steganalysis package. If locale is empty, it will auto-detect from environment variables. The translator uses embedded locale files and the i18n package for translation.

type Verdict

type Verdict string

Verdict is the tri-state classification of an analyzed image. Unlike the boolean IsClean it distinguishes a confident "clean" and a confident "stego detected" from the INCONCLUSIVE case, where the Fridrich cover- histogram reconstruction diverged (saturated β, implausibly-high β, or a quality factor outside the method's reliable range) and the headline number cannot be trusted in either direction.

This distinction is the difference between a court-defensible result and a false accusation: the vast majority of clean web images (re-saved, high-Q, small, or resized) trip the reconstruction, and reporting that as "stego detected" is the dominant false-positive source. Inconclusive says "this method cannot decide for this image", which is the honest answer.

const (
	// VerdictClean means β is confidently below the detection threshold and the
	// reconstruction was reliable. No F5 payload indicated.
	VerdictClean Verdict = "clean"

	// VerdictStego means β is at/above the detection threshold AND the cover-
	// histogram reconstruction was reliable (not saturated, β ≤ MaxPlausibleF5Beta,
	// quality in the reliable range). This is the only verdict that asserts a
	// hidden payload.
	VerdictStego Verdict = "stego_detected"

	// VerdictInconclusive means the reconstruction is untrustworthy for this
	// image, so neither "clean" nor "detected" can be asserted. Causes: raw β
	// saturated outside [0,1]; β above the F5 full-capacity ceiling
	// (MaxPlausibleF5Beta); or a quality factor outside the method's reliable
	// range (Q≤32, Q>88, or undetected). The reason is recorded in
	// AnalysisResult.InconclusiveReason and the Warnings list.
	VerdictInconclusive Verdict = "inconclusive"
)

type Warning

type Warning struct {
	// Code categorizes the warning for programmatic handling.
	Code WarningCode `json:"code"`

	// Message provides a human-readable description of the warning.
	// Messages support i18n when a translator is configured.
	Message string `json:"message"`
}

Warning represents a non-fatal issue detected during analysis. Warnings inform callers about conditions that may affect result reliability without preventing the analysis from completing.

type WarningCode

type WarningCode string

WarningCode categorizes analysis warnings for programmatic handling. Callers can filter or act on specific warning codes without parsing messages.

const (
	// WarningQualityLow indicates the quality factor is below the recommended
	// range, resulting in degraded detection accuracy.
	WarningQualityLow WarningCode = "quality_low"

	// WarningQualityVeryLow indicates the quality factor is far below the
	// recommended range, resulting in significantly degraded accuracy.
	WarningQualityVeryLow WarningCode = "quality_very_low"

	// WarningQualityHigh indicates the quality factor is too high for reliable
	// calibration. Near-lossless compression limits the statistical signal.
	WarningQualityHigh WarningCode = "quality_high"

	// WarningInsufficientCoefficients indicates the quality factor is so low
	// that there are insufficient non-zero AC coefficients for meaningful analysis.
	WarningInsufficientCoefficients WarningCode = "insufficient_coefficients"

	// WarningQualityUnknown indicates the quality factor could not be determined.
	// Results should be treated with caution when quality detection fails.
	WarningQualityUnknown WarningCode = "quality_unknown"

	// WarningHighBetaVariance indicates that per-mode beta estimates show high
	// variance (coefficient of variation > threshold). This suggests the
	// crop-and-recompress calibration may be inaccurate, often occurring with
	// non-photographic images (cartoons, illustrations). When this warning is
	// present, the message length estimate uses the minimum per-mode beta
	// instead of the average for improved accuracy.
	WarningHighBetaVariance WarningCode = "high_beta_variance"

	// WarningBetaSaturated indicates that the unclamped β estimate fell
	// outside [0, 1] before the public-facing clamp was applied. This
	// signals that the cover-image histogram reconstruction has diverged
	// (typical on small images, very high quality factors, or double JPEG
	// compression) and the headline message-length estimate is unreliable.
	// The underlying RawBeta is exposed via the analyzer's debug logging.
	WarningBetaSaturated WarningCode = "beta_saturated"

	// WarningAutoMatrixKNotConsistent indicates that auto-k inference per
	// Fridrich §3.3 found no self-consistent k — none of k=1..8 satisfies
	// "F5's own optimal-k rule would have picked this k for the implied
	// message size." Usually co-occurs with WarningBetaSaturated; the
	// per-k candidate list is still populated for manual inspection.
	WarningAutoMatrixKNotConsistent WarningCode = "auto_matrix_k_not_consistent"

	// WarningDoubleCompression indicates that the §4 multi-quality calibrator
	// (enabled via WithQualityFactors) found the best-fitting primary quality
	// factor differs significantly from the stego quality, i.e. the cover was
	// JPEG-compressed before F5 recompressed it. The headline β is the
	// calibrated estimate; see Calibration for the per-Q L2 distances.
	WarningDoubleCompression WarningCode = "double_compression"

	// WarningBetaImplausiblyHigh indicates the estimated β exceeds the F5
	// full-capacity ceiling (~0.5; see MaxPlausibleF5Beta). No genuine F5 embed
	// modifies that fraction of coefficients, so the value means the cover-
	// histogram reconstruction diverged for this image — the headline message
	// length and k are suppressed and only the per-k candidate hypothesis space
	// is published. Common on small, high-quality, or app-resized carriers
	// (e.g. PixelKnot output) where the Fridrich crop estimate breaks down.
	WarningBetaImplausiblyHigh WarningCode = "beta_implausibly_high"

	// WarningInconclusive indicates the overall verdict is VerdictInconclusive:
	// the cover-histogram reconstruction diverged or the quality factor is
	// outside the reliable range, so neither "clean" nor "stego detected" can be
	// asserted for this image. The specific cause is in
	// AnalysisResult.InconclusiveReason. This is the honest, court-defensible
	// outcome for images the Fridrich method cannot resolve — most clean web
	// images (re-saved / high-Q / small / resized) land here rather than being
	// falsely reported as stego.
	WarningInconclusive WarningCode = "inconclusive"

	// WarningLowBetaFloor indicates the estimated β sits at/below the Fridrich
	// method's intrinsic noise floor (~0.05 at the default 4-px crop). Below
	// the floor the cover-histogram reconstruction error dominates the signal,
	// so the message-length and matrix-k numbers — though reported — are not
	// reliable. Raise β above the floor (larger payloads) or treat the figures
	// as an upper-bounded guess. See MessageLengthEstimate.BelowReliableFloor.
	WarningLowBetaFloor WarningCode = "low_beta_floor"
)

Directories

Path Synopsis
internal
calibration
Package calibration implements double compression detection and multi-quality-factor calibration for JPEG steganalysis.
Package calibration implements double compression detection and multi-quality-factor calibration for JPEG steganalysis.
codec
Package codec provides the JPEG adapter layer for the steganalysis package.
Package codec provides the JPEG adapter layer for the steganalysis package.
estimator
Package estimator implements the core estimation algorithms for F5 steganalysis.
Package estimator implements the core estimation algorithms for F5 steganalysis.
jpegscan
Package jpegscan is a lightweight, allocation-frugal JPEG marker scanner used by the detection-signature layer.
Package jpegscan is a lightweight, allocation-frugal JPEG marker scanner used by the detection-signature layer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL