Camw.Cli
0.1.0
dotnet tool install --global Camw.Cli --version 0.1.0
dotnet new tool-manifest
dotnet tool install --local Camw.Cli --version 0.1.0
#tool dotnet:?package=Camw.Cli&version=0.1.0
nuke :add-package Camw.Cli --version 0.1.0
CAMW — Constraint-Aware Mixed Watermarking
A .NET 10 library that watermarks mixed datasets (natural-language text and numeric values) for ownership proof and leak attribution. It embeds a decodable, HMAC-authenticated message spread redundantly across many carriers; recovery decodes the message and cryptographically verifies it — a valid tag is proof, not a correlation.
The dataset is treated as one multi-channel medium. No single change is meaningful by itself; ownership is established from the aggregate. Constraints come first: every field states what it tolerates, and any carrier that can't be marked within budget is skipped — never force a watermark into a field that has no legitimate degrees of freedom.
Two front-ends sit on the same core: documents (strings) and relational tables (CSV).
How it works
- A master secret is expanded with HKDF-SHA256 into sub-keys (
K_select,K_auth, …). - The payload — a pseudonymous release token plus dataset version, purpose, and a 64-bit HMAC tag — is protected by an outer Reed–Solomon code over GF(256), giving a codeword.
K_selectspreads every codeword bit across many carriers (redundancyR = carriers ÷ codeword bits) and whitens each so the physical marks look random.- Carriers come from independent channels; each writes one bit where the data has slack.
- Recovery undoes the whitening, majority-votes each codeword bit, Reed–Solomon-corrects residual errors, and verifies the HMAC tag. A forged or unwatermarked input passes the tag by chance with probability only 2⁻⁶⁴.
Carrier channels
| Channel | Carrier | Bit 0 / Bit 1 | Blindly detectable? |
|---|---|---|---|
| Homoglyph | each letter with a confusable (e.g. Latin a ↔ Cyrillic а) |
original / substitute glyph | yes — glyph identity |
| Zero-width | each space | no marker / a U+200B after the space |
yes — marker presence |
| Numeric | each number | quantization-index modulation on the value | yes — grid-index parity |
| Synonym | each word in a language synonym pair | first / second member | yes — which member is present |
Channels are mutually independent on read — each ignores or normalizes away the others' marks (the synonym reader folds homoglyphs and strips zero-width chars before matching words). So stripping one channel never breaks the rest.
- Homoglyphs survive copy/paste and Unicode NFC/NFKC normalization (Cyrillic and Latin look-alikes have no compatibility mapping), but are defeated by transliteration or OCR.
- Zero-width marks are the most fragile — many editors and forms strip them.
- Numeric QIM survives as long as precision is preserved; coarse rounding erases it.
- Synonyms survive copy/paste, normalization and reformatting; only paraphrasing the
specific carrier words removes them. Built-in
en/desingle-word pairs; supply your ownSynonymTablefor domain vocabulary. The channel is opt-in per language.
Graceful degradation
Two layers of redundancy — the Reed–Solomon outer code and the keyed inner spreading — plus subset decoding keep recovery working under corruption and removal. If the full-carrier decode fails to authenticate, the detector retries over channel subsets (leave-one-out), so a stripped or corrupted channel is simply dropped as long as the survivors still cover the codeword.
Documents
using Camw;
using Camw.Payload;
var keys = KeyHierarchy.FromSecret("owner-master-secret");
var payload = new WatermarkPayload(WatermarkPayload.NewReleaseToken(), datasetVersion: 7, purposeCode: 42);
var options = new WatermarkOptions
{
// NumberCulture = CultureInfo.GetCultureInfo("de-DE"), // for 1.234,56 style numbers
// EnableSynonyms = true, // opt-in text channel (en/de built in)
};
string sealed_ = new PayloadWatermarker(options).Embed(text, payload, keys, context: schemaHash);
PayloadDetectionResult r = new PayloadDetector(options).Detect(sealed_, keys, context: schemaHash);
if (r.AuthenticationValid)
Console.WriteLine(r.Payload!.TokenHex); // recovered and cryptographically verified
PayloadWatermarker.CarrierCapacity(text) reports how many carrier bits a document holds —
compare with CodewordBits (message + RS parity) to judge whether a sample is long enough.
Tables (relational / CSV)
Camw.Tabular watermarks tabular data. Each column gets a policy:
using Camw.Tabular;
var schema = new[]
{
ColumnPolicy.Anchor("id"), // stable key — never modified, anchors selection
ColumnPolicy.Text("description"), // watermarked with the text channels
ColumnPolicy.Numeric("amount", step: 0.01m), // watermarked with QIM within ±step
ColumnPolicy.Ignored("account_no"), // protected, carried through untouched
};
var table = DataTable.FromCsv(csv, schema);
var keys = KeyHierarchy.FromSecret("owner-secret");
var payload = new WatermarkPayload(WatermarkPayload.NewReleaseToken(), 1, 0);
DataTable marked = new TabularWatermarker().Embed(table, payload, keys);
PayloadDetectionResult r = new TabularDetector().Detect(marked, keys); // decode + verify
Every carrier is addressed by HMAC(K_select, rowAnchor ‖ column ‖ channel ‖ slot) — keyed by
the row's anchor value, not its position. So the watermark is:
- order-independent — sorting or shuffling rows changes nothing;
- resilient to row deletion — surviving rows still carry their bits; RS + redundancy cover the rest;
- resilient to column/channel removal — dropping a column just removes carriers; a corrupted channel is dropped by subset decoding, while the survivors still cover the codeword.
Numeric cells use quantization-index modulation: the value is snapped onto a grid of step
spacing and the grid-index parity carries the bit, so the change is bounded by step (cells
whose change would exceed the tolerance are skipped). Text cells reuse the homoglyph,
zero-width and synonym channels. Both feed the same authenticated, RS-protected payload.
Layout
src/Camw core library
├─ Channels/ homoglyph, zero-width, numeric, synonym channels (internal)
├─ Payload/ HKDF keys, authenticated payload, Reed–Solomon, spreading, decode
└─ Tabular/ DataTable, column policies, anchored selection, QIM, watermarker/detector
src/Camw.Cli `camw` command-line tool (seal / recover)
tests/Camw.Tests xUnit test suite (58 tests)
bench/Camw.Benchmarks tabular-attack harness
The public API is small: WatermarkOptions, HomoglyphTable, SynonymTable, NumericMode,
Channel; KeyHierarchy, WatermarkPayload, PayloadWatermarker, PayloadDetector,
PayloadDetectionResult; DataTable, ColumnPolicy, ColumnKind, TabularWatermarker,
TabularDetector. Channels, key stream and codec are internal implementation details.
CLI (camw)
# Seal an authenticated token into a document, then recover + verify it
camw seal --secret owner-key --version 7 --in report.txt --out report.sealed.txt
camw recover --secret owner-key --in report.sealed.txt # exit 0 + token if authenticated
# Enable the German synonym channel and German number formatting
camw seal --secret owner-key --synonyms de --culture de-DE --in bericht.txt --out bericht.sealed.txt
# Machine-readable recovery report
camw recover --secret owner-key --in leaked.txt --json
camw recover exits 0 when an authenticated watermark is recovered, 2 when none is, 1 on
a usage/IO error. The same channel/culture options must be given to seal and recover. Run
camw help for the full option list. (Installed as a dotnet tool, the command is camw; for
local dev use dotnet run --project src/Camw.Cli -- <args>.)
Build, test, benchmark
dotnet build Camw.sln -c Release
dotnet test tests/Camw.Tests
dotnet run -c Release --project bench/Camw.Benchmarks
Limitations & roadmap
- Recovery needs the surviving carriers to cover the codeword (message + RS parity), so a very
small dataset — or dropping the dominant channel on one — may be untraceable.
CarrierCapacityvsCodewordBitstells you the margin. - The document front-end keys carriers by ordinal position, so heavy mid-document edits that insert/delete carriers shift alignment. The tabular front-end has no such limit — carriers are keyed by row anchor.
Not yet built (CAMW roadmap). The library implements the CAMW MVP: policy-driven carriers, an authenticated RS-coded payload, a hybrid text+numeric embedder, a decoder, and an attack harness. Deliberately deferred:
- Pairwise sum-preserving QIM — the numeric channel is per-cell QIM; pairing within strata to preserve group sums is a further step.
- Semantic text channel — an embedding-space partition (SemStamp-style) needs an ML model, so it is out of scope for this dependency-free library; the synonym channel is the curated stand-in.
- Collusion-resistant fingerprint codes (e.g. Tardos) for tracing colluding recipients.
- Reversible and integrity modes (encrypted restoration sidecar; per-row signatures).
Measured robustness (from the benchmark harness)
A 300-row table (anchor + text + numeric, redundancy ≈ 74×) sealed with an authenticated token, then attacked. The token is recovered and verified in every case except a wrong key, which is correctly rejected:
| attack | authenticated | channels used |
|---|---|---|
| baseline | ✅ | Homoglyph + ZeroWidth + Numeric |
| shuffle rows | ✅ | Homoglyph + ZeroWidth + Numeric |
| delete 40% of rows | ✅ | Homoglyph + ZeroWidth + Numeric |
| round numeric → integer | ✅ | Homoglyph + ZeroWidth + Numeric |
| normalize homoglyphs | ✅ | ZeroWidth + Numeric (homoglyph dropped) |
| drop numeric column | ✅ | Homoglyph + ZeroWidth |
| wrong key | ❌ (rejected) | — |
The Reed–Solomon codec is independently tested to correct exactly parity/2 byte errors and to
never return clean-but-wrong data beyond that.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 119 | 7/24/2026 |