ACVP Lab Workflow: From Vector Set to Evidence
How algorithm validation testing runs end to end — and how to state a result at exactly the evidence level it reached. Draft awaiting validation-lab practitioner review.
Why this matters: A passing test is only evidence for the exact operation, parameters, build and source it was run against. Laboratories, vendors and buyers who blur a public reference sample, an ACVTS-issued vector set and a validation certificate make claims no evidence supports.
Start here: Classify ten real-world test-log observations into the evidence classes, then see the highest claim each one supports and the overclaim it tempts you into.
Draft — awaiting review by a validation-lab practitioner
This module is a draft. It has not yet been reviewed by an accredited testing-laboratory practitioner and is not a lab training resource. Every factual statement cites its primary source; where the Hub Library does not carry a source yet, the citation links the source directly.
PQC Today executes selected public reference vectors, standards tests, conformance cases, and implementation probes. A passing result is evidence only for the identified test, operation, parameters, implementation build, and target. It is not an ACVTS verdict, a CAVP/CMVP certificate, or proof of exhaustive conformance.
This is the algorithm-testing companion to PQC Network Testing & Validation, whose workshop walks the ACVP workflow at overview level. Here the same workflow is taken apart the way a testing laboratory meets it: what a registration commits you to, what each test type asks the implementation to do, where a PKCS#11 interface cannot follow, and how to state a result without claiming more than the evidence shows. The programme landscape itself (who runs CAVP, CMVP and the testing labs) is covered in Standards, Certification & Compliance Bodies.
Algorithm Validation vs Module Validation
Two NIST programmes are routinely blurred together. The Cryptographic Algorithm Validation Program (CAVP) validates implementations of approved algorithms and their components; NIST calls algorithm validation “a prerequisite of cryptographic module validation” [CAVP]. The Cryptographic Module Validation Program (CMVP), run jointly by NIST and the Canadian Centre for Cyber Security, validates whole cryptographic modules to FIPS 140-3, using independent NVLAP-accredited Cryptographic and Security Testing (CST) laboratories [FIPS 140-3].
| Question | Algorithm validation (CAVP) | Module validation (CMVP) |
|---|---|---|
| What is tested? | An algorithm implementation, by comparing its outputs with known or expected answers [CMVP Management Manual §1.4] | A defined module against FIPS 140-3’s eleven requirement areas — module specification, interfaces, roles/services/authentication, software/firmware, operating environment, physical, non-invasive, SSP management, self-tests, life-cycle assurance, mitigation of other attacks — at one of four security levels [FIPS 140-3] |
| Which algorithms? | Approved algorithms; NIST points to SP 800-140C and SP 800-140D for the lists [CAVP] [SP 800-140Cr2] | The approved algorithms the module claims; the lab confirms each complies with all requirements of its standard, including those CAVP tests do not reach [CMVP Management Manual §2.6.2] |
| How is it run? | Black-box, through the Automated Cryptographic Validation Testing System (ACVTS): the implementation is never sent to NIST [CAVP] | A CST laboratory tests the module, reviews documentation and source, and submits a report to the CMVP validation authorities [CMVP Management Manual §2.6.2] |
| What is published? | An algorithm validation certificate on the CAVP validation list [CAVP] | A module certificate; its entry links to the CAVP certificates of the algorithms the module uses [CMVP Management Manual §4.9.3.5] |
Algorithm testing does not cover every algorithm requirement
The CMVP Management Manual requires the laboratory to confirm compliance with the algorithm standards’ “shall” statements that CAVP testing does not address [CMVP Management Manual §2.6.2]. The ACVP sub-specifications say the same from the other side: the ML-KEM and ML-DSA test types do not test zeroization of intermediate values, DRBG strength, or incorrect-length keys and signatures [ACVP ML-KEM sub-spec] [ACVP ML-DSA sub-spec].
Where this Hub stands: it runs neither programme. It executes selected public reference vectors, standards tests, conformance cases and probes, and its results are never an ACVTS verdict or a CAVP/CMVP certificate (see the notice at the top of this page).
The module side (security levels, the Modules In Process queue, and the CMVP routes for adding ML-KEM or ML-DSA to an already-validated module) is taught in FIPS 140-3 Certification.
Capability Registration & Vector-Set Negotiation
ACVP testing starts with the client, not the server. A registration is the client’s description of the capabilities of one or more algorithm / mode / revision combinations [ACVP spec §3.2.2]. ACVTS generates test cases that match those capabilities and hands the inputs to the implementation [CAVP]. Each algorithm in the registration becomes one test vector set (identified by vsId), made of test groups (tgId) that share properties, made of test cases (tcId) [ACVP spec §5.4].
The registration below is the ML-DSA sigGen example from the ACVP ML-DSA sub-specification, with its hash list shortened. Every array is a promise: the server will generate test groups for combinations of these values, and the implementation has to answer all of them [ACVP ML-DSA sigGen registration] [ACVP ML-DSA sub-spec].
{
"algorithm": "ML-DSA",
"mode": "sigGen",
"revision": "FIPS204-tr1",
"prereqVals": [{ "algorithm": "SHA", "valValue": "123456" }],
"capabilities": [{
"parameterSets": ["ML-DSA-44", "ML-DSA-65", "ML-DSA-87"],
"messageLength": [{ "min": 8, "max": 65536, "increment": 8 }],
"hashAlgs": ["SHA2-256", "SHA2-512", "SHAKE-256"],
"contextLength": [{ "min": 0, "max": 2040, "increment": 8 }]
}],
"deterministic": [true, false],
"externalMu": [true, false],
"signatureInterfaces": ["external", "internal"],
"preHash": ["pure", "preHash"],
"keyFormats": ["expanded", "seed"]
}deterministic: [true, false]commits the implementation to both signing variants, including hedged groups that carry the server’s randomness [ACVP ML-DSA sub-spec].signatureInterfaces: ["internal"]commits it to answering ML-DSA.Sign_internal on a formatted message, andexternalMu: [true]to accepting a message representative μ computed outside the module [ACVP ML-DSA sigGen registration] [FIPS 204 Alg. 7].prereqValsnames prerequisite algorithm validations (here SHA) [ACVP ML-DSA sigGen registration].- The
revisionis part of the identity: the sub-specification lists ML-DSA / sigGen as bothFIPS204andFIPS204-tr1, “a new test revision against the same standard” [ACVP ML-DSA supported revisions].
Register what you can answer, not what you implement
A module may implement hedged signing and still be unable to answer a hedged sigGen test, because the test must inject the randomness. The registration therefore has to match what the test harness can drive, not just what the product does. The Hub’s own ACVP-format prototype makes the same decision in reverse: it accepts only the algorithm / mode / revision triples it pins, and rejects any other with a diagnostic rather than guessing a closest revision.
Prompt → Response → Disposition → Validation
The ACVP specification’s minimal message flow has six exchanges [ACVP spec §12.4]:
- 1
Register capabilities
POST /testSessionsThe registration lists each algorithm / mode / revision and the options the implementation supports. The server answers with a test session and one vector-set URL per algorithm.
- 2
Download each prompt
GET /testSessions/{id}/vectorSets/{vsId}The prompt (the spec also calls it the request) holds test groups (tgId) of test cases (tcId). Exactly one prompt exists per vector set.
- 3
Submit the response
POST …/vectorSets/{vsId}/resultsThe client returns the implementation’s output for every test case, linked to the prompt by vsId.
- 4
Read the disposition
GET …/vectorSets/{vsId}/resultsThe server reports passed, fail, incomplete, expired, missing, unreceived or error for the vector set and for each test case.
- 5
Certify the session
PUT /testSessions/{id}Allowed only when the session is both publishable and passed. The request associates the module and its operational environment (OE) and lists any prerequisite validations.
- 6
Retrieve the validation
GET /requests/{requestId}The certification is a request; once approved it yields the identifier of the validation record.
Sources for the steps: [ACVP spec §5.4] [ACVP spec §12.16.4] [ACVP spec §12.17.4].
Details that decide what a result means
A passed disposition means every test case in the vector set was processed and passed, and indicates the algorithm is ready for validation — it is not itself the validation [ACVP spec §5.4, §12.17.4].
A session created with isSample: true lets the client retrieve the expected results, and its vector sets may be smaller [ACVP spec §12.16, §12.17.7]. For a validation authority, vector sets are one-time use: an old vector set can never be reused to obtain a new certificate [ACVP spec §14].
NIST runs two ACVTS environments. Demo is a semi-volatile sandbox for testing implementations and ACVP clients. Prod is restricted to accredited CST laboratories and 17ACVT laboratories; a lab may run an algorithm in Prod only after running it at least once through certification in Demo [Accessing the ACVTS]. Only Prod creates the algorithm validation certificates on the CAVP list [CAVP].
Map this onto the Hub’s claim ladder and the boundary is exact: generating a response to an issued prompt is rung 7; the disposition is rung 8; a published validation record is rung 9. The Hub’s ACVP-format prototype stops at local import and response export — it has no ACVTS session, credentials, submission or verdict retrieval.
AFT, VAL, MCT and Algorithm-Specific Test Types
A test type describes the format of a test rather than its intention; several tests of the same type can cover different assurances [ACVP ML-DSA sub-spec]. The vocabulary is defined per algorithm, in each ACVP sub-specification:
| Algorithm / mode | Test type | What the implementation must do |
|---|---|---|
| SHA-1 / SHA-2 | AFT | Hash single, multiple and partial blocks of random data — checks chunking and padding [ACVP SHA sub-spec] |
| SHA-1 / SHA-2 | MCT | Monte Carlo Test: from one seed, run 100 × 1,000 chained hashes where each digest feeds the next message; aimed at state, allocation and error-handling faults [ACVP SHA sub-spec] |
| SHA-1 / SHA-2 | LDT | Large Data Test: hash a multi-gigabyte message described, not transmitted, by the prompt [ACVP SHA sub-spec] |
| ML-KEM / keyGen | AFT | Generate a key pair from a provided seed (ML-KEM.KeyGen_internal) [ACVP ML-KEM sub-spec] |
| ML-KEM / encapDecap | AFT | Encapsulate with a server-chosen m (ML-KEM.Encaps_internal) and return c and k [ACVP ML-KEM sub-spec] |
| ML-KEM / encapDecap | VAL | Decapsulate valid or modified ciphertexts (ML-KEM.Decaps_internal), or decide whether an encapsulation key (FIPS 203 §7.2) or decapsulation key (§7.3) is valid [ACVP ML-KEM sub-spec] |
| ML-DSA / keyGen, sigGen, sigVer | AFT | Key pair from a seed; signatures compared with the server’s known signature; validity decisions on modified signatures [ACVP ML-DSA sub-spec] |
| SLH-DSA / keyGen, sigGen, sigVer | AFT | Key pair from SK.seed, SK.prf and PK.seed; signatures from provided keys and randomness; validity decisions on modified signatures [ACVP SLH-DSA sub-spec] |
Minimums are part of the test design
The sub-specifications fix minimum test counts per combination of capabilities — for example at least 15 ML-DSA sigGen tests and at least 3 ML-DSA sigVer tests per modification type [ACVP ML-DSA sub-spec], and at least 13 SLH-DSA sigGen tests [ACVP SLH-DSA sub-spec]. ML-DSA sigGen also requires tests that drive every rejection path of ML-DSA.Sign_internal, and tests that force at least 32 rejections, on the internal, deterministic interface [ACVP ML-DSA sub-spec].
A single passing case per parameter set is therefore a sample of an ACVP test type, never its coverage.
Deterministic vs Randomized Algorithms
FIPS 203, 204 and 205 each split their algorithms into an external interface, which generates its own randomness, and an internal interface that takes the randomness as an input. The internal interfaces are the ones CAVP tests, and each standard says they should not be offered to applications other than for testing, because the random values shall be generated by the module [FIPS 203 §6] [FIPS 204 §6] [FIPS 205 §9].
| Operation | Randomness | Consequence for testing |
|---|---|---|
| ML-KEM.Encaps | 32-byte m drawn inside the module; Encaps_internal(ek, m) takes it as input [FIPS 203 Alg. 17, 20] | Byte-exact testing needs the internal interface with the server’s m. |
| ML-DSA.Sign | Default hedged: rnd from an RBG. Optional deterministic: rnd = 32 zero bytes; that is the only difference [FIPS 204 §3.4, Alg. 2, Alg. 7] | Deterministic signatures can be compared byte for byte; hedged ones only if the test can inject rnd. |
| SLH-DSA sign | Default hedged: opt_rand = addrnd. Deterministic: opt_rand = PK.seed [FIPS 205 §9.2, Alg. 19] | Same split as ML-DSA. |
FIPS 204 treats the variants differently for security, not only for testing: hedged signing helps mitigate side-channel attacks, the deterministic variant “should not be used on platforms where side-channel attacks are a concern and where they cannot be otherwise mitigated”, and implementing the hedged variant alone is sufficient for interoperability [FIPS 204 §3.4].
Comparator policy follows from the algorithm
Two correct implementations of a hedged signature scheme will not produce the same bytes. Across engines or platforms, randomized outputs are compared semantically (does it verify?) and deterministic outputs byte for byte. Choosing the comparator per operation is part of the test design, not an afterthought.
PKCS#11 Adapter Boundaries
ACVP talks to an implementation through whatever interface the test harness can reach [ACVP spec §4.2, §5]. For the Hub that interface is PKCS#11 v3.2, on two WASM engines (C++ and Rust). An ACVP test group can only be answered if PKCS#11 can carry exactly the inputs the group supplies — nothing coerced, nothing defaulted.
| ACVP test group | PKCS#11 v3.2 path | Hub disposition |
|---|---|---|
| ML-KEM decapsulation (VAL) | Import dk, then C_DecapsulateKey with CKM_ML_KEM (ML-KEM.Decaps) [PKCS#11 v3.2 §6.68] | Executed; k is the derived key’s CKA_VALUE |
| ML-KEM encapsulation (AFT) | None: C_EncapsulateKey takes a public-key handle and a template, with no input for m [PKCS#11 v3.2 §5.18.8] | Unsupported — ML-KEM encapsulation AFT needs the server-chosen m (FIPS 203 ML-KEM.Encaps_internal); PKCS#11 v3.2 C_EncapsulateKey draws m from the token RNG and has no input for it, and this prototype uses no randomness-injection seam. |
| ML-KEM key checks (VAL) | None: FIPS 203 §7.2/§7.3 checks are not a PKCS#11 operation [FIPS 203 §7.2, §7.3] | Unsupported (reading an object-creation return code as a verdict would be an inference) |
| ML-DSA sigVer, external, pure / preHash | CKM_ML_DSA (ML-DSA.Verify) or CKM_HASH_ML_DSA_<hash>, context in CK_SIGN_ADDITIONAL_CONTEXT [PKCS#11 v3.2 §6.67.5–6.67.7] | Executed; hashAlgs SHA2-512/224 and SHA2-512/256 have no CKM_HASH_ML_DSA_* mechanism and are unsupported per test |
| ML-DSA internal interface on M′ (externalMu false) | None: CKM_ML_DSA implements FIPS 204 Algorithms 2 and 3 on the message M [PKCS#11 v3.2 §6.67.5] | Unsupported |
| ML-DSA internal interface with external μ | No PKCS#11 v3.2 mechanism. The Hub’s engines use a non-standard mechanism code, 0x403c, for it; FIPS 204 allows μ to be computed in a different module [FIPS 204 Alg. 7, line 6] | Product-specific: executed (Playground suite and ACVP-format prototype) only where an engine advertises 0x403c, and labelled vendor-defined in evidence.json; shown as a skip otherwise |
| ML-DSA sigGen, deterministic | Import sk, sign with hedgeVariant CKH_DETERMINISTIC_REQUIRED [PKCS#11 v3.2 §6.67.5] | Byte-comparable with NIST’s deterministic sigGen sample (Playground suite) |
| ML-DSA sigGen, hedged | CKH_HEDGE_REQUIRED demands hedging but offers no input for rnd [PKCS#11 v3.2 §6.67.5] | Not reachable on either engine; shown as an explicit skip |
| ML-DSA keyGen (AFT) | CKM_ML_DSA_KEY_PAIR_GEN with the seed ξ supplied as CKA_SEED. The specification lists CKA_SEED among the attributes the mechanism contributes, so accepting a caller’s seed is engine behaviour, not a PKCS#11 requirement [PKCS#11 v3.2 §6.67.4] | Byte-comparable public key (Playground suite), engine-specific path |
The adapter’s three honest outcomes
For every test case the Hub’s dispatcher produces exactly one of: an answer (e.g. CKR_OK → testPassed: true; CKR_SIGNATURE_INVALID or CKR_SIGNATURE_LEN_RANGE → false), an unsupported record with the reason, or an error (any other return value, which produces no response entry). Unsupported is never counted as a pass, and an API failure is never reported as “signature invalid”.
Negative and Failure Testing
ACVP builds negative cases into the vector sets. An implementation that answers “valid” to everything fails them:
- ML-DSA sigVer: the server modifies a valid signature — modified message, modified commitment, modified z, modified hint — and includes at least 3 tests per modification type [ACVP ML-DSA sub-spec].
- SLH-DSA sigVer adds modified R, SIGFORS and SIGHT, and signatures that are too long or too short [ACVP SLH-DSA sub-spec].
- ML-KEM VAL: modified ciphertexts must go through implicit rejection and still return a key; invalid encapsulation and decapsulation keys must be detected [ACVP ML-KEM sub-spec] [FIPS 203 Alg. 18, §7.2, §7.3].
Implicit rejection is not an error path
A decapsulation test that expects an error return for a modified ML-KEM ciphertext is wrong: ML-KEM.Decaps_internal returns a key derived from the secret value z and the ciphertext instead [FIPS 203 §6.3, Alg. 18]. The negative assertion is that the key differs from the one the unmodified ciphertext yields — made without exposing secret-dependent diagnostics.
Two kinds of negative testing sit outside ACVP. Requirements ACVP does not test — the sub-specifications list zeroization, DRBG strength and incorrect-length ML-KEM/ML-DSA inputs [ACVP ML-KEM sub-spec] [ACVP ML-DSA sub-spec]. And API behaviour: wrong key type, wrong state, short buffers and other PKCS#11 return codes, which OASIS profile cases and product probes exercise [PKCS#11 Profiles v3.2].
Vocabulary the Hub holds itself to: one happy-path case is “sampled”, never “covered”; positive, negative, boundary and error-path coverage are reported separately, so a passing round-trip can never turn a negative-coverage cell green.
Evidence Provenance & Reproducibility
The Hub gives every result exactly one of eight evidence classes, each with the only claim a passing result of that class may make [PQC Today evidence policy]. The classes are the Hub’s policy, not a NIST taxonomy:
| Evidence class | Meaning | Permitted claim |
|---|---|---|
NIST ACVP-Server reference samplenist-acvp-reference-sample | Expected values copied from the public NIST ACVP-Server repository with immutable source identity. | “Passes this public NIST ACVP-Server reference sample” |
ACVTS-issued vectoracvts-issued-vector | Prompt issued to an authorized ACVTS test session and executed without changing the prompt. | “Executed ACVTS-issued vector set <id>; no validation claim until accepted” |
Published standard KATpublished-standard-kat | Expected values printed in a cited standard or consensus RFC. | “Passes the cited standard's example/KAT” |
Independent oracleindependent-oracle | Expected values produced by a separately identified implementation such as OpenSSL. | “Agrees with <oracle/version> for this case” |
Cross-implementation differentialcross-implementation-differential | Two implementations agree for the same inputs. | “C++ and Rust agree for this case” |
Functional round-tripfunctional-round-trip | Output produced by an implementation is consumed by the same or paired implementation. | “Completes this round-trip; no external correctness claim” |
OASIS profile caseoasis-profile-case | An identified OASIS PKCS#11 Profiles test case is replayed. | “Passes this named OASIS profile case” |
Product mechanism probeproduct-mechanism-probe | A PQC Today-authored test of a cited PKCS#11 behavior. | “Passes this product-authored probe” |
The claim ladder
Public wording stays on the highest rung actually reached. Passing rung 6 does not imply rungs 7–9 [PQC Today evidence policy].
- 1Mechanism is advertised
- 2Operation executes
- 3Functional round-trip passes
- 4Independent implementation agrees
- 5Published KAT passes
- 6Public NIST ACVP-Server reference sample passes
- 7ACVTS-issued vector response is generated
- 8ACVTS accepts the response— needs ACVTS and NIST
- 9NIST publishes a validation record— needs ACVTS and NIST
Provenance is a hash, not a label
A result is publishable only with its tested scope attached: algorithm and revision, operation, parameters, positive or negative expectation, vector source with an immutable hash (or oracle name and version), engine and build identity, runner and test ID, actual and expected result, timestamp and known limitations.
The Hub’s public NIST fixtures are pinned by ACVP-Server commit and SHA-256 [NIST ACVP-Server]. Its ACVP-format prototype calls a prompt a public reference sample only when the prompt’s SHA-256 matches one of those pins; any other prompt is recorded as an unverified imported vector set. When a vector is transformed — for example a sigGen output reused as a positive sigVer case — both the upstream and the local operation are recorded.
Generated counts, never typed by hand
Read at build time from the Hub’s vector manifest (src/data/validation/validation-counts.generated.json): 93 active vector files, of which 53 carry public NIST ACVP-Server reference samples (964 active cases).
| Evidence class | Active cases |
|---|---|
| NIST ACVP-Server reference sample | 964 |
| ACVTS-issued vector | 0 |
| Published standard KAT | 22 |
| Independent oracle | 4253 |
| Cross-implementation differential | 0 |
| Functional round-trip | 0 |
| OASIS profile case | 0 |
| Product mechanism probe | 2 |
Cross-Implementation & Cross-Platform Limits
Running the same case on two implementations or two platforms is valuable — a disagreement is always a finding. Agreement is weaker than it looks:
- Shared layers. In the Hub’s ACVP-format prototype, the C++ and Rust engines are driven by one dispatcher and one response builder. A defect in that shared code makes both engines agree and both be wrong, so dual-engine agreement is differential evidence, not independent validation. External expected values — a NIST sample, a published KAT — are what break the tie.
- Byte vs semantic equivalence. Hedged signatures differ on every run by design [FIPS 204 §3.4]; the comparison policy must be declared per operation.
- Environment identity. ACVP binds testing to an operational environment — “the specific hardware and/or software the client’s cryptographic implementation uses to run” — and certification associates the module with its OE [ACVP spec §5.4, §12.16.4]. A result from WebAssembly in a browser says nothing about a native build, a different CPU, or a hardware accelerator until that target runs the same cases and records its own identity.
- Honest statuses. Cross-target reports keep pass, fail, unsupported, not run and not comparable apart; the last three never add up to a pass.
Contributing a Source-Backed Test
The Hub accepts a new expected-value vector only with its provenance. Every vector file under src/data/acvp/ must have an entry in the vector manifest, and the gate fails until it does. The manifest’s own header documents the procedure:
- Add the vector file under src/data/acvp/.
- Generate a skeleton entry with the maintainers’ vector-manifest tool — it records the file’s SHA-256 and one case record per test case.
- Fill every TODO: the evidence class; the source (for NIST ACVP-Server material the commit, upstream path, retrieval date and upstream SHA-256); each case’s operation, parameters and positive/negative expectation; and a lineage record for any subset, rename or change of operation.
- The maintainers then run the vector-manifest check and regenerate the validation counts.
What a reviewer will check
- The source is primary and immutable (a commit, a published document revision).
- A licence or redistribution note exists for the copied values.
- The expected behaviour is stated, including every negative case.
- The Hub’s evidence policy asks for two reviewers — one verifies the source, a different one reviews the implementation — and the author is not the only reviewer.
- Nothing issued to a real ACVTS session is committed: issued vector sets may be controlled laboratory data and are processed locally only.
Propose the test with the validation contribution issue template: it asks for the algorithm and parameters, the immutable source, the licence note and the expected behaviour, which is what a reviewer checks above.
Not a coder? Open a Data Suggestion issue with the source URL and the exact case, and the maintainer does the manifest work.
Check off all sections and mark this reading done.
Classify evidence, dissect a public NIST vector set, and produce a response artifact.
Related modules
Check your understanding
5 questions on ACVP Lab Workflow: From Vector Set to Evidence, each with its answer and the reason.
Take the quizNext step
Practice it: HSM Playground validation workbenchThe HSM Playground’s Developer → Validation workbench runs the evidence-labelled validation suite and the same ACVP-format import/export prototype on both PKCS#11 engines.
Learning module content can be inaccurate. Please double-check its information. Report inaccuracies in PQC Today GitHub Discussions.