# NoData — Cryptographic Primitives Specification

**Version:** 1.0 (April 2026)
**Status:** stable, in production
**Companion docs:** `architecture-and-threat-model.md` · `audit-chain-spec.md`

This document specifies the exact cryptographic algorithms, parameter choices, and construction details used by every NoData component. It is intended for auditors, security engineers, and procurement teams who need to verify that NoData's choices are sound before adopting the platform.

---

## 1. Symmetric content encryption (AES-256-GCM)

**Algorithm:** AES-GCM with 256-bit key
**IV:** 96 bits, generated per record from a CSPRNG
**Authentication tag:** 128 bits
**Max data per key:** 2^32 records — chain anchor reset enforced before exhaustion
**Library:** Node `crypto` (OpenSSL 3.x), Python `cryptography` (OpenSSL backend), Go `crypto/aes` + `crypto/cipher`

### IV construction

IVs are random, never counter-based. We do **not** use the deterministic-IV-with-record-counter scheme — it is a footgun under tenant migration. Random 96-bit IVs at our write rates have an effectively zero collision probability per key; key rotation enforced before approaching the birthday bound.

### AAD (Additional Authenticated Data)

AAD is constructed as:

```
AAD = tenant_id || device_id || receipt_id || schema_version
```

This binds every ciphertext to:
- the tenant that wrote it (prevents cross-tenant ciphertext substitution)
- the device that produced it (prevents replay across devices)
- the receipt that announces it (prevents replay across operations)
- the schema version (prevents downgrade-to-older-format attacks)

Decryption fails if any field is altered.

### Ciphertext envelope (v2 format)

```
aes256gcm:v2:<base64(iv||ciphertext||tag||wrapped_dek||header_json)>
```

The header_json includes:
- `tenant_id` — opaque UUID
- `kek_id` — which KEK wrapped the DEK
- `created_at` — ISO 8601 UTC
- `receipt_id` — chain anchor

The DEK is wrapped using the tenant KEK via AES-KW (RFC 3394). On BYOK, the wrap/unwrap happens in customer KMS — NoData never sees the unwrapped DEK.

---

## 2. Key derivation (HKDF-SHA-256) — where it's used today

**Algorithm:** HKDF as specified in RFC 5869, SHA-256 hash.

Used in production for:
- **MyGate / E2E content envelopes** — ECDH P-256 shared secret → HKDF-expand → AES-GCM key. See `apps/web/src/lib/mygate-crypto-core.ts:54` (open source).
- **Future** per-tenant KEK derivation — currently a single server-managed KEK; HKDF-from-master is on the engineering list when contract requirements drive it.

The audit chain HMAC secret is **not** HKDF-derived today. See `audit-chain-spec.md` §7 for the actual storage model (single global secret, env-var or GCP-KMS-wrapped).

---

## 3. Audit chain HMAC — production formula

**Algorithm:** HMAC-SHA-256
**Key:** single global server secret (`PROOF_HMAC_SECRET`), env-var fallback or GCP-KMS-wrapped at rest. See `apps/web/src/lib/secrets.ts`.
**Output:** 64-char hex, stored in `nd_protect_receipts.chain_hmac`.

The exact construction (matches code at `apps/web/src/app/api/verify/route.ts:156-158`):

```
hmacInput = event_hash + "|" + (prev_receipt_id || "") + "|" + created_at
chain_hmac = HMAC-SHA-256(secret, hmacInput)
```

Full chain math, verification scenarios, and tampering-detection examples in `audit-chain-spec.md`.

```
HMAC_i = HMAC(chain_secret, prev_hmac || event_canonical_json || timestamp)
```

Each receipt names its predecessor by ID. Tampering with any historical event breaks the chain at that point and every subsequent receipt; this is detectable by any verifier given the chain endpoint.

---

## 4. Hashing (SHA-256)

**Algorithm:** SHA-256 (FIPS 180-4)
**Used for:**
- Content fingerprinting (`content_hash`)
- Receipt event canonicalization
- Manifest integrity in audit-export.zip
- File-tree Merkle nodes (folder signing)

We publish content hashes alongside artifacts so any consumer can independently verify what they received.

### Perceptual hashing (NOT a cryptographic primitive)

For image content, we additionally publish a perceptual hash (`pHash`) using the standard 64-bit DCT-based pHash. **This is not authenticated** and exists only to help detect near-duplicate signatures over visually-similar derivatives. Verification logic uses pHash as a hint, never as proof.

---

## 5. File / folder signing

### Single file (`nodata sign <file>`)

```
content_hash = SHA256(file_bytes)
receipt = {
  content_hash,
  perceptual_hash?,  // images only
  signer_nickname,
  signer_device_id,
  signed_at,
  prev_receipt_id,
  chain_index
}
chain_hmac = HMAC(chain_secret, prev_hmac || canonical(receipt))
sidecar = receipt + chain_hmac (written to <file>.nodatasig)
```

Verification requires the original file or its `content_hash`, the sidecar, and a roundtrip to `/api/verify`.

### Folder / Merkle tree (`nodata sign --dir <path>`)

A canonical traversal of the directory:
1. List all files in lexicographic order, excluding `.git`, `node_modules`, `.nodata-tree.sig`, and patterns from `.nodata-ignore`
2. Compute SHA-256 of each file's bytes
3. Build a Merkle tree: leaves are file hashes prefixed with `0x00`, internal nodes are SHA-256 of `0x01 || left || right` (RFC-style domain separation)
4. The root hash anchors a single receipt
5. `.nodata-tree.sig` is written at the directory root with the receipt and a manifest of (path, hash, size, mtime)

`nodata verify --dir` replays the traversal, recomputes the root, and reports added / removed / modified files relative to the manifest. Detection is exact, not heuristic.

### Region signing (`@nodata-sign-begin <id>` / `@nodata-sign-end <id>`)

For intra-file signing, content between markers is canonicalized (whitespace-normalized at line boundaries only — semantic whitespace inside the region is preserved) and hashed. The region's hash anchors a sub-receipt linked to the file's overall sig.

Five comment-style families are recognized: `//`, `#`, `--`, `<!-- -->`, `/* */`.

---

## 6. Random number generation

All cryptographic randomness uses the platform CSPRNG:
- Node: `crypto.randomBytes()`
- Python: `secrets` module / `os.urandom()`
- Go: `crypto/rand`

We do not seed our own RNG. We never use `Math.random()`, `random.random()`, or non-CS PRNGs for any security purpose. CI greps for these in our codebase.

---

## 7. Constant-time operations

All MAC comparisons use constant-time equality (`crypto.timingSafeEqual` / `hmac.compare_digest` / `subtle.ConstantTimeCompare`). All public verification endpoints use timing-safe comparison.

We rely on AES-NI / ARMv8-AES for constant-time AES on supported hardware. Production environments without these instructions trigger a startup warning.

---

## 8. Forward secrecy

TLS 1.3 with X25519 key exchange. Static RSA cipher suites are disabled. Per-tenant chain secrets rotate on policy (default: 90 days, configurable per contract).

---

## 9. Algorithm transition table

| Component | Today | 2026 H2 (planned) | Rationale |
| --- | --- | --- | --- |
| Symmetric encryption | AES-256-GCM | (no change) | Industry standard, hardware accelerated |
| Hash | SHA-256 | + SHA-3-256 dual-attestation option | Defense in depth |
| MAC | HMAC-SHA-256 | (no change) | Chain link, post-quantum safe |
| Receipt signature | Ed25519 (shipped) | + PQC signature alongside | Offline verification without us; Ed25519 is not PQ-safe |
| Epoch anchoring | Merkle root (RFC 6962) → external witness feed + OpenTimestamps/Bitcoin (shipped) | additional independent witness | Removes "trust the operator to keep one history" |
| KDF | HKDF-SHA-256 | (no change) | Standard, well-analyzed |
| TLS | 1.3 | (no change) | |
| PQC | none | Hybrid X25519+Kyber768 (where supported) | Hedge against quantum |

We publish this table because the right question from a sophisticated buyer is *"what do you change next, and why?"* — not *"what do you have today?"*.

---

## 10. Compliance notes

| Standard | Status | Comment |
| --- | --- | --- |
| FIPS 140-3 | Underlying libraries (OpenSSL 3.x) ship FIPS-validated modules | Available in FIPS mode for enterprise tenants |
| NIST SP 800-38D (GCM) | Compliant | IV construction follows §8.2.1 (random IV) |
| RFC 5869 (HKDF) | Compliant | |
| RFC 3394 (AES-KW) | Compliant | Used for DEK wrapping |
| NIST SP 800-90A | Compliant via OS CSPRNG | We do not implement our own DRBG |

---

## 11. What is *not* in this document

- The exact byte layout of the `.nodatasig` sidecar — see source: [`@nodatachat/protect`](https://www.npmjs.com/package/@nodatachat/protect) `src/sidecar.ts`
- Network protocol details — see `audit-chain-spec.md` §6
- Server-side key wrapping internals — closed source by policy, see `open-vs-closed.md`

---

*Have we made a wrong choice anywhere? File an issue:*
*github.com/daviderez4/nodatachat-core/issues — we publish all crypto challenges and responses.*
