Stego Engine — Feasibility, Readiness & Confidence Assessment
Scope: the deferred combined steganography engine — two orthogonal geometric
channels exposed as three modes (Traceability / Authenticity / Hybrid) — sold as
the premiumwatermarkingmodule. Priorities, in order: security → consistency →
compatibility → speed. Dev time/cost is explicitly not a constraint.This is an assessment, not a build. Verdict first, then the reasoning, the real hard
parts, the risks, and a de-risking plan. Written 2026-07-10.
0. TL;DR verdict
Feasible and well-positioned to build, with high confidence on the architecture and
the invisible/crop-resilient path, and medium confidence on the camera/print-scan path
until a real-capture corpus proves the robustness numbers.
The reason confidence is this high before a line of engine code exists: the hard product
decisions are already made and frozen in a way that can't drift. The seam is done. What's
left is a bounded, well-understood compute problem (font-aware PDF surgery + CV decode) that
has two working reference implementations to clean-room from. This is not research; it is
disciplined engineering against a spec, with one genuine research-grade risk (print-scan
survival) that must be measured, not assumed.
One blocker to resolve before building: the frozen contract has an inverted starmap
comment (§4.1) that, taken literally, would make the engine build the vault for the wrong
mode. Fix the contract first.
| Dimension | Confidence | One-line basis |
|---|---|---|
| Architecture / seam readiness | Very high | Interface, proto, ledger, module-gate all built and frozen |
| Channel V (Traceability, invisible, crop/screenshot) | High | Established technique; reference impl exists; blind decode |
| Channel K (Authenticity, camera) | Medium-High | Reference exists but has a known decoder-collapse bug to fix + recalibrate |
| Print-scan survival | Medium | Design target, not proven — needs corpus + calibration |
| Font compatibility (simple + composite in one layer) | Medium-High | The keystone; both ref impls each did only half — must unify |
| Security / crypto (MAC payload, KMS, mTLS, vault) | High | Design is sound and standard; replaces the CRC false-positive risk |
| Collusion / reflow / re-typeset resistance | Low (out of scope) | Explicitly un-covered gaps; roadmap items, not guarantees |
1. What "3 modes" actually is (and why that framing matters)
There are two channels and three modes. The modes are policy presets over the channels:
| Mode | Channels | Axis / mechanism | Optimized for | Cost |
|---|---|---|---|---|
| Traceability | V only | Vertical baseline / line-spacing shift (±~0.7 pt) | Invisible, blind self-service verify, survives crop/screenshot/B&W/JPEG | No camera resilience |
| Authenticity | K only | Horizontal inter-word kern (one-sided +ε, ~1.5–2 pt) | Phone-camera capture, strong crypto serial | Slightly visible; needs vault |
| Hybrid | V + K | Both, orthogonal axes | Full capture coverage + cross-channel corroboration | Visible (K) + vault + ~2× compute |
Why the orthogonality is the whole game. V is read as a width-integrated row centroid
(moving ink horizontally doesn't change it). K is read as per-line gap ratios re-segmented
per line band (shifting a line vertically doesn't change them). So both signals live on the
same document with near-zero cross-talk — Hybrid is genuinely V and K decoded independently,
not a compromise blend. This is the single most important technical claim, and it's the one
I'd validate empirically first (§6, DR-1) because everything about Hybrid rests on it.
Framing consequence for selling it as a module: the three "modes" are a clean pricing and
policy story (invisible-and-cheap → visible-and-camera-proof → both), and they map 1:1 onto the
already-frozen Mode enum. No new surface area is needed to expose them — the module boundary
already exists.
2. Readiness — what already exists (this is the good news)
The engine is deferred behind a no-op seam that is fully built. Concretely, on disk today:
- Frozen gRPC contract —
proto/stego/v1/stego.proto:Encode/Decode/Capabilities/
Health, theMode/Tier/Verdict/EgressChannelenums,starmapblob, trace-propagation
convention (traceparent+issuance-idmetadata). Committed and buf-compiled so the Go side
and the future sidecar cannot drift. - Go consumer port + no-op adapter —
internal/protection/app/ports.go(the
ProtectionEngineinterface, a field-for-field image of the proto) and
adapters/noop.go(passes PDF through,Applied:false,Reason:ENGINE_NOT_ENABLED). Swapping
PROTECTION_ENGINE=grpcis designed to require zero caller changes. - Attribution plumbing already live — the Deliverer chokepoint mints the issuance-ledger
row today, under the no-op engine. So the identity→document→version→mode→key_epoch mapping
exists before the engine that consumes it. This de-risks integration enormously: the engine
plugs into a socket that's already wired and load-bearing. - Module gating already built —
LICENSING.mddefineswatermarkingas a premium module
unlocked only by a signed license file (requireModule("watermarking"),modules[]in a
Ed25519-signed license, hot-swappable, air-gap friendly). Note it says "watermarking has no
routes yet" — so the verify/forensic endpoints are net-new surface to add, but the enforcement
mechanism they'll hang off is done. - Reference implementations on disk —
~/remote-development/Starfield(Channel K / kerning)
and~/remote-development/seeddms60x(the DMS host). geomstego (Channel V) behavior is
documented in the blueprint. These are clean-room design references only (§13 rules): study
behavior, reimplement, never port. Starfield is covered by the CEO grant. - Vetted AGPL-free stack — pikepdf (qpdf, MPL-2.0) for token-level content-stream surgery +
pypdfium2 (PDFium, Apache/BSD) for glyph boxes and raster render, OpenCV/numpy, tesseract,
reedsolo. This deliberately removes PyMuPDF/fitz and Ghostscript (the two AGPL landmines) with
no functional loss. Python 3.11 is present; none of these are installed yet (expected — the
sidecar doesn't exist).
Net readiness: everything except the compute core itself is in place. There is no
architectural discovery left to do. That is why this reads as an execution task, not an R&D bet.
3. The real hard parts (where the effort and the risk actually are)
Four things are genuinely hard. Everything else is plumbing.
3.1 The unified font-aware surgery layer — the keystone
Both reference impls each handled only one font class: geomstego did Type0/CID only, Starfield
did simple Latin-1/WinAnsi only. That mutual exclusion is an implementation artifact, not a
signal limitation — but unifying it is the hardest single piece. The one layer must, for the
same text run and both simple (Type1/TrueType/WinAnsi) and composite (Type0/CID) fonts:
1. Resolve each font's encoding/CMap (bytes-per-code + code→Unicode) to find U+0020 spaces and split
TJ/Tj strings at correct code boundaries (1-byte simple and 2-byte CID).
2. Read per-font advance widths (CID W / simple Widths) to convert a Δpt into a TJ kern number
(-1000·Δpt/fontsize, font-independent math).
3. Track full text/graphics state (Tm, Tlm, Tf, Tc/Tw, CTM via q/Q/cm) to place the
baseline shift.
4. Compose both edits in one BT block, inserting in descending byte-offset order so offsets
stay valid.
5. Keep a raster fallback for fonts it still can't parse — but flag such docs "text-layer lost"
so fail-closed policy can refuse them in invisible mode.
This is the compatibility priority made concrete. Real-world PDFs are a zoo (subsetted fonts,
embedded CMaps, mixed encodings, rotated CTMs, form XObjects). The failure mode isn't "wrong bit"
— it's "corrupted PDF" or "visible artifact," both unacceptable. Confidence here is Medium-High:
the technique is known and pikepdf's TokenFilter is the right tool, but breadth of font/PDF
coverage is where the long tail of real bugs will live. Budget the most time here.
3.2 Channel K's decoder-collapse bug (must fix + recalibrate)
Starfield's ±3 pt symmetric kerning drives "0"-bit gaps toward zero (words touch), which
destroys K's own decoder — the silent column it measures vanishes. Required change: one-sided /
positive-biased encoding (bit-1 = +ε, bit-0 = 0, never negative) and/or lower ε to ~1.5–2 pt.
Consequence: K's robustness thresholds were calibrated at 150 DPI / ε=3 pt, so the entire K
calibration must be re-run after the change. This is known, bounded work — but it's real work,
and it gates any Authenticity/Hybrid robustness claim.
3.3 Print-scan survival — the one genuine research risk
The blueprint is honest that print-scan is a design target, not a shipped guarantee. Phone-
camera capture is the proven K case; print→scan adds toner spread, halftoning, scanner MTF, and
rescan skew on top. Whether ε≈1.5–2 pt gap-ratio signal survives that chain is an empirical
question that must be measured, not reasoned about. This is the single biggest reason overall
confidence is "high" and not "very high." De-risking is §6 DR-2.
3.4 The shared normalization front-end
Every uploaded leak artifact (PDF, screenshot, phone photo, scan, partial crop) routes once
through deskew/deblur/keystone+barrel dewarp, then feeds the rectified image to both decoders.
This is the cheap, high-leverage win — it lifts V's phone-camera survival (V has no dewarp today).
It also forces a DPI re-calibration of V against rectified inputs. CV-standard, but the
calibration coupling means V and K can't be calibrated fully independently; plan a joint multi-DPI
pass.
4. Consistency & correctness — issues to resolve before building
4.1 ⚠️ Contract bug: starmap is attached to the wrong mode
The frozen contract says the vault blob belongs to Traceability, but the design says it belongs
to Authenticity. These directly contradict:
proto/stego/v1/stego.proto:bytes starmap = 3; // present for TRACEABILITY/HYBRID onlyinternal/protection/app/ports.go:Starmap []byte // ... populated for Traceability/Hybrid only- vs.
BUILD_BLUEPRINT.md§6: Channel V = Traceability = blind (ID straight from pixels, no
vault); Channel K = Authenticity = non-blind (needs the starmap from the vault). §5's proto
sketch even commentsstarmap // present only for K/HYBRID— i.e. Authenticity/Hybrid.
Taken literally, the committed contract would have the engine emit a vault blob for the mode that is
supposed to be self-verifying and store nothing for the mode that requires the vault to decode.
Traceability is the public blind tier precisely because it needs no registry; wiring a starmap to it
is backwards. Resolve this in the proto + ports before writing the engine, or the engine and the
seam will disagree on day one. (Low effort to fix — it's a comment/semantics correction and a mode
label — but it must be deliberate, since the contract is "frozen.")
4.2 Payload format is the security keystone — get it right once
Both channels must carry the same in-band payload so one ledger attributes either:
version(4b) | issuance_id | keyed-MAC tag → Reed-Solomon ECC. Two non-negotiables:
- Keyed MAC, not CRC. geomstego's CRC-16 gave false positives and forced a heuristic tiebreaker
— a wrongful-attribution risk. Replace with truncated HMAC(master_key[epoch], issuance_id).
A random decode passes with prob 2⁻ᵀ; size T (24–32 bits) to the legal confidence bar. This is the
false-positive killer and the core of the "security first" priority.
- No PII in-band, ever. issuance_id is an opaque index into the ledger row; the MAC is keyed by
key_epoch for rotation (old epochs stay verifiable). Capacity: V tiles the codeword and
majority-votes across paragraphs; K has more room (RS + 2× block repeat).
4.3 Verdict / tier semantics must match the seam
Decode returns per-channel {issuance_id, mac_ok, confidence} + a Verdict
(CONFIRMED/LIKELY/INCONCLUSIVE) + cross_channel_agreement. Two-tier exposure is a hard
security boundary: the blind public verify endpoint runs V only (no vault, payload redacted —
"is this traced? y/n"); the forensic console (access-controlled) runs V+K against the vault. K is
registry-bound and can never appear in the public tier. The Tier enum already encodes this; the
engine must honor it, and the new verify routes must enforce it.
5. Security posture (the top priority) — assessment: strong
- Keyed-MAC payload eliminates the reference impls' false-positive path — the biggest forensic
risk in the legacy design. ✅ - Master secret in KMS/HSM, addressed by
key_epochfor rotation; vault blobs encrypted with
KMS-managed DEKs; mTLS between Go and sidecar; no secrets in source (the reference repos committed
a stego secret to git — the constraint doc explicitly forbids repeating that). ✅ - Fail-closed: if watermarking can't complete, the protected download is blocked, never
served unmarked (configurable per classification). The sidecar being stateless + the Go side owning
policy makes this clean to enforce. ✅ - Data-minimization in the vault: store line fingerprints/features for anchoring rather than
raw line text where robustness allows (GDPR). Watermarks are personal-data processing — DPIA hook,
retention/erasure path, evidentiary bar before a CONFIRMED verdict can drive personnel action. ✅
These are designed-in, not bolted-on. - Residual security-relevant gaps (must be stated, not hidden): recipient collusion (diffing
two copies to locate/strip the mark), re-typeset / reflow / OCR-and-reprint (destroys both
geometric signals), and severe blur. Anti-collusion (Tardos fingerprinting) is a roadmap item, not
a shipped guarantee. A buyer must be told the threat model: this defeats casual leaking and
screenshot/photo exfiltration with cryptographic attribution — it is not a defense against a
determined adversary who retypesets the document.
6. De-risking plan — the order I'd actually build in
The whole strategy is: prove the two scariest claims on a real corpus before committing to the full
build, because they're the only things that could invalidate the module's core promise.
- DR-0 (contract): Fix the §4.1
starmap/mode inversion in proto + ports. One PR, do it first. - DR-1 (orthogonality spike): Smallest possible V+K encode on a handful of real PDFs; confirm
cross-talk is near-zero by decoding each channel with the other present. If Hybrid's independence
doesn't hold empirically, everything downstream changes — so validate it cheaply, first. - DR-2 (real-capture corpus): Build the corpus early, not at the end: phone photos at varied
angles/lighting, messaging-app recompression (WhatsApp/Telegram JPEG), and print→scan at
multiple DPIs. Measure per-channel trace rate. This is what converts "print-scan is a design
target" into a published number (or an honest "camera-yes, print-scan-partial" SLA). - DR-3 (font-coverage matrix): A test set spanning Type1/TrueType/WinAnsi, Type0/CID, subsetted
fonts, embedded CMaps, rotated CTMs, form XObjects. Every entry must round-trip to a byte-valid,
visually-clean PDF or fall to the flagged raster path. This is the compatibility gate. - DR-4 (K recalibration): Implement the one-sided fix, then re-run K's DPI/ε calibration against
DR-2's rectified inputs jointly with V. - Then build the full sidecar (surgery layer → V → K → normalization → unified verify), wire the
gRPC adapter behindPROTECTION_ENGINE=grpc, add the two verify routes under
requireModule("watermarking"), and run the §7 validation/hardening pass (pen-test, DPIA,
published trace-rate SLA).
The point of this ordering: DR-1 and DR-2 are cheap relative to the full build and can kill or
reshape the design. Do them before the expensive keystone (§3.1).
7. Confidence, stated plainly
- Will the module ship and integrate cleanly? Very high. The seam is done, the ledger is live,
the module-gate exists, the stack is vetted, the references are on disk. - Will Traceability (invisible + crop/screenshot + blind verify) hit its promise? High. This is
the most solid path and I'd expect it to work close to spec. - Will Authenticity survive phone-camera capture? Medium-High, pending the K fix + recalibration.
- Will it survive print→scan at a sellable trace rate? Medium, and unknown until measured — the
one honest research risk. Sell the SLA on measured numbers, not the design target. - Will it resist a determined re-typesetting/colluding adversary? No — and that's a stated,
bounded limitation, not a surprise.
Bottom line: this is a green-light to build, priorities well-matched (security is the strongest
dimension; compatibility is the hardest but tractable; speed is rightly last). The only true unknown
is print-scan robustness, and the plan front-loads measuring it. Fix the contract inversion, run the
two spikes, then build the keystone.
8. Open decisions for the user (won't block the spikes)
- Print-scan scope: ship Authenticity as camera-resilient now, print-scan as a measured/best-
effort SLA, or hold the mode until print-scan clears a bar? (Recommend: ship camera-first, label
print-scan by the DR-2 number.) - MAC tag width T: 24 vs 32 bits — trades capacity against false-positive floor (2⁻ᵀ). Pick to
the legal confidence bar for personnel action. - Anti-collusion (Tardos): roadmap-only for v1, or in scope? (Recommend: roadmap; it's a
separate, large piece and not required for the core leak-attribution promise.)