| 1 | # π€ Golden vectors β the cross-language crypto contract |
| 2 | |
| 3 | `contra-v1.json` is the reference for every derivation in `PROTOCOL.md`. |
| 4 | |
| 5 | CONTRA implements the same crypto three times: Go on the instance, JavaScript |
| 6 | in the console, Swift on the Mac. They must produce identical bytes. **All |
| 7 | three suites pass this one file, or the contract is not being kept** β a second |
| 8 | implementation that passes vectors of its own has proved nothing, and cross- |
| 9 | platform KDF drift is the bug class behind the 1Password PRF mismatch that |
| 10 | shaped our whole recovery design. |
| 11 | |
| 12 | Changing a value in this file is changing the protocol. |
| 13 | |
| 14 | ## What it pins |
| 15 | |
| 16 | Argon2id at `m=64MiB, t=3, p=1`; all five `contra/v1/*` HKDF labels; the KCV |
| 17 | construction; P-256 derivation from a seed, including a seed that forces the |
| 18 | rejection branch; Crockford Base32 with the Oβ0 and I/Lβ1 mappings; the 20-bit |
| 19 | checksum and a single-substitution repair; AES-256-GCM with its AAD binding. |
| 20 | `account_chain` is the whole hierarchy end to end, so a new implementation can |
| 21 | bisect where it diverges rather than staring at one wrong key. |
| 22 | |
| 23 | The wire formats `PROTOCOL.md` leaves open β HKDF mode, the AAD layout, |
| 24 | Crockford packing, the rejection loop β are pinned in **discussion 19**. Read |
| 25 | that before implementing against this file. |
| 26 | |
| 27 | ## Where the numbers come from |
| 28 | |
| 29 | The generator is `gen/gen.c`, **C against OpenSSL** β deliberately not Go. |
| 30 | |
| 31 | Generating vectors from an implementation and then testing that implementation |
| 32 | against them proves only that the code agrees with itself. So the vectors come |
| 33 | from an implementation nobody here wrote, and the Go package is only ever the |
| 34 | thing under test. If you are ever tempted to regenerate this file to make a |
| 35 | test pass, stop: that is the exact failure the arrangement exists to prevent. |
| 36 | Fix the code, or argue on the record that the spec is wrong. |
| 37 | |
| 38 | `gen.c` also re-computes published third-party vectors β the PHC Argon2id |
| 39 | reference outputs, RFC 5869, RFC 4231, NIST CAVP AES-GCM β and aborts if any |
| 40 | disagrees. Otherwise "independent" would only mean the two implementations are |
| 41 | not the same, not that either is right. |
| 42 | |
| 43 | Every input is generated from its own published label: |
| 44 | |
| 45 | fixture(label, n) = SHA-256("contra/v1/vectors/" || label)[0..n] |
| 46 | |
| 47 | so the file regenerates from the source alone, and nothing in it is, or may be |
| 48 | reused as, a real secret (`AGENTS.md` decision 8). |
| 49 | |
| 50 | ## Regenerating |
| 51 | |
| 52 | cd gen && make vectors |
| 53 | |
| 54 | `REJECT_K` in the `Makefile` is the seed witness that forces a P-256 rejection. |
| 55 | It is pinned rather than searched each time because finding one costs a few |
| 56 | billion HKDF evaluations β a rejection is a ~2^-32 event. If the |
| 57 | `contra/v1/ec-p256` label ever changes, the witness dies with it: run |
| 58 | `make search` (minutes, all cores) and pin the new `k`. |
| 59 | |
| 60 | ## Verifying the Go side |
| 61 | |
| 62 | go test ./internal/contra/crypto/ |
| 63 | |
| 64 | The suite has been mutation-checked: lowering the Argon2 time cost, typoing an |
| 65 | HKDF label, truncating the KCV, dropping the AAD length prefixes or its domain |
| 66 | tag, skipping the rejected EC candidate, accepting non-zero Base32 pad bits, |
| 67 | widening the checksum, and dropping the O/I/L mappings each turn it red. A |
| 68 | vector nobody has watched fail is a vector nobody should trust. |
| 69 | |