# CSV Steganography — Design Exploration

*How to embed a cryptographic signature in a CSV file without cluttering the view or breaking parsers.*

---

## Problem Statement

We have a working approach for signing Python dicts: the `__msd` key carries a steganographic payload — a single `🔏` emoji with the full metadata, timestamp, and Ed25519 signature hidden in invisible Unicode variation selectors. JSON round-trips preserve it. The dict stays clean and readable.

Can we do the same for CSV files?

### Constraints

1. **Not visible in a text viewer** — the signature shouldn't clutter the CSV when opened in a plain text editor
2. **Not breaking any CSV parser** — Python `csv`, pandas, Excel, Google Sheets should all handle it
3. **Self-contained** — the signature must live inside the CSV file itself, not in a sidecar
4. **Survives standard round-trips** — save-as-CSV, import/export, JSON intermediaries
5. **UTF-8 required** — we already require UTF-8 for emoji-based keys; same assumption here

---

## Approaches Explored

### 1. Extra `__msd` Column with Unicode Steganography ✓

Add `__msd` as the last column. First data row carries the steganographic emoji; other rows leave it empty.

```
name,age,score,__msd
Alice,30,95,🔏
Bob,25,87,
Charlie,35,92,
```

| Criterion | Result |
|---|---|
| Visible in text viewer? | One column header + one emoji — minimal |
| Breaks CSV parsers? | No — standard column, standard string value |
| Breaks Excel / Sheets? | No — cell shows `🔏`, column can be hidden |
| Survives round-trips? | Yes — variation selectors are regular Unicode, no commas/quotes/newlines |
| Self-contained? | Yes |

**Verdict: Best approach.** Consistent with the JSON `__msd` key convention. Minimal visual footprint.


### 2. Zero-Width Characters in Existing Cells ✗

Encode signature bits as zero-width joiners (U+200D), non-joiners (U+200C), and zero-width spaces (U+200B) inside existing cell values.

| Criterion | Result |
|---|---|
| Visible? | No |
| Breaks parsers? | No |
| **Fragile?** | **Yes — `str.strip()` removes some ZWCs. Excel normalizes Unicode. Copy-paste loses them. Data cleaning pipelines strip them. String comparisons fail silently: `"Alice" ≠ "Alice\u200B"`.** |

**Verdict: Too fragile.** Silent data corruption makes this unreliable in real-world pipelines.


### 3. CSV Comment Line (`#`) ✗

```
# __msd:🔏
name,age,score
Alice,30,95
```

| Criterion | Result |
|---|---|
| Visible? | Yes, as a comment line |
| Breaks parsers? | **Yes — Python's `csv` module does NOT support comments. pandas does with `comment='#'`, but it's not standard.** |

**Verdict: Incompatible.** No comment standard in CSV.


### 4. Trailing Row with Hidden Payload ✗

Last row: `🔏,,` — emoji in first cell, rest empty.

| Criterion | Result |
|---|---|
| Visible? | Shows as an extra data row |
| Breaks parsers? | Parsed as data — confuses type-sensitive parsers. `🔏` in a "name" column is nonsensical. |

**Verdict: Pollutes data.** Indistinguishable from a malformed row.


### 5. Variation Selectors on Last Character of Last Cell ✗

Append steganographic payload to the last cell value without adding a column.

| Criterion | Result |
|---|---|
| Visible? | No |
| **Data corruption?** | **Yes — cell value silently changes. `"95"` becomes `"95\uFE00..."`. Numeric parsing fails. String equality breaks.** |

**Verdict: Silently corrupts data.** Absolute deal-breaker.


### 6. Unicode TAG Characters (U+E0001–U+E007F) ✗

Invisible "tag" characters from the Supplementary Special-purpose Plane.

| Criterion | Result |
|---|---|
| Visible? | Generally not |
| **Fragile?** | **Same issues as zero-width chars — stripped by text processors, poorly supported across platforms.** |

**Verdict: Fragile.** Same class of problems as Approach 2.


### 7. Sidecar File (`data.csv.msd`) ✗

Separate file alongside the CSV containing the signature.

| Criterion | Result |
|---|---|
| Self-contained? | **No — two files to manage. Easy to separate them.** |

**Verdict: Not self-contained.** Could complement the primary approach but can't stand alone.


---

## Recommended Approach: Extra `__msd` Column

### How It Works

**Signing:**
1. Parse the CSV into a list of rows (header + data)
2. Sign the semantic content: the ordered list of rows, each being an ordered list of cell values (strings)
3. Encode the signature + metadata as a steganographic emoji using the same Unicode variation selector technique as dict signing
4. Append `__msd` as the last column header
5. Place the steganographic emoji `🔏` (with hidden payload) in the first data row's `__msd` cell
6. Leave all other rows' `__msd` cells empty

**Verification:**
1. Parse the CSV
2. Extract the `__msd` value from the first data row
3. Decode the steganographic payload (emoji → base64 → decompress → signature data)
4. Reconstruct the signed content (all columns except `__msd`, all rows)
5. Verify the Ed25519 signature against the reconstructed content

### What Gets Signed

The signature covers the **semantic content** of the CSV — the ordered list of rows where each row is an ordered list of string values, excluding the `__msd` column. This means:

- **Renaming a column** → signature breaks ✓
- **Changing any cell value** → signature breaks ✓
- **Adding or removing a row** → signature breaks ✓
- **Adding or removing a column** → signature breaks ✓
- **Reordering rows** → signature breaks ✓
- **Reformatting whitespace, re-quoting cells** → signature survives ✓ (we sign values, not raw bytes)

### Example

**Original CSV:**
```
name,age,score
Alice,30,95
Bob,25,87
```

**After signing:**
```
name,age,score,__msd
Alice,30,95,🔏
Bob,25,87,
```

**In Python:**
```python
signed_csv = msd.sign_and_embed_csv("data.csv", metadata, key)
# or
signed_csv = msd.sign_and_embed_csv(csv_string, metadata, key)

msd.verify_csv(signed_csv)  # True
```

**In pandas:**
```python
import pandas as pd
df = pd.read_csv("signed_data.csv")
# df has columns: name, age, score, __msd
# Users can simply:
df = df.drop(columns=['__msd'])
# or ignore it — __msd values are mostly empty
```

### Consistency with the JSON Approach

| Aspect | JSON (dicts) | CSV |
|---|---|---|
| Key / column name | `__msd` | `__msd` |
| Steganography | Emoji + variation selectors | Same |
| Payload | metadata + timestamp + signature + key | Same |
| Encoding chain | compress → base64 → emoji steganography | Same |
| Visual footprint | One key with one emoji value | One column with one emoji cell |
| Parsing impact | One extra key | One extra column |

### Edge Cases

**What if `__msd` column already exists?**
→ Error: same as JSON, where we raise `ValueError` if `__msd` key already exists.

**What if the CSV is empty (header only)?**
→ Sign the header row. No data rows to place the emoji in — place it in a new first data row: `,,,...,🔏` (empty values for all data columns).

**What if the CSV has no header?**
→ Treat the first row as data. Append `__msd` as the last field of the first row.

**What if someone opens the CSV in Excel and re-saves?**
→ Excel may strip the variation selectors from the emoji, destroying the signature. This is the same caveat as our WARNING for binary file signing: "Some programs may strip the attached metadata when re-saving." We document this.

**What about very large CSVs?**
→ Signing a 1GB CSV means hashing all cell values. We could use streaming BLAKE3 hashing to keep memory usage low.

### Caveats

1. **Excel round-trips may strip the signature** — same as binary file signing
2. **UTF-8 encoding required** — CSVs in ASCII/Latin-1 can't carry emoji
3. **Adding a column changes the structure** — but `__msd` is a well-known MSD convention, easy to filter out
