|
| 1 | +# CLAUDE.md — @forgesworn/private-equality |
| 2 | + |
| 3 | +AI agent instructions for working on this codebase. |
| 4 | + |
| 5 | +## Build & Test |
| 6 | + |
| 7 | +```bash |
| 8 | +npm run build # tsc → dist/ |
| 9 | +npm test # vitest (single run) |
| 10 | +npm run coverage # vitest with the per-file coverage gate |
| 11 | +npm run typecheck # tsc --noEmit |
| 12 | +``` |
| 13 | + |
| 14 | +Always run `npm test` after changes. Always run `npm run typecheck` before committing. |
| 15 | + |
| 16 | +## Architecture |
| 17 | + |
| 18 | +| File | Purpose | |
| 19 | +|------|---------| |
| 20 | +| `src/group.ts` | Isolates `@noble/curves` ristretto255 — the ONLY file importing it. Point/scalar codecs, `hashToScalar`, `randomScalar`. `Pt` is a structural interface (dodges TS4094 on @noble's anonymous class). | |
| 21 | +| `src/zkp.ts` | Three sigma proofs: PoK of a discrete log, representation (`P = g3^r`, `Q = g1^r · g2^y`), equality of two discrete logs. All Fiat–Shamir with domain separation + per-proof tag byte. | |
| 22 | +| `src/smp.ts` | The four-message OTR-SMP state machine + fixed-layout wire codec. `initiate`/`respond` entry points. | |
| 23 | +| `src/types.ts` | Public types: `Secret`, `SmpResult`, `SmpStep`, `SmpSession`, `SmpError`, `PRIVATE_EQUALITY_VERSION`. | |
| 24 | +| `src/index.ts` | Public API re-exports. | |
| 25 | + |
| 26 | +### Data flow |
| 27 | + |
| 28 | +Pure state machine — no transport. The consumer ferries opaque `Uint8Array` |
| 29 | +messages over an authenticated channel: `initiate(secret, binding)` → |
| 30 | +`{ session, first }`; each side calls `session.next(incoming)` until |
| 31 | +`{ done: true, result: { match } }`. Four messages total. |
| 32 | + |
| 33 | +## Crypto Safety — Do Not Change Without Expert Review |
| 34 | + |
| 35 | +- **Domain separators** (`private-equality/pok-v1`, `private-equality/repr-v1`, |
| 36 | + `private-equality/eq-v1`, `private-equality/secret-v1`) are protocol |
| 37 | + constants. Changing them breaks interoperability between versions. |
| 38 | +- **Per-proof tag bytes** separate the distinct proofs within a session. Do not |
| 39 | + renumber or merge them — two proofs sharing a challenge domain enables proof |
| 40 | + transplantation. |
| 41 | +- **`sessionBinding` is folded into every Fiat–Shamir challenge.** Never make it |
| 42 | + optional and never drop it from a challenge — it is the MITM defence. |
| 43 | +- **Identity points are rejected on the wire** (decode-time). Do not relax this — |
| 44 | + it closes the degenerate `QaQb = identity` case. |
| 45 | +- **The zero secret-scalar guard** in secret derivation is intentional. Do not |
| 46 | + remove it. |
| 47 | +- **The wire codec is fixed-layout** (32-byte points and scalars, strict length |
| 48 | + checks). Do not add variable-length fields without redesigning the codec. |
| 49 | +- **`PRIVATE_EQUALITY_VERSION` in `src/types.ts` is pinned by |
| 50 | + `src/smoke.test.ts` as a literal string.** Anvil bumps `package.json` from |
| 51 | + commit prefixes; the constant does not update itself. Any releasing change |
| 52 | + (`feat:`/`fix:`) must update `src/types.ts` and `src/smoke.test.ts` to the |
| 53 | + new version in the same PR. |
| 54 | + |
| 55 | +## Conventions |
| 56 | + |
| 57 | +- **British English** in all prose (colour, initialise, behaviour, licence). |
| 58 | +- **ESM-only** — all imports use `.js` extensions. |
| 59 | +- **Commit messages**: `type: description` (e.g. `feat: add subpath export`, `fix: reject oversized message`). |
| 60 | +- **No `Co-Authored-By`** lines in commits. |
| 61 | +- **Anvil auto-release on main** — every push to main runs `forgesworn/anvil@v0` and can auto-publish. Work on branches; merge to main only when a logical chunk is complete. |
| 62 | + |
| 63 | +## Release & Versioning |
| 64 | + |
| 65 | +**Automated via [forgesworn/anvil](https://github.com/forgesworn/anvil)** — |
| 66 | +`auto-release.yml` reads conventional commits on push to `main`, bumps the |
| 67 | +version, and creates a GitHub Release; `release.yml` then runs the pre-publish |
| 68 | +gates and publishes to npm via OIDC trusted publishing. |
| 69 | + |
| 70 | +| Type | Version Bump | |
| 71 | +|------|--------------| |
| 72 | +| `fix:` | Patch (0.1.x) | |
| 73 | +| `feat:` | Minor (0.x.0) | |
| 74 | +| `BREAKING CHANGE:` (in commit body) | Major (x.0.0) | |
| 75 | +| `chore:`, `docs:`, `refactor:`, `ci:` | None | |
| 76 | + |
| 77 | +Every release needs a matching `CHANGELOG.md` entry (anvil extracts the release |
| 78 | +body from it) and a synchronised `PRIVATE_EQUALITY_VERSION`. |
| 79 | + |
| 80 | +## Testing |
| 81 | + |
| 82 | +Tests live in `src/*.test.ts`. Vitest with a per-file coverage gate |
| 83 | +(`vitest.config.ts`). Coverage floor: `group`/`zkp` at 100%, `smp` at 100% |
| 84 | +lines / 87% branch. Tests cover the full protocol round-trip, mismatch |
| 85 | +non-leakage, binding mismatch aborts, malformed/replayed messages, and |
| 86 | +tampered proofs. PRs touching `zkp.ts` or `smp.ts` require crypto review. |
0 commit comments