Back to DashboardProtocolsPhase 6 · Infrastructure & Performanceadvanced90 minWork in progress

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.

Practice in the Simulation

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].

Algorithm validation compared with module validation
QuestionAlgorithm 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, and externalMu: [true] to accepting a message representative μ computed outside the module [ACVP ML-DSA sigGen registration] [FIPS 204 Alg. 7].
  • prereqVals names prerequisite algorithm validations (here SHA) [ACVP ML-DSA sigGen registration].
  • The revision is part of the identity: the sub-specification lists ML-DSA / sigGen as both FIPS204 and FIPS204-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. 1

    Register capabilities POST /testSessions

    The 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. 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. 3

    Submit the response POST …/vectorSets/{vsId}/results

    The client returns the implementation’s output for every test case, linked to the prompt by vsId.

  4. 4

    Read the disposition GET …/vectorSets/{vsId}/results

    The server reports passed, fail, incomplete, expired, missing, unreceived or error for the vector set and for each test case.

  5. 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. 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:

ACVP test types by algorithm
Algorithm / modeTest typeWhat the implementation must do
SHA-1 / SHA-2AFTHash single, multiple and partial blocks of random data — checks chunking and padding [ACVP SHA sub-spec]
SHA-1 / SHA-2MCTMonte 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-2LDTLarge Data Test: hash a multi-gigabyte message described, not transmitted, by the prompt [ACVP SHA sub-spec]
ML-KEM / keyGenAFTGenerate a key pair from a provided seed (ML-KEM.KeyGen_internal) [ACVP ML-KEM sub-spec]
ML-KEM / encapDecapAFTEncapsulate with a server-chosen m (ML-KEM.Encaps_internal) and return c and k [ACVP ML-KEM sub-spec]
ML-KEM / encapDecapVALDecapsulate 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, sigVerAFTKey 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, sigVerAFTKey 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].

Where randomness enters each PQC algorithm
OperationRandomnessConsequence for testing
ML-KEM.Encaps32-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.SignDefault 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 signDefault 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.

Which ACVP test groups a PKCS#11 v3.2 interface can carry
ACVP test groupPKCS#11 v3.2 pathHub 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 / preHashCKM_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, deterministicImport 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, hedgedCKH_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:

The eight evidence classes and their permitted claims
Evidence classMeaningPermitted claim
NIST ACVP-Server reference sample
nist-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 vector
acvts-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 KAT
published-standard-kat
Expected values printed in a cited standard or consensus RFC.“Passes the cited standard's example/KAT”
Independent oracle
independent-oracle
Expected values produced by a separately identified implementation such as OpenSSL.“Agrees with <oracle/version> for this case”
Cross-implementation differential
cross-implementation-differential
Two implementations agree for the same inputs.“C++ and Rust agree for this case”
Functional round-trip
functional-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 case
oasis-profile-case
An identified OASIS PKCS#11 Profiles test case is replayed.“Passes this named OASIS profile case”
Product mechanism probe
product-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].

  1. 1Mechanism is advertised
  2. 2Operation executes
  3. 3Functional round-trip passes
  4. 4Independent implementation agrees
  5. 5Published KAT passes
  6. 6Public NIST ACVP-Server reference sample passes
  7. 7ACVTS-issued vector response is generated
  8. 8ACVTS accepts the response— needs ACVTS and NIST
  9. 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).

Active validation cases by evidence class
Evidence classActive cases
NIST ACVP-Server reference sample964
ACVTS-issued vector0
Published standard KAT22
Independent oracle4253
Cross-implementation differential0
Functional round-trip0
OASIS profile case0
Product mechanism probe2

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:

  1. Add the vector file under src/data/acvp/.
  2. Generate a skeleton entry with the maintainers’ vector-manifest tool — it records the file’s SHA-256 and one case record per test case.
  3. 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.
  4. 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.

Check your understanding

5 questions on ACVP Lab Workflow: From Vector Set to Evidence, each with its answer and the reason.

Take the quiz

Next step

Practice it: HSM Playground validation workbench

The 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.