fpr-ff1 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fpr_ff1-0.1.0/.codegraph/.gitignore +5 -0
- fpr_ff1-0.1.0/.gitattributes +21 -0
- fpr_ff1-0.1.0/.github/workflows/ci.yml +108 -0
- fpr_ff1-0.1.0/.github/workflows/publish.yml +41 -0
- fpr_ff1-0.1.0/.gitignore +37 -0
- fpr_ff1-0.1.0/.gitleaks.toml +4 -0
- fpr_ff1-0.1.0/.python-version +1 -0
- fpr_ff1-0.1.0/AGENTS.md +140 -0
- fpr_ff1-0.1.0/CHANGELOG.md +9 -0
- fpr_ff1-0.1.0/LICENSE +21 -0
- fpr_ff1-0.1.0/PKG-INFO +348 -0
- fpr_ff1-0.1.0/README.md +322 -0
- fpr_ff1-0.1.0/SECURITY.md +86 -0
- fpr_ff1-0.1.0/docs/AGENTS.md +25 -0
- fpr_ff1-0.1.0/docs/architecture.md +59 -0
- fpr_ff1-0.1.0/docs/backlog.md +62 -0
- fpr_ff1-0.1.0/docs/configuration.md +28 -0
- fpr_ff1-0.1.0/docs/developer-guide.md +107 -0
- fpr_ff1-0.1.0/docs/directory-structure.md +45 -0
- fpr_ff1-0.1.0/justfile +69 -0
- fpr_ff1-0.1.0/pyproject.toml +120 -0
- fpr_ff1-0.1.0/src/fpr_ff1/__init__.py +23 -0
- fpr_ff1-0.1.0/src/fpr_ff1/_exceptions.py +34 -0
- fpr_ff1-0.1.0/src/fpr_ff1/_ff1.py +549 -0
- fpr_ff1-0.1.0/src/fpr_ff1/py.typed +0 -0
- fpr_ff1-0.1.0/tests/__init__.py +0 -0
- fpr_ff1-0.1.0/tests/_oracle/__init__.py +95 -0
- fpr_ff1-0.1.0/tests/_oracle/_m2crypto_shim.py +85 -0
- fpr_ff1-0.1.0/tests/conftest.py +18 -0
- fpr_ff1-0.1.0/tests/test_contract.py +205 -0
- fpr_ff1-0.1.0/tests/test_differential.py +231 -0
- fpr_ff1-0.1.0/tests/test_exact_arithmetic.py +140 -0
- fpr_ff1-0.1.0/tests/test_intermediates.py +59 -0
- fpr_ff1-0.1.0/tests/test_interoperability.py +120 -0
- fpr_ff1-0.1.0/tests/test_nist_vectors.py +54 -0
- fpr_ff1-0.1.0/tests/test_properties.py +147 -0
- fpr_ff1-0.1.0/tests/test_smoke.py +70 -0
- fpr_ff1-0.1.0/tests/test_validation.py +497 -0
- fpr_ff1-0.1.0/tests/vectors/nist_ff1_intermediates.json +7797 -0
- fpr_ff1-0.1.0/tests/vectors/nist_ff1_samples.json +87 -0
- fpr_ff1-0.1.0/uv.lock +581 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Normalise line endings to LF in the repository and on checkout, on every
|
|
2
|
+
# platform.
|
|
3
|
+
#
|
|
4
|
+
# Without this, Windows checkouts get CRLF (git's core.autocrlf defaults to
|
|
5
|
+
# true on GitHub's Windows runners) while `[tool.ruff.format] line-ending`
|
|
6
|
+
# is "lf", so `ruff format --check` reports every file as needing reformatting
|
|
7
|
+
# and the Windows CI leg fails before it runs a single test.
|
|
8
|
+
* text=auto eol=lf
|
|
9
|
+
|
|
10
|
+
# Test fixtures are compared byte-for-byte against transcribed NIST values;
|
|
11
|
+
# never let any tool rewrite them.
|
|
12
|
+
tests/vectors/*.json text eol=lf
|
|
13
|
+
|
|
14
|
+
# Lockfiles and generated artefacts.
|
|
15
|
+
uv.lock text eol=lf
|
|
16
|
+
|
|
17
|
+
# Anything genuinely binary, so git does not attempt conversion.
|
|
18
|
+
*.png binary
|
|
19
|
+
*.jpg binary
|
|
20
|
+
*.gz binary
|
|
21
|
+
*.whl binary
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
# Allows publish.yml to reuse this workflow as a release gate.
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
env:
|
|
16
|
+
# Turn a missing differential oracle into a hard failure. Those tests are
|
|
17
|
+
# the only coverage for radices without published NIST vectors, so a silent
|
|
18
|
+
# skip would look exactly like a pass.
|
|
19
|
+
FPR_FF1_REQUIRE_ORACLE: "1"
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
quality:
|
|
23
|
+
name: ${{ matrix.os }} / py${{ matrix.python-version }}
|
|
24
|
+
runs-on: ${{ matrix.os }}
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
29
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- name: Install uv
|
|
35
|
+
uses: astral-sh/setup-uv@v5
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
cache-dependency-glob: uv.lock
|
|
39
|
+
|
|
40
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
41
|
+
run: uv python install ${{ matrix.python-version }}
|
|
42
|
+
|
|
43
|
+
- name: Sync dependencies
|
|
44
|
+
run: uv sync --all-extras --dev --python ${{ matrix.python-version }}
|
|
45
|
+
|
|
46
|
+
- name: Format check
|
|
47
|
+
run: uv run ruff format --check .
|
|
48
|
+
|
|
49
|
+
- name: Lint
|
|
50
|
+
run: uv run ruff check .
|
|
51
|
+
|
|
52
|
+
- name: Type check
|
|
53
|
+
run: uv run pyright
|
|
54
|
+
|
|
55
|
+
# FPR_FF1_REQUIRE_ORACLE (set at workflow level) makes a missing oracle
|
|
56
|
+
# raise at collection time, so this run cannot pass with the
|
|
57
|
+
# differential and interoperability suites silently skipped.
|
|
58
|
+
- name: Test with 100% coverage gate
|
|
59
|
+
run: uv run pytest
|
|
60
|
+
|
|
61
|
+
build:
|
|
62
|
+
name: Build distributions
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
needs: quality
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@v4
|
|
67
|
+
|
|
68
|
+
- name: Install uv
|
|
69
|
+
uses: astral-sh/setup-uv@v5
|
|
70
|
+
with:
|
|
71
|
+
enable-cache: true
|
|
72
|
+
|
|
73
|
+
- name: Build
|
|
74
|
+
run: uv build
|
|
75
|
+
|
|
76
|
+
- name: Check metadata
|
|
77
|
+
run: uvx twine check dist/*
|
|
78
|
+
|
|
79
|
+
- uses: actions/upload-artifact@v4
|
|
80
|
+
with:
|
|
81
|
+
name: distributions
|
|
82
|
+
path: dist/
|
|
83
|
+
|
|
84
|
+
secrets:
|
|
85
|
+
name: Secret scan
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
env:
|
|
88
|
+
# Pinned so a scanner update cannot turn a release red without warning.
|
|
89
|
+
GITLEAKS_VERSION: "8.30.1"
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@v4
|
|
92
|
+
with:
|
|
93
|
+
fetch-depth: 0
|
|
94
|
+
|
|
95
|
+
# The gitleaks CLI is used rather than gitleaks/gitleaks-action, which
|
|
96
|
+
# rejects `release` events outright ("ERROR: The [release] event is not
|
|
97
|
+
# yet supported"). publish.yml reuses this workflow as its release gate,
|
|
98
|
+
# so the action failed there and blocked publishing. The CLI has no
|
|
99
|
+
# event restrictions and runs the same command as `just secrets`.
|
|
100
|
+
- name: Install gitleaks
|
|
101
|
+
run: |
|
|
102
|
+
curl -sSfL \
|
|
103
|
+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
|
104
|
+
| sudo tar -xz -C /usr/local/bin gitleaks
|
|
105
|
+
gitleaks version
|
|
106
|
+
|
|
107
|
+
- name: Scan for secrets
|
|
108
|
+
run: gitleaks dir . --redact --exit-code 1
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC). No API tokens are stored
|
|
4
|
+
# in this repository; configure the publisher at
|
|
5
|
+
# https://pypi.org/manage/project/fpr-ff1/settings/publishing/
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
quality:
|
|
17
|
+
# Never publish something that has not passed the full matrix.
|
|
18
|
+
uses: ./.github/workflows/ci.yml
|
|
19
|
+
|
|
20
|
+
publish:
|
|
21
|
+
needs: quality
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment:
|
|
24
|
+
name: pypi
|
|
25
|
+
url: https://pypi.org/p/fpr-ff1
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write # required for Trusted Publishing
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
|
|
31
|
+
- name: Install uv
|
|
32
|
+
uses: astral-sh/setup-uv@v5
|
|
33
|
+
|
|
34
|
+
- name: Build
|
|
35
|
+
run: uv build
|
|
36
|
+
|
|
37
|
+
- name: Check metadata
|
|
38
|
+
run: uvx twine check dist/*
|
|
39
|
+
|
|
40
|
+
- name: Publish to PyPI
|
|
41
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
fpr_ff1-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.sample
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
.python-version.local
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
ENV/
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pyright/
|
|
18
|
+
# Hypothesis property-testing scratch database (per-machine, never commit).
|
|
19
|
+
.hypothesis/
|
|
20
|
+
.coverage
|
|
21
|
+
coverage.xml
|
|
22
|
+
htmlcov/
|
|
23
|
+
dist/
|
|
24
|
+
build/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
|
|
27
|
+
# Editors and OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Logs and local scratch
|
|
35
|
+
*.log
|
|
36
|
+
.tmp/
|
|
37
|
+
tmp/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12.13
|
fpr_ff1-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Agent contract for this repository. Read fully before any code change.
|
|
4
|
+
|
|
5
|
+
## Project
|
|
6
|
+
|
|
7
|
+
Open-source Python implementation of **FF1**, the format-preserving encryption mode from NIST SP 800-38G. Published as a standalone PyPI library (`fpr-ff1`; claiming the name and configuring Trusted Publishing is a pre-1.0 backlog item) — no network, accounts, or key-management features. It replaces `ubiq_security_fpe`, deprecated in favour of a SaaS client and no longer maintained.
|
|
8
|
+
|
|
9
|
+
The design goal: a reviewer can compare the source against SP 800-38G line by line and find no gaps.
|
|
10
|
+
|
|
11
|
+
### Scope
|
|
12
|
+
|
|
13
|
+
- **In scope:** FF1 encrypt/decrypt, parameter validation, numeral and string interfaces, alphabet handling, tests, docs.
|
|
14
|
+
- **Out of scope, permanently:** FF3/FF3-1, identifier generation, persistence, checksums, key generation/storage/derivation, application-specific defaults or alphabets.
|
|
15
|
+
- A private downstream project consumes this library. **Nothing about that consumer may leak into this package.** Reject radix-32 defaults, fixed 13-numeral lengths, or any caller-specific convenience; a feature that only makes sense for one caller belongs in the caller.
|
|
16
|
+
|
|
17
|
+
### FF1 only
|
|
18
|
+
|
|
19
|
+
FF3 and FF3-1 are **not** in scope and must never be added. The February 2025 second public draft of SP 800-38G Rev. 1 removes FF3 entirely, following Beyne's tweak-schedule weakness affecting both FF3 and FF3-1. Being FF1-only is a deliberate feature; the README says so.
|
|
20
|
+
|
|
21
|
+
## Standards baseline
|
|
22
|
+
|
|
23
|
+
SP 800-38G (2016, updated 2019) is the normative text, plus the Rev. 1 second public draft's tightened limits:
|
|
24
|
+
|
|
25
|
+
- Minimum domain: `radix ** minlen >= 1_000_000` — stricter than the 2016 text's `>= 100`; a deliberate fail-closed choice noted in the changelog.
|
|
26
|
+
- Maximum length: `maxlen < 2**32`, implemented as `2**32 - 1` (fail closed on the boundary).
|
|
27
|
+
- Key sizes: 128, 192, 256 bits only.
|
|
28
|
+
- Radix: `2 <= radix < 2**16`.
|
|
29
|
+
- Forward AES only; no inverse cipher function.
|
|
30
|
+
- Exactly 10 rounds.
|
|
31
|
+
- **No floating-point arithmetic anywhere in the FF1 core.**
|
|
32
|
+
|
|
33
|
+
Rev. 1 is still a draft. Track its status; if it is finalised with different limits, that is a breaking change requiring a major version. The README documents the draft baseline.
|
|
34
|
+
|
|
35
|
+
## Public API
|
|
36
|
+
|
|
37
|
+
A single `FF1` class:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
class FF1:
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
key: bytes, # 16, 24 or 32 bytes
|
|
44
|
+
radix: int,
|
|
45
|
+
*,
|
|
46
|
+
alphabet: str | None = None, # enables the str interface
|
|
47
|
+
tweak: bytes = b"", # default tweak
|
|
48
|
+
min_tweak_len: int | None = None,
|
|
49
|
+
max_tweak_len: int | None = None,
|
|
50
|
+
) -> None: ...
|
|
51
|
+
|
|
52
|
+
# numeral interface - the primitive
|
|
53
|
+
def encrypt_numerals(self, x: Sequence[int], tweak: bytes | None = None) -> list[int]: ...
|
|
54
|
+
def decrypt_numerals(self, x: Sequence[int], tweak: bytes | None = None) -> list[int]: ...
|
|
55
|
+
|
|
56
|
+
# string interface - requires alphabet
|
|
57
|
+
def encrypt(self, s: str, tweak: bytes | None = None) -> str: ...
|
|
58
|
+
def decrypt(self, s: str, tweak: bytes | None = None) -> str: ...
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def min_length(self) -> int: ...
|
|
62
|
+
@property
|
|
63
|
+
def max_length(self) -> int: ...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Rules:
|
|
67
|
+
|
|
68
|
+
- `encrypt_numerals` / `decrypt_numerals` are the primitive; `encrypt` / `decrypt` are thin string wrappers. Do not duplicate logic between them.
|
|
69
|
+
- String methods without an `alphabet` raise, with a message pointing at the numeral interface.
|
|
70
|
+
- Validate `alphabet` length and uniqueness at construction.
|
|
71
|
+
- No global or module-level state, no implicit default key, no environment-variable configuration.
|
|
72
|
+
- Every rejection raises a typed exception rooted at `FF1Error`; never silently truncate, pad, coerce, or clamp.
|
|
73
|
+
|
|
74
|
+
## Implementation gotchas
|
|
75
|
+
|
|
76
|
+
These are the failure modes that produce **plausible but wrong output** — every one still runs without raising.
|
|
77
|
+
|
|
78
|
+
- `b` is derived from `v`, not `u`. They differ when `n` is odd.
|
|
79
|
+
- Bit length is `(radix ** v - 1).bit_length()`. Never `math.log2`, `math.log`, `math.ceil`, `**0.5`, or float literals — this is the Bouncy Castle bug class. Leave a comment saying so, or it will be "simplified" later.
|
|
80
|
+
- Padding is `(-t - b - 1) % 16`. Python's modulo already returns the non-negative result; do not rewrite it defensively.
|
|
81
|
+
- Encrypt and decrypt differ in exactly three places: `Q` built from `B` vs `A`; round order `0..9` vs `9..0`; final assignment `A, B = B, C` vs `B, A = A, C`. The parity rule `m = u if i % 2 == 0 else v` is **identical in both** — do not mirror it.
|
|
82
|
+
- `S` is truncated to `d` bytes, not `d` bits.
|
|
83
|
+
- The PRF is CBC-MAC with a zero IV over 16-byte-aligned input.
|
|
84
|
+
- Cipher context caching: cache **one ECB encryptor** and call `update()` repeatedly (roughly 1.6x). Never call `finalize()` on it. Never cache a CBC encryptor (it carries chaining state).
|
|
85
|
+
- Cite spec steps in internal docstrings, e.g. "SP 800-38G Algorithm 7, step 6.iii".
|
|
86
|
+
|
|
87
|
+
## Local development
|
|
88
|
+
|
|
89
|
+
- Python `>=3.12,<3.15` (CI matrix: 3.12, 3.13, 3.14 across Linux, macOS, Windows). Local dev on 3.12. Tools: `uv`, `just`. `src/` layout; ships `py.typed`.
|
|
90
|
+
- `just setup` — create `.venv` and install deps.
|
|
91
|
+
- `just quality` — format check, lint, typecheck, tests with coverage.
|
|
92
|
+
- `just build` — quality gate plus `uv build`.
|
|
93
|
+
- `just secrets` — gitleaks scan (must be installed locally).
|
|
94
|
+
|
|
95
|
+
`pyproject.toml` enforces a **100% line and branch coverage floor** on the FF1 module (`fail_under = 100`, branch coverage on). Every raise path must be exercised by a test; delete unreachable branches rather than leaving dead code.
|
|
96
|
+
|
|
97
|
+
Documentation is maintained under `docs/`. Update the matching doc file in the same change that makes it true; see `docs/AGENTS.md`.
|
|
98
|
+
|
|
99
|
+
## Tests
|
|
100
|
+
|
|
101
|
+
Conformance is the product. A change that makes tests pass by weakening them is a defect.
|
|
102
|
+
|
|
103
|
+
1. **NIST sample vectors:** all 9 published samples, encrypt and decrypt (AES-128/192/256, radix 10 with/without tweak, radix 36).
|
|
104
|
+
2. **Per-round intermediates — the real conformance test:** the NIST document publishes `P`, `Q`, `R`, `S`, `y`, `m`, `c`, `C`, plus derived `u`, `v`, `b`, `d` for every round. Transcribe these and assert round-by-round via a **test-only trace hook** (never exported from the public API). Two compensating bugs can pass an output test; they cannot pass an intermediate test.
|
|
105
|
+
3. **Parameter validation:** exercise every rejection path — key lengths 0/15/17/23/25/31/33 bytes; radix 0, 1, `2**16`, negative; lengths below `min_length` (and at `min_length` exactly, which must succeed) and above `max_length`; numerals negative or `>= radix`; tweak bounds; alphabet length mismatch, duplicate characters, non-str; string characters absent from the alphabet; string methods without an alphabet. Verify `min_length` across radices: 2 → 20, 10 → 6, 16 → 5, 32 → 4, 36 → 4, 256 → 3.
|
|
106
|
+
4. **Exact-arithmetic regression:** radices where `radix ** v` sits exactly on a power-of-two boundary (2, 4, 8, 16, 32, 64, 256) — precisely where float evaluation flips to the wrong integer. Also static-scan (AST or token) the FF1 module for float ops (`math.log`, `math.log2`, `math.ceil`, `math.pow`, `/`, float literals) and fail CI on violation.
|
|
107
|
+
5. **Property-based (Hypothesis):** round-trip; length and alphabet preservation; tweak sensitivity; key sensitivity (a one-bit key change produces unrelated output); determinism.
|
|
108
|
+
6. **Bijectivity:** for exhaustively enumerable domains (e.g. radix 2 length 20, radix 10 length 6), confirm the image is the full domain — no gaps, no repeats.
|
|
109
|
+
7. **Differential testing:** radix 10 and 36 have NIST vectors; **every other radix has none.** Compare against independent oracles (`ubiq-security-fpe`, Rust `fpe` crate), dev dependencies only, pinned. Never commit self-generated outputs as "vectors" — that tests nothing and locks in bugs permanently. Mark oracle tests optional-but-run-in-CI so a missing oracle does not block local development.
|
|
110
|
+
8. **Edge cases:** empty tweak vs absent tweak (must be equivalent); very long tweaks; minimum and maximum practical lengths; odd and even `n` (the `u != v` path); all-zero and all-max numerals; radix 2 and radix `2**16 - 1`.
|
|
111
|
+
9. **Interoperability:** a documented test matching `ubiq_security_fpe` output for the same inputs, so migrating users can verify ciphertext portability. A correctness obligation to downstream users, not a nicety.
|
|
112
|
+
|
|
113
|
+
Vector files live in `tests/vectors/` as JSON, never inline literals. Never regenerate NIST fixtures from this implementation.
|
|
114
|
+
|
|
115
|
+
## Never do
|
|
116
|
+
|
|
117
|
+
- Never add FF3 or FF3-1.
|
|
118
|
+
- Never add key generation, storage, derivation, or management helpers.
|
|
119
|
+
- Never claim FIPS validation; passing published vectors is conformance evidence only. The README states this explicitly.
|
|
120
|
+
- Never claim key zeroization; Python bytes are immutable and the GC copies them. Document as a known limitation.
|
|
121
|
+
- Never regenerate NIST fixtures from this implementation.
|
|
122
|
+
- Never add a runtime dependency beyond `cryptography` without explicit approval.
|
|
123
|
+
- Never add application-specific defaults, convenience wrappers, or alphabets shaped to one caller.
|
|
124
|
+
- Never let a test assert current behaviour where it should assert specified behaviour.
|
|
125
|
+
|
|
126
|
+
## Conventions
|
|
127
|
+
|
|
128
|
+
- Full type annotations; ship `py.typed`. Runtime dependency: `cryptography` only.
|
|
129
|
+
- Licence: MIT (decided).
|
|
130
|
+
- Semantic versioning; any change to accepted inputs or produced outputs is a major version.
|
|
131
|
+
- Migration from `ubiq_security_fpe` is **guide only** (decided 2026-08-21); no compatibility shim ships.
|
|
132
|
+
- CI matrix across all supported Python versions on Linux, macOS and Windows. Publish via PyPI Trusted Publishing; do not commit tokens.
|
|
133
|
+
- README must cover: what FF1 is, why FF3 is excluded, the Rev. 1 constraints applied, the FIPS disclaimer, and a migration section for `ubiq_security_fpe` users. Include `SECURITY.md` with a disclosure contact.
|
|
134
|
+
- Prefer clarity over cleverness. This module is read far more often than written, and a subtle bug is invisible without the vectors.
|
|
135
|
+
|
|
136
|
+
## Open decisions
|
|
137
|
+
|
|
138
|
+
Ask before deciding:
|
|
139
|
+
|
|
140
|
+
1. Optional accelerated backend (backlogged for post-1.0): whether to build it at all; if built, opt-in only — the pure-Python implementation stays the reference and the default.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html); per the project
|
|
7
|
+
contract, **any change to accepted inputs or produced outputs is a major version**.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
fpr_ff1-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joel Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|