Camw.Cli 0.1.0

dotnet tool install --global Camw.Cli --version 0.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Camw.Cli --version 0.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
#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

  1. A master secret is expanded with HKDF-SHA256 into sub-keys (K_select, K_auth, …).
  2. 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.
  3. K_select spreads every codeword bit across many carriers (redundancy R = carriers ÷ codeword bits) and whitens each so the physical marks look random.
  4. Carriers come from independent channels; each writes one bit where the data has slack.
  5. 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/de single-word pairs; supply your own SynonymTable for 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. CarrierCapacity vs CodewordBits tells 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.1.0 119 7/24/2026