Contributing¶
Thanks for helping! The one thing to understand about this repository: the product is a bit-exact cross-language mapping. Everything below follows from that.
Ground rules¶
SPEC.mdis normative. Code follows the spec, never the other way around. If an implementation and the spec disagree, the implementation is wrong (or the spec needs an RFC — open an issue first).- Any change that alters
encodeoutput ordecodeacceptance is a new format version, not a patch. Format v1 outputs are frozen forever. - An implementation is conformant iff it passes the files in
testvectors/: ff1_nist.json— the official NIST FF1 samples (validates the FF1 core),v1.json— dealcode vectors (validates the full codec). This covers every section:vectors,invalid_codes,normalize,range_counters, and the top-levelinvalid_configs(see SPEC.md §9 for the exact obligations of each),v1c.json— fixed-length cycling mode vectors (SPEC.md §11), required for implementations that ship the mode; all seven here do.
Working on an implementation¶
Each directory (python/, js/, go/, java/, rust/, c/, cpp/) is a
self-contained, idiomatically packaged library. Its README shows how to run
its tests. Shared expectations:
- No new runtime dependencies. AES/SHA-256 come from the platform's standard or designated crypto library only.
- Public APIs stay small and mirrored across languages: construct a codec
(key, alphabet, min/max length, domain) →
encode/decode→ three error kinds (config / range / invalid code). - Codecs are immutable and safe for concurrent use.
Changing the spec or vectors¶
- Open an issue describing the change and its compatibility impact.
- Spec text, the Python reference (
python/), regenerated vectors (python3 scripts/generate_test_vectors.py), and updates to all implementations land in a single PR. - Output-affecting changes bump the format version (new tweak prefix
dealcode/v2/...) and add new vector files next to the old ones — v1 vectors are never edited.
Adding a new language¶
Port from SPEC.md alone (peeking at python/ is fine), make both vector
files pass in your test suite, add a README with a quickstart and a database
recipe, and wire a job into .github/workflows/ci.yml (including the new
name in the only input's options list). That's the whole bar.
Branches & merging¶
Trunk-based: main is always releasable and is protected against
force-push and deletion; release tags (v*, go/v*) are immutable.
Contribute from a fork/feature branch via PR — squash-merged, branch
auto-deleted. Versions are SemVer, released in lock-step across all seven
packages (see RELEASING). Dependabot patch/minor updates auto-merge once
CI passes; majors wait for a human.
CI¶
CI runs automatically on every push to main and on every pull request
(all seven implementations plus a vectors-reproducibility check). Please
still run your language's suite locally before pushing — it's faster
feedback than the runners. gh workflow run ci.yml -f only=<job> remains
available for one-off manual runs of a single job.
Releasing (maintainers)¶
Registry publishing is automated per registry via the publish-*.yml
workflows; the full checklist, the credential/Trusted-Publisher
inventory, and hard-won bootstrap notes live in
RELEASING.md.
License¶
MIT. By contributing you agree your contribution is MIT-licensed.
Conformance in practice¶
The bar for "this implementation is correct" is deliberately mechanical:
pass the files in
testvectors/
(v1c.json covers the fixed-length cycling mode, required for
implementations that ship it — all seven here do).
ff1_nist.json pins the FF1 core to the nine official NIST sample
vectors, independent of dealcode's own layer. If your FF1 is right, these
pass; if these pass, seven different FF1 implementations are provably
computing the same permutation. (NIST publications are U.S. public domain.)
v1.json exercises the full codec: every preset alphabet, stage
boundaries (the exact counters where codes grow a character), both
key-derivation paths (AES-sized bytes used directly vs. everything else
derived), decode normalization (hex case-folding, Crockford O→0,
I/L→1), domains, and codes that MUST be rejected — wrong length, wrong
charset, out-of-stage, out-of-counter-space. It is generated from the Python
reference implementation by
scripts/generate_test_vectors.py;
counters are encoded as JSON strings because they exceed 2^53.
Every language's test suite consumes both files, so a port is conformant the moment its suite is green — no judgment calls. This is also why v1 vectors are frozen forever: they are the compatibility contract between implementations, and between you and every code your users have already been issued.