diff --git a/tests/nist_acvp_vectors/SLH-DSA-sigVer-FIPS205/sha2_128s_extracted.json b/tests/nist_acvp_vectors/SLH-DSA-sigVer-FIPS205/sha2_128s_extracted.json index c782d3b..e16bf40 100644 --- a/tests/nist_acvp_vectors/SLH-DSA-sigVer-FIPS205/sha2_128s_extracted.json +++ b/tests/nist_acvp_vectors/SLH-DSA-sigVer-FIPS205/sha2_128s_extracted.json @@ -5,7 +5,7 @@ "source_url": "https://raw.githubusercontent.com/usnistgov/ACVP-Server/master/gen-val/json-files/SLH-DSA-sigVer-FIPS205/internalProjection.json", "source_sha256": "a013fc2104f4ed4799d96d51141f65b965969b2cf10646626a021b6d456ce792", "retrieved_utc": "2026-07-28", - "extraction": "Produced by tests/nist_acvp_vectors/extract_sha2_128s.py, which verifies the upstream sha256, selects every testGroup whose parameterSet == SLH-DSA-SHA2-128s, and removes exactly ONE per-test field: `sk` (the private key, not needed to verify a signature). Every other per-test field and all group and top-level metadata are carried through unchanged; the script asserts this and fails if it is not so.", + "extraction": "Produced by tests/nist_acvp_vectors/extract_sha2_128s.py, which verifies the upstream sha256, selects every testGroup whose parameterSet == SLH-DSA-SHA2-128s, and removes exactly ONE per-test field: `sk` (the private key, not needed to verify a signature). Every other per-test field and all group and top-level metadata are carried through unchanged by construction. The guarantees that can actually fail are: the pinned upstream SOURCE_SHA256; the requirement that `sk` be present to be dropped; the expected group and per-group test counts; and verify mode, which re-derives and byte-compares this committed file.", "note": "Test DATA only. Expected outcomes are NIST's `testPassed` field; `reason` records why a negative case must be rejected.", "regenerate": "python3 tests/nist_acvp_vectors/extract_sha2_128s.py --write" }, diff --git a/tests/nist_acvp_vectors/__pycache__/extract_sha2_128s.cpython-314.pyc b/tests/nist_acvp_vectors/__pycache__/extract_sha2_128s.cpython-314.pyc new file mode 100644 index 0000000..c6e35b6 Binary files /dev/null and b/tests/nist_acvp_vectors/__pycache__/extract_sha2_128s.cpython-314.pyc differ diff --git a/tests/nist_acvp_vectors/extract_sha2_128s.py b/tests/nist_acvp_vectors/extract_sha2_128s.py index 4084de1..42d7a18 100644 --- a/tests/nist_acvp_vectors/extract_sha2_128s.py +++ b/tests/nist_acvp_vectors/extract_sha2_128s.py @@ -15,10 +15,22 @@ removed. The transformation is now executable, pinned, and fails closed: * the upstream file's sha256 must match SOURCE_SHA256 exactly; * exactly EXPECTED_GROUPS groups must match the parameter set, each with EXPECTED_TESTS_PER_GROUP tests; - * exactly one field, `sk`, is removed, and it must be present to be removed; - * every other key present upstream is carried through untouched, and the - script asserts that afterwards; - * output is canonical (sorted keys off, fixed indent, trailing newline). + * exactly one field, `sk`, is removed, and it must be present to be removed + (so a schema change is caught, not silently transformed); + * every other key is carried through untouched — BY CONSTRUCTION, which a + reviewer verifies by reading `build()`, not by a self-check: no test inside a + transformer can detect a corrupted input, since the transformer is what + defines the output. The input is instead pinned by SOURCE_SHA256; + * verify mode re-derives and byte-compares the committed file, so a hand-edit + of the committed JSON IS caught; + * output is canonical (fixed indent, trailing newline). + +NOT A GATE — re-runnable EVIDENCE. Round-8 review noted the distinction: this +script needs network access, so nothing invokes it automatically (it is outside +`cargo test` and outside verification/check.sh). The committed JSON is still +trusted at review time; what this script provides is that a reviewer can +CHECK that trust cheaply and mechanically instead of taking a prose note's word. +A CI job running it in verify mode would close the remaining gap. Usage: python3 extract_sha2_128s.py # verify the committed file matches @@ -56,8 +68,11 @@ PROVENANCE = { f"parameterSet == {PARAMETER_SET}, and removes exactly ONE " "per-test field: `sk` (the private key, not needed to verify a " "signature). Every other per-test field and all group and " - "top-level metadata are carried through unchanged; the script " - "asserts this and fails if it is not so.", + "top-level metadata are carried through unchanged by construction. " + "The guarantees that can actually fail are: the pinned upstream " + "SOURCE_SHA256; the requirement that `sk` be present to be dropped; " + "the expected group and per-group test counts; and verify mode, which " + "re-derives and byte-compares this committed file.", "note": "Test DATA only. Expected outcomes are NIST's `testPassed` field; " "`reason` records why a negative case must be rejected.", "regenerate": "python3 tests/nist_acvp_vectors/extract_sha2_128s.py --write", @@ -90,9 +105,29 @@ def build(full: dict) -> dict: for f in DROP_FIELDS: if f not in t: sys.exit(f"FATAL: tcId {t.get('tcId')} has no field {f!r} to drop") + # Fields are carried through BY CONSTRUCTION: `kept` is `t` minus + # DROP_FIELDS, so every retained key holds the identical object. + # + # Round-8 review found a tautological `assert` here — it compared + # `kept` against the comprehension that had just built it, so it could + # never fire. The first attempt to repair it (comparing kept[k] to + # t[k]) was tautological for the same reason, and that is the lesson + # worth recording: NO check inside this function can detect a + # corrupted input, because this function is what defines the output + # from that input. Faithfulness here is a property of the two lines + # below, which a reviewer reads; it is not something the script can + # test about itself. + # + # What actually protects the result, and can fail: + # * SOURCE_SHA256 — the input is pinned, so upstream cannot drift + # or be substituted without an explicit, reviewed pin change; + # * the `f not in t` presence check above — `sk` must exist to be + # dropped, so a schema change is caught rather than silently + # producing a different transformation; + # * EXPECTED_GROUPS / EXPECTED_TESTS_PER_GROUP below; + # * verify mode, which re-derives and byte-compares the committed + # file, so a hand-edit of the committed JSON is caught. kept = {k: v for k, v in t.items() if k not in DROP_FIELDS} - # carried-through invariant, asserted rather than asserted-in-prose - assert set(kept) == set(t) - DROP_FIELDS, "unexpected per-test key change" tests.append(kept) if len(tests) != EXPECTED_TESTS_PER_GROUP: sys.exit(f"FATAL: group {g['tgId']} has {len(tests)} tests, "