imagehide / design

INN encoder β€” design rationale

locked 2026-04-25 v3 patch-tile
Highlight any text β†’ πŸ’¬ comment. Publishes to GitHub Issues; optional πŸ€– Ask Claude to fix tag for action items.

Why patch-tile adapter, Ξ±=1 fixed, smooth Ξ±-ramp curriculum, single canonical trim β€” and what was tried and rejected. Companion to docs/inn_arch_notes.md + docs/arch_notes.md.

Β§1Goal

Carry an 896-bit data payload (ECC-encoded to 1024 b) in an RGB cover image. Training is fixed at 256Γ—256 for compute budget reasons; the deployment target is real photos at native capture resolution (12 MP-class / 1024Β²+), and the patch-tile design is resolution-scaling by construction (Β§3).

requirementtargetdeployed (idea097/d001)
bit accuracyβ‰₯ 0.99 on WeChat chain @ 1024Γ—1024 (worst real-platform pipeline at native capture scale); β‰₯ 0.95 floor across the full 14-attack Γ— 7-size sweep. Crop / rotate / large resize / noise / blur = reject by design.0.992 on chain_wechat @ 1024Β² (β‰ˆ target); avg 0.995 / min 0.955 over 7-size sweep; 0.999 / 0.992 at 1024Β² alone
container fidelity vs coverindistinguishable to human eyes β€” both high PSNR and high SSIM (PSNR alone misses structural artefacts that SSIM catches)PSNR 37.4 dB Β· SSIM 0.940 (clean, 1024Β²)
inference≀ 1 s per 12 MP image on a flagship phone NPU (~17 TFLOPS fp16)~25 ms est (12 MP fp16) β€” 40Γ— under budget
model size≀ TrustMark Q (21 M params, 85 MB)0.167 M params Β· 0.7 MB fp32 (130Γ— under)

"mild blur" was an earlier draft target. The current threat model is compression-only (see Β§6); blur is intentionally out of scope alongside crop / rotate / noise. The eval suite reflects this β€” no blur, no Gaussian noise, no geometric attacks.

Β§2Architecture

Two halves of a coupling INN are invertible only if both halves of the output are kept. Steganography drops half (z) at the encoder; the verifier supplies zeros and the network learns to make the bits recoverable from the container alone.

                EMBED (camera)                              EXTRACT (verifier)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
bits ─→ β”‚ PatchBitAdapter (fixed)    β”‚  attack β”‚ PatchBitAdapter (fixed)          β”‚ ─→ bits'
host β†’  β”‚ Haar DWT                   β”‚  ───→   β”‚ Haar DWT (same)                  β”‚ ←─ container
        β”‚ concat host_dwt βŠ• secret   β”‚         β”‚ concat c_dwt βŠ• zeros (z=0)       β”‚
        β”‚ DKiS INN β€” 8 or 16 fwd     β”‚         β”‚ DKiS INN β€” 8 or 16 reverse       β”‚
        β”‚ split β†’ keep 1st half      β”‚         β”‚ split β†’ keep 2nd half            β”‚
        β”‚ inverse Haar DWT           β”‚         β”‚                                  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β–Ό
                  container ──── transmission / attacks β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why DWT first

Edges and texture (LH / HL / HH subbands) have low energy in natural images and survive JPEG well β€” ideal hosts for low-amplitude bit signal. LL is left mostly untouched so the cover's structure is preserved. Haar DWT is reversible by construction with zero learnable params.

Why coupling INN

Strict invertibility by construction: x1, x2 = INN_reverse(y1, y2) is exact arithmetic, no learning involved. The decoder is the encoder run backwards β€” no asymmetric learned encoder/decoder pair to fall out of sync.

DKiS coupling-block math
# forward (DKiS_block.forward)
y1 = x1 + Ξ± Β· f(x2)           # host absorbs residual from secret
x2 = x2 Β· k1                  # fixed sign mask
x2 = shuffle(x2, k2)          # fixed channel-tile permutation
y2 = exp(g(y1)) Β· x2 + h(y1)  # secret rescaled and offset by host

INN init detail

DKiS originally zero-initializes each subnet's conv5 output layer, making the INN start as exact identity β€” f(x2) = g(x2) = h(x2) = 0, so the secret never reaches the host half and bit gradients can't escape the BCE = ln(2) fixed point. INNCodec.__init__ re-initializes all conv5 weights with kaiming_normal Γ— 0.1 so residuals start small but non-zero. Load-bearing for the network to ever leave the random-prediction floor.

Β§3Adapter design β€” patch tile (v3, locked)

Bits ↔ tensor by construction. The hardest design decision in the project, and the one that has changed most times. This section is the v3-base design rationale β€” the choices that led to the deployed model. The deployed carrier (idea097/d001) carries three small extensions on top of this base; see the "Deployed extensions" sub-section below for what diverges, and model Β§ 2 for the paper-exact formal spec of the deployed pipeline.

stepoperation (v3-base, design rationale)
1. bipolarbits ∈ {0,1}^N β†’ Β±1
2. grid padplace first N of 1024 slots in a 32Γ—32 grid; pad unused slots with 0
3. scramblefixed sign-mask multiply (deterministic PRNG)
4. tilenearest-neighbor up-sample to DWT spatial dims (H/2, W/2)
5. broadcastrepeat across 12 DWT channels β†’ secret (B, 12, H/2, W/2)
← decodemean-pool over patch + sign-mask invert + sign-threshold β†’ bit logits

The deployed carrier (idea097_design001) trains and serves at N = 1024 (full grid; secret_len=1024 in train_args.txt) β€” the inner BCH(127,99) + outer RS(128,100) ECC codeword fills every slot, no padding. The 896-bit MWIP data payload is wrapped by that ECC before hitting the adapter; from the adapter's view the channel is 1024 b. The N=896 path (use 896, ignore 128) is the earlier no-ECC pre-image; PatchBitAdapter still defaults to n_bits=896 in source, but train_args at load-time overrides it.

The v3-base adapter round-trip is mathematically perfect (validated on smoke test). On the v3-base path, bit recovery is bottlenecked solely by the INN's robustness to attacks, not by an asymmetric learned encoder/decoder pair. The deployed extensions below relax the strict "zero-parameter" framing for ~50 k extract-side params; see below.

Why a (nearly-)zero-parameter patch adapter

propertypatch tile (v3-base)
Adapter params0 on the embed side; ~24 on the deployed extract side (the idea031 expert bank, see below)
Easy collapse via adapterno (bipolar signal is fixed; bank is initialized to a meaningful prior and lightly regularized)
Deep collapse via INN f/g/hpossible in principle but requires many params to align β€” much harder
Spatial structure of bitsuniform coverage by construction
Robustness mechanismexplicit β€” averaging over patch (repetition code)
JPEG alignment at 256Β² trainingexact at 8Γ—8 patches = JPEG blocks
Known tradeoffINN must do all perceptual hiding β€” may yield lower PSNR than a learned encoder/decoder pair would

Earlier drafts paired the INN with a learnable ~5.4 M-param Linear adapter; that path collapsed (weights β†’ 0 under attack pressure) and was dropped. The deployed model has no such adapter β€” patch tile only.

Deployed extensions on the v3-base adapter

The deployed carrier (autohide/.../idea097_design001/code/inn_model.py, verified against train_args.txt: --num_blocks 8 --subnet_type light --secret_len 1024 --no-amp) generalises three pieces of the v3-base picture above. The rationale is preserved; the implementation is richer. The full formal spec is in model Β§ 2 β€” what follows is the one-paragraph "why" for each extension.

extensionv3-base picturedeployed behaviorwhy
12 balanced permutations
encode side
nearest-neighbor tile, broadcast across 12 DWT channels β€” every channel carries the same bipolar copy each DWT channel c ∈ {0..11} gets its own deterministic balanced permutation Ο€c that scatters slot values to disjoint pixel positions. Seed 0xD1FF5E βŠ• c βŠ• (hΒ·1000003 + w). bit redundancy now spreads across both space and DWT channel; per-channel decorrelation lets the coupling INN move secret energy into whichever subbands have local headroom rather than enforcing the same spatial pattern everywhere.
Subband-weighted MoE decode (idea031)
decode side, +~12 k params
uniform mean across 12 channels per-batch decode-weight vector wc = router(xΜ‚) Β· expert_bank with a learnable (K=2, 12) bank initialised to a [4, 4, 4, 2, 2, 2, 2, 2, 2, 1, 1, 1] / sum subband prior (LL Γ— 4, LH/HL Γ— 2, HH Γ— 1) and a small router conv stack reading the container RGB. JPEG / WeChat low-pass crushes HH harder than LL. The fixed subband prior already encodes that; the MoE adds an attack-conditioned tilt (two experts) so the decode can lean further into LL when the attack signature looks lossy.
DWT cover predictor (idea034)
decode side, +~35 k params
reverse INN runs on concat[c_dwt, 0] a small 3-block residual head estimates host_dwt_est from the container RGB; the reverse INN sees c_dwt_aug = c_dwt + 0.5 Β· (c_dwt βˆ’ host_dwt_est) instead of c_dwt. amplifies the residual that carries the bit signal before the INN tries to recover it. The predictor is zero-init at the head, so step 0 is mathematically identical to v3-base (host_dwt_est = 0 β†’ c_dwt_aug = c_dwt); training only ever improves on the base.
Why "zero-parameter adapter" still mostly holds. The embed side is genuinely zero-parameter: no learnable weights touch the bit signal between bipolar conversion and the INN forward pass. The ~50 k extra params live entirely on the extract side and are buffered into the verifier-side checkpoint. Camera-side embed latency is unaffected β€” the ~25 ms / 12 MP figure quoted in index and spec still applies. The deployed total trained-param count is ~0.21 M (0.167 M INN backbone + 0.04 M extract-side heads); the "0.167 M / 0.7 MB" framing communicates the deployable-on-camera budget, not the full trained-network size.
Honest framing. This is not spread-spectrum. Spread-spectrum uses a long pseudo-random sequence per bit; we use a constant Β±1 over a patch (a low-entropy repetition code). The robustness story to claim is "averaging over many pixels denoises the bit" β€” not spread-spectrum theoretical guarantees.

Patch grid choice

croppatch RGBpatch DWTpixels/bit (RGB)
256Γ—2568Γ—84Γ—464
512Γ—51216Γ—168Γ—8256
1024Γ—102432Γ—3216Γ—161024
12 MP (β‰ˆ4032Γ—3024 β†’ 4032Γ—3008 after trim)~126Γ—94~63Γ—47~12 000

Grid fixed at 32Γ—32 = 1024 slots regardless of input resolution. The deployed carrier uses all 1024 slots (ECC codeword fills the channel; the 896-b data payload is wrapped by BCH + RS before the adapter sees it). Patch size scales with the input. Requires H, W divisible by 64 (so DWT dims % 32 == 0).

JPEG alignment β€” the corrected story

JPEG always uses 8Γ—8 DCT blocks regardless of image size. Whether the patch lines up depends on patch size:

Earlier doc claimed alignment was load-bearing. Walked back too far in correction. Current claim: alignment is a soft preference at 8 px and a non-issue at 16 px and above. Under heavy compression (q < 30) and double-JPEG the misalignment penalty grows β€” needs empirical measurement (Β§11 ablation 4).

Ξ± = 1 fixed. An earlier draft had a learnable scalar Ξ± scaling the patch tile. Codex flagged it as a hidden collapse path β€” under attack pressure the optimizer can drive Ξ± β†’ 0, recreating v1's "embed nothing" failure mode with one parameter instead of 5.4 M. Dropped. Reintroducing Ξ± as a fixed schedule (ramp 0.1 β†’ 1.0) is listed in Β§11.

Sign-mask scramble

Fixed pseudo-random Β±1 sign-mask applied to patch values before tiling. Deterministic from seed; verifier knows it. Zero learned params, breaks the bit-index-to-image-region coupling (otherwise bit #0 would always be top-left and an attacker could zero out that corner).

Pre-tile sign-mask is not spread-spectrum β€” it's a permutation/scrambler. Approaching SS-style localized-erasure robustness would require pseudo-random Β±1 within each patch instead of constant; that's ablation 6 in Β§11.

Β§4Curriculum β€” smooth Bernoulli Ξ±-ramp

Replaces the 3-stage step function (clean → low → high) that collapsed runs 1 and 2 at the discrete A→B boundary at step ~5,800.

step rangeΞ±what happens
0 β†’ 5,0000clean β€” attack module is skipped entirely
5,000 β†’ 15,000linear 0 β†’ 1each kornia child op fires at p_max Β· Ξ±; encoder sees a continuously-increasing fraction of attacked batches
15,000 β†’ 30,0001full target severity

A single AttackLayer(severity="high") instance is shared throughout. NoiseCurriculum.attack(step) mutates each child's Bernoulli p in place. At step 5,800 (the historical collapse point) Ξ± is now ~0.08 instead of 100% β€” the encoder sees ~8% of batches with attacks rather than 100%.

Caveat. Smoothing alone delays the slide rather than removing the embed-nothing basin. If a future run still collapses, the next layer is the embedding-energy floor relu(rms_floor βˆ’ β€–container βˆ’ coverβ€–_rms) β€” outlaws the basin in the loss landscape rather than delaying the slide. Optional companion: bit-weight ramp Γ—2–5 across the Ξ±-ramp window.

Collapse signature (what we're avoiding)

Runs 1 (56248029) and 2 (56250090) both collapsed at the same A→B step-function boundary at ~step 5,800 in 2026-04-25:

runjobstage A end (~4,600)post-transition (~5,800)
156248029bit_acc 1.0, PSNR 49bit_acc 0.5, PSNR 63, secret pathway killed (no diagnostic yet)
256250090bit_acc 1.0, PSNR 49, secret_rec_rms 93bit_acc 0.5, PSNR 64, secret_rec_rms 0.019, secret_rec_err_rms 0.94

Run 2 confirmed via the new diagnostics that the recovered secret tensor was driven 5,000Γ— toward zero in the very first 1,200 steps after attacks turned on β€” unambiguous "embed nothing" basin.

AttackLayer (severity="high", Ξ±=1)

opQ range / scaleBernoulli p
resize (down-then-up)scale ~ U(0.5, 1.0)0.5
jpeg1Q ~ U(40, 95)0.9
jpeg2 (chained)Q ~ U(20, 40)0.3

Forward order: resize first, then JPEG β€” matches platform pipelines. Output spatial dims preserved by construction. Geometric (crop, rotate) and other photometric (blur, noise, color jitter, grayscale) attacks intentionally absent.

Β§5Diagnostics & collapse signals

Logged per --log_every step. Both secret_rec_rms and secret_rec_err_rms were added 2026-04-25 to catch embed-nothing collapse the moment it starts.

metrichealthycollapse signal
bit_acc, bit_acc_ema→ 1.0flat at 0.5
psnr(container, cover)30–35 dBrises past ~50 dB = container β‰ˆ cover, secret discarded
l1_container_cover~0.01–0.02drops toward 0 with bit_acc still 0.5
secret_rec_rmslarge + stable through ramp (run 1 saw ~93 in clean phase)drops toward 0 = INN killed the secret pathway
secret_rec_err_rmssmall relative to secret_rec_rmsrises toward secret_rms (~1.0) = recovered tensor uncorrelated
alphamatches curriculum schedulediscontinuities = curriculum bug
img_loss, bit_lossfinite, decreasingNaN
Kill criterion. L1 nosedive while bit_acc stays at 0.5 = deep "embed nothing" collapse. PSNR jumping up under noise is the giveaway. Cancel and root-cause; don't wait for an optimizer recovery (it won't come).

Β§6Threat-model scope

Compression-only. The training and eval attack suites are limited to canonical-delivery transforms β€” resize + JPEG + JPEG chains. Geometric attacks (crop, rotate) and other photometric attacks (blur, noise, color) are out of scope by design.

Why no resize-invariance, even though resize is in training

Adding resize to training gives a robustness margin against small in-pipeline resampling (recipient-app re-rendering, DPI tweak, format conversion within canonical-resolution range), not full resize-tolerance. Two architectural binders prevent that regardless of how well the encoder learns:

  1. The 32Γ—32 patch grid is not scale-invariant. After a 0.27Γ— resize, patch boundaries no longer align to integer pixels β€” the verifier's mean-pool decode lands on the wrong cells.
  2. Cryptographic content-binding requires bit-exactness. Recovered bits must hash to the same H that was signed; resize changes pixel values, hence H. Tolerant mode (Hamming threshold Ο„ on H) helps within ~10% scale; large platform resizes blow past Ο„.

Realistic deployment for large resizes: "verify against the original-resolution camera file, not the social-media-resized derivative." Feed-scraped copies are intentionally unverifiable, which is consistent with the provenance argument β€” a re-uploaded resize cannot impersonate the original because it cannot carry the signature.

Why no crop-tolerance

Whole-image design with H = RobustHash(image). Any crop changes H arbitrarily; PDQ drift is 20–50+ bits on small crops (per memory/project_mwip_ecc.md). Crop = tamper, by design. The verifier rejects.

This is an explicit trade for simplicity and smaller payload. Per-tile localization was considered in early drafts; rejected because payload exploded (~5 KB/image) and the tile-sync problem made graceful crop-recovery aspirational rather than concrete.

Β§7Mobile variant β€” Levers 2 + 3

Defaults flipped to (num_blocks=8, subnet_type="light") on 2026-04-28. The legacy 16/RDB baseline is still reachable; eval auto-detects the architecture from saved args, so 16/RDB checkpoints still load.

backboneblockssubnetparamsFLOPs vs baselinestatus
16 / RDB165-conv dense (RDB)4.05 M1Γ—legacy baseline. collapses on Ξ±-ramp at Ξ±β‰ˆ0.16
8 / RDB85-conv dense2.03 M0.5Γ—ablation: blocks only
16 / light162-conv flat (Light)0.334 M~0.08Γ—ablation: subnet only
8 / light82-conv flat (Light)0.167 M~0.04Γ—deployed mobile target β€” survived Ξ±-ramp without modification

LightConvBlock: Conv2d(in→32, 3×3) → LeakyReLU → Conv2d(32→out, 3×3). No dense concat — flat input/output channel profile that NPUs pipeline well (Apple Neural Engine, Qualcomm Hexagon). Per-block FLOPs ~12× lower than RDB at the same in/out channels.

Combined with halving the block count: ~24Γ— FLOP reduction, ~24Γ— param reduction.

Hyperparameters used by the deployed run (idea097_design001, in sibling autohide repo): lr=4e-4, bit_weight=20.0, img_weight=1.0, --no-amp, single-resolution 256Β² training, 30,000 steps. Curriculum: stage A 0–5,000 clean, stage B 5,000–15,000 Bernoulli Ξ±-ramp, stage C 15,000–30,000 at Ξ±=1 (autohide defaults; unchanged). The imagehide tree's multi-res sampler (pixel-budget 1.0 M, DPED + CLIC + COCO/DIV2K) is the in-flight research direction (idea084/d001 β†’ idea112/d003), not what the deployed checkpoint used.

Β§8Trim & deployment

Single canonical center-trim. Verifier rejects images that don't fit the trim convention rather than searching all 1024 shifts. Codex tightening β€” preserves the false-accept surface.

Camera (embed)

1. capture full-res RGB image (W Γ— H), e.g., 4032 Γ— 3024
2. center-trim to (W', H') where W', H' are floor-divisible by 32:
     W' = (W // 32) * 32   β†’ 4032 (already divisible)
     H' = (H // 32) * 32   β†’ 3008
   trim symmetrically: drop (W βˆ’ W')/2 from each of left+right, similar top+bottom
   keep the trimmed strips set aside (unmodified)
3. apply INN embed at (W', H') with 32Γ—32 grid β†’ patches sized (W'/32, H'/32)
4. reattach the trimmed strips around the watermarked region
   resulting image is original (W Γ— H) with a watermarked W' Γ— H' interior
   the strips (≀ 31 px wide) are unwatermarked but pixel-identical to original
5. JPEG-compress, save

Verifier (extract)

1. receive image of dimensions (W Γ— H)
2. validate: W and H are within the declared canonical-delivery profile
   (natively-captured camera resolution; reject crops/resizes that change W or H)
3. center-trim to (W', H') using the same floor-divide-by-32 convention
4. apply INN extract at (W', H') β†’ 1024 logits β†’ take 896 β†’ un-scramble β†’ BCE > 0
5. proceed with H-mismatch check (Hamming < Ο„) and Ed25519 verification
Why single canonical, not multi-shift. "Verifier tries all 32Γ—32 = 1024 trim shifts and accepts if any decodes" is existential β€” image accepted if any of 1024 canonicalizations parses. That widens the false-accept surface for free, exactly the kind of slack a security reviewer attacks. We commit to one trim rule and reject anything that doesn't decode under it.

Β§9Rejected alternatives

From the two-round codex review (2026-04-25) and the design's history. Listed so future iterations don't redo a settled rejection without new evidence.

Learnable Ξ± scalar on the patch tile

A single learnable scalar scaling the bipolar patch signal. Under attack pressure the optimizer drives Ξ± β†’ 0, recreating v1's "embed nothing" failure mode with one parameter instead of 5.4 M. Dropped 2026-04-25. Ξ± = 1 fixed. A fixed schedule (ramp 0.1 β†’ 1.0) preserves the "start gentle" benefit without the collapse path β€” listed in Β§11.

Multi-shift verifier (1024 trim positions)

Existential acceptance ("any of 1024 trims decodes") widens the false-accept surface for free. Dropped in favor of single canonical center-trim. The multi-shift number is reported for the security discussion only β€” and is one reason we picked single-canonical.

Spread-spectrum framing of the patch design

Earlier draft described the patch tile as "spread-spectrum." Codex flagged: spread-spectrum uses a long pseudo-random sequence per bit; we use a constant Β±1 over a patch (low-entropy repetition code). Don't overclaim. Robustness story: "averaging over many pixels denoises the bit." Approaching SS-style robustness would require pseudo-random Β±1 within each patch β€” ablation 6 in Β§11.

"JPEG alignment is load-bearing"

Earlier doc claimed 8-px alignment was essential. Walked back too far in correction. Current claim: alignment is a soft preference at 8 px, a non-issue at β‰₯ 16 px. Under heavy compression (q < 30) and double-JPEG the misalignment penalty grows β€” needs empirical phase-sweep validation.

Hard receptive-field ceiling for multi-resolution inference

Codex initially framed multi-res training as required to make 1024+ inference work, citing a "hard receptive-field ceiling." User pushed back from experience: translation-equivariant CNNs without normalization layers handle resolution mismatches fine; the patch design's local-boundary nature means the receptive-field-vs-patch argument doesn't bind. Reverted. Multi-res training is now classified as an empirical optimization, gated on what the eval shows, not a correctness prerequisite.

Per-tile design (early draft)

Per-tile hash + per-tile signature, with localization. Payload exploded to ~5 KB/image, tile-sync problem made graceful crop-recovery aspirational, and localization was a stretch claim. Dropped 2026-04-24 in favor of whole-image MWIP. Crop = tamper, by design.

A-strict (no H in pixels, recomputed at verify)

Ed25519 requires bit-exact input; robust hash drifts β‰₯ 1 bit under benign transforms; ECC wrapping fixes channel noise on embedded bits, not signer/verifier recomputation mismatch. Crypto-unsound. Tolerant mode (embedded H, Hamming threshold Ο„ before Ed25519) is the only sound cheap option.

Hybrid sidechannel (manifest for cert / metadata)

Embedding the minimum payload in pixels and shipping certs / metadata in a C2PA-style manifest. Dropped: collapses the paper into the TrustMark+C2PA slot, loses the manifest-free novelty claim.

Trapdoor INN (sk to embed, pk to extract)

Would eliminate replay at embedding. Requires cryptographic primitives (lattices, SNARKs, pairings) incompatible with continuous tensor arithmetic. Open research problem. Future work, not this paper. Replay is blocked by content-binding instead.

DKiS-style symmetric key fusion

Same key for embed and extract β†’ any verifier can forge. Incompatible with public verification. Dropped at v2. Asymmetric Ed25519 signatures replace the symmetric channel.

Β§10TrustMark baseline (for reference)

The CNN baseline we left behind. TrustMark is payload-agnostic via two Linear layers (encoder input, decoder output); a payload sweep just resizes those.

componentrole
Secret2Image.denseLinear(secret_len β†’ 16Β·16Β·3) β†’ reshape β†’ upsample β†’ concat with cover β†’ U-Net encoder. Payload-dependent.
SecretDecoder.decoder.fcfinal FC of the backbone (ResNet-18/50/DenseNet), resized to Linear(fc.in_features β†’ secret_len). Payload-dependent.
everything elseU-Net conv backbone, ResNet50, discriminator, noise layer, loss β€” all payload-independent

Exp 0 capacity sweep β€” what we learned

payloadfinal bit_acc_emastagenote
100 b0.9812comfortable
256 b0.9132marginal
512 b0.7950 (stall)cliff
1024 b0.6820 (stall)random+
2048 b0.6230 (stall)random+
p512 cliff is architectural. Retrying at p512 with 35 k extra steps and a cosine LR reset moved bit_acc by < 0.5 pp (0.795 β†’ 0.790). Confirmed not a training-length issue. The 16Γ—16Γ—3 bottleneck in Secret2Image is the binder β€” for β‰₯ 1024 b we'd need to widen to 32Γ—32Γ—3 (one full sweep). This is the reason for the INN pivot.

The information bottleneck

The secret projects through a 16Γ—16Γ—3 = 768-dim spatial bottleneck before entering the U-Net. For 2000 bits that's 0.38 dims per bit. For 4000 bits, 0.19. Independent of how many parameters the linear layer has, this caps capacity. Options were:

Β§11Open ablations load-bearing per codex

Codex flagged these as load-bearing β€” none of the design's robustness claims can be settled without them.

#ablationquestion
1Patch adapter under noiseDoes the v3 patch actually hold under noise? The whole motivation.
2Deep-collapse detectionWatch L1(container, cover) per Β§5. If it nosedives at any stage transition while bit_acc stays at 0.5, INN f/g/h are collapsing toward identity. Kill criterion to be set empirically.
3Crop-size ablationTrain at one size, eval at {256, 384, 512, 768, 1024}. If 768/1024 fail clean, the receptive-field ceiling is real and deployment scope narrows.
4JPEG phase sweepAll 64 phase shifts Γ— q ∈ {20, 30, 40, 50}. Measure actual misalignment penalty.
5Trim-search vs single-canonical FA"Single fixed center-trim" vs "try all 1024 trim shifts and accept if any decodes." Report FA delta.
6PR Β±1 within patches vs constantSS-like (PR Β±1 spreading code per patch) vs current (constant Β±1), matched energy. Test under localized erasure.
7Patch grid size16 / 32 / 64 β€” capacity vs robustness tradeoff. 32 is default.
8Mean-pool vs learned WienerA 1Γ—1 conv pre-pool could weight-pool optimally if learned. Adds params; might recover lost SNR.
9Per-channel sign mask12 different DWT-channel sign masks instead of broadcasting one mask. Cross-channel redundancy.
10Fixed Ξ± scheduleRamp 0.1 β†’ 1.0 (vs current Ξ± = 1 throughout). Preserves "start gentle" without the learnable collapse path.
11INN-as-adapterReplace patch-tile adapter with a small 4-block coupling INN. Tied weights, ~200 k params. Probably overkill given the 0-param patch design works.