Skip to content

CI: QR-UOV KAT digests, implementation coverage guard, and a ref/opt/avx2 matrix - #1

Merged
0rlych1kk4 merged 5 commits into
0rlych1kk4:feat/qruov-integrationfrom
paschal533:ci/qruov-kats-and-implementation-coverage
Oct 1, 2026
Merged

0rlych1kk4 merged 5 commits into
0rlych1kk4:feat/qruov-integrationfrom
paschal533:ci/qruov-kats-and-implementation-coverage

Conversation

@paschal533

@paschal533 paschal533 commented Sep 22, 2026 •

Copy link
Copy Markdown

The CI half of the split, as offered in #2582. Three commits: the 30 missing all KAT digests, a guard against shipping an implementation nothing can enable, and a matrix that builds ref, opt and avx2 and checks which one it actually got.

Based on 4134ad9. Nothing here changes QR-UOV sources or the importer.

The 30 missing all digests

Every QR-UOV entry in tests/KATs/sig/kats.json carried only single, while all 77 pre-existing signature entries carry both. tests/test_kat_all.py reads kats[name]['all'] with no guard, so it failed 30 times with KeyError rather than reporting a data problem.

The digests were computed with the method test_kat_all.py itself uses, a sha256 over kat_sig <alg> --all with CRLF normalised, cross-checked for one scheme against a direct reimplementation of that test. Each of the 30 outputs also reconstructs to the corresponding published Round 3 PQCsignKAT_32.rsp hash in qruov/round3@3d84d0f, so they are not only self-consistent with this tree.

Provenance worth stating plainly: they were computed on an avx2 build. single already agrees across ref, opt and avx2 for all 30, and --all differs only in running 100 vectors where single runs one, so the digests should not depend on the implementation. The matrix below checks that on ref and opt rather than leaving it assumed. If a digest turns out to be implementation-dependent, that job fails and we have learned something worth knowing.

Since first posting this I re-derived all 30 digests on a ref build (gcc 16.1.0, no AVX2, 50 minutes) rather than leave the claim argued. All 30 reproduce exactly, the set matches kats.json with nothing skipped, and a separate reviewer independently reproduced 3 of them on their own ref build. The digests are therefore confirmed on two of the three implementations, on two operating systems; the matrix below covers opt.

tests/test_kats_json.py then makes a missing digest fail as what it is. It reads the json alone, needs no build, runs on every platform, and names the algorithms at fault.

A guard for implementations that ship but cannot be enabled

src/sig/qruov/CMakeLists.txt tests OQS_ENABLE_SIG_qruov_<scheme>_opt in 30 places and .CMake/alg_support.cmake declares no such option, so the condition is false in every configuration. The 30 opt directories, and the shared qruov_qruov_opt, ship and are compiled nowhere.

Nothing notices, and that is the part worth fixing generically. The build succeeds, and the KATs pass on whichever implementation was selected, because all three produce identical vectors. The result looks exactly like full coverage.

tests/test_cmake_option_coverage.py compares the options a CMakeLists tests against the options alg_support.cmake declares. Across the whole tree that is 636 declared and 477 referenced, and today exactly 30 are referenced but undeclared, all QR-UOV opt, with no other family affected. src/oqsconfig.h.cmake is checked the same way and independently, since it carries a #cmakedefine for all 90 QR-UOV implementations while only 60 can ever be defined.

On the design, because your ARM64 finding changed it. I had offered a guard that simply fails when a shipped implementation cannot be enabled. Now that opt is deliberately withheld, that guard would have failed your build on the first run.

Rather than hand-list the 30 names, the check derives them from the same place the generator does. add_enable_by_alg_conditional.fragment emits a per-implementation option only for an implementation that is not the family default and that declares either a supported platform or memory_optimized; QR-UOV's opt declares supported-platforms: [], so no option can exist. Reading docs/algorithms/*/*.yml for that condition yields exactly the QR-UOV opt implementations and nothing else tree-wide, so the test carries only a reason per family, not a list of schemes, and needs no edit when a parameter set is added or renamed. An implementation that cannot be enabled and has no recorded reason fails, and a reason recorded for something that has become selectable also fails.

The recorded reason cites the ARM64 SIGBUS and notes that the wrapper selects with #if _opt / #elif _avx2, so whenever opt does become selectable that ordering has to change in the same commit, or enabling opt will silently drop AVX2.

If you would rather make opt selectable than record it, the fix belongs in scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg_conditional.fragment, which emits a per-implementation option only when the implementation declares a supported platform, and QR-UOV's opt declares supported-platforms: []. Editing the generated .cmake would be reverted by the upstreamcheck job. Either way the guard is satisfied, which is the intent: it asks for a decision, not a particular one.

The matrix

A qruov-implementations job in .github/workflows/extended.yml, next to nistkat-x64, the other job that runs test_kat_all: ref, opt and avx2 on x86-64 Linux, each building the 30 schemes and running the QR-UOV KATs in parallel.

Each job asserts which implementation the configure step actually selected before it builds. That is the point rather than a detail. All three produce identical KAT vectors, so a job that silently fell back to ref would pass while testing nothing, which is how opt came to ship for 30 parameter sets without being compiled anywhere.

Two corrections since the first version of this PR, both of which defeated that purpose. I had this reviewed before asking you to spend time on it, and it came back with two blockers worth repeating here because they are the same failure mode the PR is about.

The avx2 job did not test avx2. OQS_DIST_BUILD defaults ON, and the generated wrapper then keeps a run-time CPU check that falls back to ref when the host lacks AVX2, BMI2 or AES (sig_qruov_1q7L10aes.c:52-66). Asserting the compile-time define proved nothing about what ran, so on such a runner the job would have gone green while executing ref. All three jobs now set OQS_DIST_BUILD=OFF with an explicit OQS_OPT_TARGET, matching nistkat-x64, so the define does imply the code path; the assertion step refuses to continue if OQS_DIST_BUILD is set at all. If a runner lacks the ISA the job now dies loudly instead of quietly grading ref.

The assertion could also pass vacuously. Masking grep's exit status swallowed a real error as well as "no matches", leaving an empty string, and comparing an empty string to a number inside an if reads as false rather than tripping set -e. A missing header passed every guard, and the KAT step would then skip all 60 tests and exit 0. It now requires a non-empty header and tolerates only grep's "no match".

It sits in extended.yml rather than pr.yml on the same reasoning as open-quantum-safe#2509: three full builds plus 30 KAT sweeps on every PR cuts against moving heavy testing off the PR path. The two source-tree checks stay in basic.yml, which is the cheap half, and are added to the pytest ignore lists that already name test_code_conventions.py so they run once rather than in every build job.

The opt job is a configuration override, not a source change. It exists so the sources in the tarball are still built and KAT-checked on x86-64 while the ARM64 behaviour is investigated, and it may be useful to that investigation: if opt passes here and crashes there, the fault is ARM-specific rather than in the opt sources generally. It says nothing about ARM64 itself.

One limit worth stating plainly: the three implementations share qruov_qruov_common/, so agreement between them is evidence of reproducibility, not of correctness. A fault in the shared layer would be identical in all three and every job would stay green. What covers that is the comparison against the published Round 3 vectors described above, which is separate from this matrix.

What I could not test

The workflow itself is still unrun on Linux. I am on Windows x86-64, where the avx2 options are gated to Linux|Darwin, so I cannot exercise the Linux gating locally. What I did verify here: a clean ref build of all 30 schemes, 30 of 30 single-vector KATs passing on it, that OQS_MINIMAL_BUILD="SIG_qruov" selects exactly the 30 schemes, and that the flag extraction rejects hostile scheme names (I fed it $(...), backticks, ; and a -D injection attempt; the [A-Za-z0-9]+ class drops all of them, so nothing outside that set reaches the shell). The guard and the digest checks have tests that confirm they fail in each direction they claim to, including a #[[ ]] bracket comment that previously read as live code.

That gap is exactly why every job asserts its own selection rather than trusting my flags: if the flag logic is wrong on a Linux runner, the job fails with the counts instead of quietly testing ref three times. The first run on your branch is the real test, and I would rather it fail loudly than pass vacuously.

Unrelated and not addressed here: tests/test_code_conventions.py::test_memory_functions cannot run on Windows, as it reads source files with the locale codec and hits UnicodeDecodeError.

Happy to move the matrix into linux.yml or platforms.yml instead, drop the opt job, or turn the recorded reason into a hard failure once opt is selectable. Say which you prefer.

@0rlych1kk4
0rlych1kk4 force-pushed the feat/qruov-integration branch from 4134ad9 to 96a3962 Compare September 25, 2026 14:02
@paschal533
paschal533 force-pushed the ci/qruov-kats-and-implementation-coverage branch from 2ed6ccc to a349143 Compare September 25, 2026 14:28
@0rlych1kk4
0rlych1kk4 force-pushed the feat/qruov-integration branch 2 times, most recently from a4ad814 to 04686f2 Compare September 25, 2026 18:14
@0rlych1kk4

Copy link
Copy Markdown
Owner

The conflicts are from the QR-UOV base branch having been rebased since this PR was opened.
I reapplied your four CI commits locally on top of the current feat/qruov-integration head (04686f2) and they apply cleanly with authorship preserved.
Local validation on that rebased stack passes:

  • tests/test_kats_json.py: 8 passed
  • tests/test_cmake_option_coverage.py: 10 passed
  • tests/test_kat_all.py -k qruov: 30 passed

Could you please rebase these four commits onto the current feat/qruov-integration and force-push the PR branch? I’ll recheck the CI after that.

@0rlych1kk4
0rlych1kk4 force-pushed the feat/qruov-integration branch from 04686f2 to e5a1ba9 Compare September 29, 2026 00:01
@paschal533
paschal533 force-pushed the ci/qruov-kats-and-implementation-coverage branch from a349143 to 9a7bd56 Compare September 29, 2026 11:58
@paschal533

Copy link
Copy Markdown
Author

Rebased onto the current feat/qruov-integration head. That's e5a1ba9 now, since the branch moved again after open-quantum-safe#2595 landed. The first three commits carried over unchanged. The fourth conflicted only on the pytest lines open-quantum-safe#2595 rewrote, so each one keeps the new --numprocesses=logical --dist load --maxschedchunk=1 flags with our two --ignores added. test_kats_json.py (8) and test_cmake_option_coverage.py (10) pass locally.

@0rlych1kk4

Copy link
Copy Markdown
Owner

Rebased onto the current feat/qruov-integration head. That's e5a1ba9 now, since the branch moved again after open-quantum-safe#2595 landed. The first three commits carried over unchanged. The fourth conflicted only on the pytest lines open-quantum-safe#2595 rewrote, so each one keeps the new --numprocesses=logical --dist load --maxschedchunk=1 flags with our two --ignores added. test_kats_json.py (8) and test_cmake_option_coverage.py (10) pass locally.

Thanks for rebasing this onto the current head and preserving the open-quantum-safe#2595 scheduling changes. I’ll let the CI run and recheck the result before merging.

@paschal533

Copy link
Copy Markdown
Author

Two notes on the checks, so nothing here looks stuck for the wrong reason.

The four zephyr_test jobs will stay queued. zephyr.yml runs on runs-on: oqs-x64, a runner that only exists in the open-quantum-safe org, so on a personal fork nothing picks them up. They run fine on open-quantum-safe#2582. Everything else here is green: 68 passed, 1 skipped.

The QR-UOV ref/opt/avx2 matrix won't appear in PR checks at all. It lives in extended.yml, which only runs from push.yml when the head commit message contains [extended tests], plus weekly, on release, or by manual dispatch. So it hasn't run on GitHub yet. I ran the same configurations locally on x86-64 Linux (Ubuntu 24.04, gcc 13), all 30 parameter sets, with OQS_STRICT_WARNINGS=ON:

  • forced ref, avx2 and opt: 150/150 each (test_sig, test_kat, test_kat_all, test_mem, test_alg_info), with the selection assertion passing in each (30/0/0, 30/0/30, 30/30/0)
  • dist build: 120/120
  • ASan on ref, avx2 and opt, and UBSan on ref and avx2: all clean, no sanitizer reports
  • 0 compiler warnings

I can dispatch extended.yml on my fork to get a real run to link if that helps.

@0rlych1kk4

Copy link
Copy Markdown
Owner

Two notes on the checks, so nothing here looks stuck for the wrong reason.

The four zephyr_test jobs will stay queued. zephyr.yml runs on runs-on: oqs-x64, a runner that only exists in the open-quantum-safe org, so on a personal fork nothing picks them up. They run fine on open-quantum-safe#2582. Everything else here is green: 68 passed, 1 skipped.

The QR-UOV ref/opt/avx2 matrix won't appear in PR checks at all. It lives in extended.yml, which only runs from push.yml when the head commit message contains [extended tests], plus weekly, on release, or by manual dispatch. So it hasn't run on GitHub yet. I ran the same configurations locally on x86-64 Linux (Ubuntu 24.04, gcc 13), all 30 parameter sets, with OQS_STRICT_WARNINGS=ON:

  • forced ref, avx2 and opt: 150/150 each (test_sig, test_kat, test_kat_all, test_mem, test_alg_info), with the selection assertion passing in each (30/0/0, 30/0/30, 30/30/0)
  • dist build: 120/120
  • ASan on ref, avx2 and opt, and UBSan on ref and avx2: all clean, no sanitizer reports
  • 0 compiler warnings

I can dispatch extended.yml on my fork to get a real run to link if that helps.

Thanks, that clarifies the queued Zephyr jobs and the extended-test behavior. Yes, please dispatch extended.yml on your fork so we have a GitHub Actions run for the ref/opt/avx2 matrix before merging.
I’ll review that result together with the current diff afterward.

@paschal533

Copy link
Copy Markdown
Author

Started: https://github.com/paschal533/liboqs/actions/runs/36589276155

extended.yml isn't registered as a dispatchable workflow on my fork, so I triggered it the other way push.yml allows: a side branch, ci/qruov-extended-run, holding this PR's tree at 9a7bd5655 plus one empty commit whose message carries [extended tests]. Same tree as the PR branch, which I haven't touched.

So far QR-UOV avx2 has passed. ref and opt are still queued behind the rest of the extended suite. ref took about 47 minutes locally, so it will be a while. I'll confirm here once all three finish.

@paschal533

Copy link
Copy Markdown
Author

All three QR-UOV jobs passed on GitHub Actions: https://github.com/paschal533/liboqs/actions/runs/36589276155

job selection check pytest (test_kat + test_kat_all, -k QRUOV)
QR-UOV ref schemes=30 opt=0 avx2=0 60 passed (17m29s)
QR-UOV avx2 schemes=30 opt=0 avx2=30 60 passed (1m01s)
QR-UOV opt schemes=30 opt=30 avx2=0 60 passed (3m51s)

The run is on ci/qruov-extended-run, which is this PR's tree at 9a7bd5655 plus one empty commit carrying [extended tests].

@paschal533

Copy link
Copy Markdown
Author

Correction to my last comment. The three QR-UOV jobs in run 36589276155 passed, but the run didn't, and I should have said so. Both constant-time-x64 jobs (generic and extensions) failed with 30 failures each, all QR-UOV. The PR carried no valgrind suppressions for it, so every secret-dependent branch valgrind found counted as a failure.

Fixed on this branch in 82150a1. It adds tests/constant_time/sig/passes/qruov and tests/constant_time/sig/issues/qruov and registers both in passes.json / issues.json. The new run is green: https://github.com/paschal533/liboqs/actions/runs/36652092113 (50 passed, 0 failed, and the usual 3 skipped: code-coverage, full-tests, downstream-release).

job result
QR-UOV ref / opt / avx2 passed
constant-time-x64 (generic) 175 passed, 132 skipped; all 30 QR-UOV sets pass
constant-time-x64 (extensions) 175 passed, 132 skipped; all 30 QR-UOV sets pass

Same arrangement as before: ci/qruov-extended-run is this branch's tree at 82150a1 plus one empty commit carrying [extended tests].

How the suppressions are split. Line numbers are in the ref sources.

passes holds branches on data that is thrown away or published:

  • fastop.h:15/17/20: rejection sampling of PRG output branches on whether a byte equals q. Rejected bytes are discarded, so this shows which positions were resampled, not the values kept.
  • linsys.c:140: the consistency check before a signing retry. An inconsistent system is discarded and signing retries with fresh randomness.

issues holds genuinely secret-dependent code, recorded so it stays tracked:

  • linsys.c:38: the pivot search in the LU decomposition branches on the zero pattern of a secret-derived matrix, which leaks pivot positions and rank.
  • gf.h:53: field inversion by table lookup indexed by a secret-derived pivot. That's a cache-timing channel, though the table is only q bytes.
  • qruov_common.c:22/31/33: canonicalisation of s. s is published, but whether it was negated reveals one bit about the value before canonicalisation. I believe that's harmless, and it sits in issues until someone else agrees.

None of this contradicts the docs: qruov.yml already says no-secret-dependent-branching-claimed: false. One question: it also says no-secret-dependent-branching-checked-by-valgrind: false, and with these files in place the extended tests now do run valgrind over QR-UOV. Should that flag change, or would you rather leave it until the issues entries are dealt with upstream?

This PR targets feat/qruov-integration, so merging it gives open-quantum-safe#2582 the suppressions as well. Without them its first extended run would fail the same way.

@0rlych1kk4

Copy link
Copy Markdown
Owner

Correction to my last comment. The three QR-UOV jobs in run 36589276155 passed, but the run didn't, and I should have said so. Both constant-time-x64 jobs (generic and extensions) failed with 30 failures each, all QR-UOV. The PR carried no valgrind suppressions for it, so every secret-dependent branch valgrind found counted as a failure.

Fixed on this branch in 82150a1. It adds tests/constant_time/sig/passes/qruov and tests/constant_time/sig/issues/qruov and registers both in passes.json / issues.json. The new run is green: https://github.com/paschal533/liboqs/actions/runs/36652092113 (50 passed, 0 failed, and the usual 3 skipped: code-coverage, full-tests, downstream-release).

job result
QR-UOV ref / opt / avx2 passed
constant-time-x64 (generic) 175 passed, 132 skipped; all 30 QR-UOV sets pass
constant-time-x64 (extensions) 175 passed, 132 skipped; all 30 QR-UOV sets pass
Same arrangement as before: ci/qruov-extended-run is this branch's tree at 82150a1 plus one empty commit carrying [extended tests].

How the suppressions are split. Line numbers are in the ref sources.

passes holds branches on data that is thrown away or published:

  • fastop.h:15/17/20: rejection sampling of PRG output branches on whether a byte equals q. Rejected bytes are discarded, so this shows which positions were resampled, not the values kept.
  • linsys.c:140: the consistency check before a signing retry. An inconsistent system is discarded and signing retries with fresh randomness.

issues holds genuinely secret-dependent code, recorded so it stays tracked:

  • linsys.c:38: the pivot search in the LU decomposition branches on the zero pattern of a secret-derived matrix, which leaks pivot positions and rank.
  • gf.h:53: field inversion by table lookup indexed by a secret-derived pivot. That's a cache-timing channel, though the table is only q bytes.
  • qruov_common.c:22/31/33: canonicalisation of s. s is published, but whether it was negated reveals one bit about the value before canonicalisation. I believe that's harmless, and it sits in issues until someone else agrees.

None of this contradicts the docs: qruov.yml already says no-secret-dependent-branching-claimed: false. One question: it also says no-secret-dependent-branching-checked-by-valgrind: false, and with these files in place the extended tests now do run valgrind over QR-UOV. Should that flag change, or would you rather leave it until the issues entries are dealt with upstream?

This PR targets feat/qruov-integration, so merging it gives open-quantum-safe#2582 the suppressions as well. Without them its first extended run would fail the same way.

Thanks for the correction and for rerunning the full extended suite.
The split between passes and issues looks consistent with the liboqs constant-time test model, and the green rerun addresses the extended-test failure.
For no-secret-dependent-branching-checked-by-valgrind, I’d prefer to leave it false for now unless the liboqs maintainers confirm that the flag is intended to mean “Valgrind was run with known issues documented,” rather than “the implementation passes the no-secret-dependent-branching check.”
Since QR-UOV still has entries under issues, keeping the metadata conservative seems safer until that interpretation is confirmed.

@0rlych1kk4
0rlych1kk4 force-pushed the feat/qruov-integration branch from e5a1ba9 to 35aefac Compare October 1, 2026 09:27
@0rlych1kk4

Copy link
Copy Markdown
Owner

I rebased feat/qruov-integration onto current main, so the base moved from e5a1ba9 to 35aefac. GitHub is now reporting a conflict in tests/KATs/sig/kats.json.
Could you please rebase your branch onto 35aefac and keep both the latest upstream kats.json changes and the 30 QR-UOV KAT entries?

Every QR-UOV entry in tests/KATs/sig/kats.json carried only `single`,
while all 77 pre-existing signature entries carry both `single` and
`all`. tests/test_kat_all.py reads `kats[name]['all']` with no guard, so
it failed 30 times with KeyError rather than reporting a data problem.

The digests were computed with the method test_kat_all.py itself uses, a
sha256 over `kat_sig <alg> --all` with CRLF normalised, cross-checked for
one scheme against a direct reimplementation of that test. Each of the
30 outputs also reconstructs to the corresponding published Round 3
PQCsignKAT_32.rsp hash in qruov/round3@3d84d0f, so they are not merely
self-consistent with this tree.

They were computed on an avx2 build. `single` already agrees across ref,
opt and avx2 for all 30, and `--all` differs only in running 100 vectors
where `single` runs one, so the digests should not depend on the
implementation; the matrix added alongside this checks that on ref and
opt rather than assuming it.

test_kats_json.py then makes a missing digest fail as what it is. It
reads the json alone, so it needs no build and runs on every platform,
and it names the algorithms at fault instead of surfacing as a KeyError
inside an unrelated test.
src/sig/qruov/CMakeLists.txt tests OQS_ENABLE_SIG_qruov_<scheme>_opt in
30 places and .CMake/alg_support.cmake declares no such option, so the
condition is false in every configuration. The 30 opt directories, and
the shared qruov_qruov_opt, ship in the tarball and are compiled
nowhere. Nothing notices: the build succeeds and the KATs pass on
whichever implementation was selected, because all three produce
identical vectors.

The check compares the options a CMakeLists tests against the options
alg_support.cmake declares. Across the whole tree that is 636 declared
and 477 referenced, and today exactly 30 are referenced but undeclared,
all QR-UOV opt, with no other family affected. It reads CMake files
only, so it needs no configure and no build.

src/oqsconfig.h.cmake is checked the same way and independently, since
it carries a #cmakedefine for all 90 QR-UOV implementations while only
60 can ever be defined. Three generated surfaces disagree because the
templates filter implementations differently, so a defect that is
invisible in one is visible in another.

Withholding an implementation is legitimate, so KNOWN_UNSELECTABLE
records it with a reason, and the comparison is exact in both
directions: an unselectable implementation that is not recorded fails,
and a recorded one that has become selectable also fails. A one-way
allowlist would go stale without anyone noticing, which is the same
failure mode being fixed here.

QR-UOV opt is recorded rather than declared because making it selectable
produced reproducible SIGBUS crashes in 8 of the 30 variants on ARM64.
The entry also notes that the wrapper selects with `#if _opt / #elif
_avx2`, so whenever opt does become selectable that ordering has to
change in the same commit or enabling opt will silently drop AVX2.

Note that a fix for QR-UOV belongs in
scripts/copy_from_upstream/.CMake/alg_support.cmake/add_enable_by_alg_conditional.fragment,
which emits a per-implementation option only when the implementation
declares a supported platform, and QR-UOV opt declares none. Editing the
generated file would be reverted by the upstreamcheck job.
A matrix over the three implementations on x86-64 Linux, each building
the 30 schemes and running the QR-UOV KATs.

Each job checks which implementation the configure step actually
selected before it builds anything. That is the point rather than a
detail: all three implementations produce identical KAT vectors, so a
job that silently fell back to ref would pass while testing nothing, and
that is how opt came to ship for 30 parameter sets without being
compiled anywhere. A job that cannot select its target fails and says
what it got instead.

ref turns the avx2 options off, avx2 requires all 30 to be on, and opt
forces the cache variables directly, since no option declares them. The
opt job is therefore a configuration override, not a source change, and
it exists so the sources in the tarball are still built and KAT-checked
on x86-64 while the ARM64 behaviour is investigated. It says nothing
about ARM64.

The two source-tree checks are also wired into the stylecheck job in
basic.yml, which needs no build, so a missing digest or an unreachable
implementation fails in about a second rather than after a full matrix.
Two blockers found in review, both of which defeated the stated purpose.

The avx2 job did not test avx2. OQS_DIST_BUILD defaults ON, and the
generated wrapper then keeps a run-time CPU check that falls back to ref
when the host lacks AVX2, BMI2 or AES. The job asserted a compile-time
define, so on such a runner it would have gone green while executing
ref: exactly the failure it was written to prevent. All three jobs now
set OQS_DIST_BUILD=OFF with an explicit OQS_OPT_TARGET, matching what
nistkat-x64 already does, so the define implies the code path, and the
assertion step refuses to continue if OQS_DIST_BUILD is set at all.

The assertion step could pass vacuously. Masking grep's exit status
swallowed a real error as well as "no matches", leaving an empty string,
and comparing an empty string to a number inside an `if` reads as false
rather than tripping set -e. A missing header therefore passed every
guard, and the KAT step would then skip all 60 tests and exit 0. It now
requires a non-empty header and tolerates only grep's "no match" status.

The job moves from a new workflow called by pr.yml into extended.yml,
next to nistkat-x64, the other job that runs test_kat_all. Three full
builds plus 30 KAT sweeps on every PR ran against the project's stated
direction of moving heavy testing out of the PR path. The two
source-tree checks stay in basic.yml, which is the cheap half.

The KAT step is now run in parallel with a 360 minute timeout, which is
what the comparable job uses; 60 minutes serial was optimistic.

The allowlist of 30 literal scheme names is gone. Whether an
implementation can have an option is decided by the generator from
docs/algorithms metadata, so that is where the set is now derived from,
leaving only a reason per family and implementation. It no longer needs
hand-editing when a parameter set is added or renamed.

Comment stripping now removes bracket forms first. A declaration inside
a bracket comment was read as live, which would have let the coverage
check be silenced, and a hash inside a bracket argument truncated a line
that was live, which would have hidden a real finding. Both directions
now have regression tests, so the docstring's claim is enforced rather
than asserted.

kats.json is also checked for whole missing entries against the public
header, using the same exclusions test_kat_all.py skips on. Iterating
the file only ever validated what was in it, so deleting an algorithm
outright passed every check and then returned as the KeyError this
module exists to pre-empt.

Both new modules are added to the pytest ignore lists that already name
test_code_conventions.py, so the source-tree checks run once in
stylecheck rather than in every build job.
The constant-time job in extended.yml fails on all 30 QR-UOV parameter
sets, because nothing records the operations valgrind flags in the ref
implementation. PR CI does not run extended.yml, so this first shows up in
a manual or weekly extended run.

passes/qruov holds the branches that only touch discarded data: rejection
sampling of PRG output (fastop.h) and the retry on an inconsistent linear
system (linsys.c:140). issues/qruov holds the rest: the LU pivot search
(linsys.c:38), the secret-indexed gf_inv table lookup (gf.h:53), and the
canonicalisation of s (qruov_common.c:22, 31, 33). The last is believed
harmless, since it branches on s, which is published, but it reveals
whether s was negated, so it stays in issues until that is confirmed.
@paschal533
paschal533 force-pushed the ci/qruov-kats-and-implementation-coverage branch from 82150a1 to 4fd2870 Compare October 1, 2026 10:14
@paschal533

Copy link
Copy Markdown
Author

Rebased onto 35aefac. thank you!

@0rlych1kk4
0rlych1kk4 merged commit 4b9556c into 0rlych1kk4:feat/qruov-integration Oct 1, 2026
69 of 73 checks passed
0rlych1kk4 added a commit that referenced this pull request Oct 1, 2026
…n-coverage

CI: QR-UOV KAT digests, implementation coverage guard, and a ref/opt/avx2 matrix
Signed-off-by: 0rlych1kk4 <108985066+0rlych1kk4@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants