q-armor-pqc
ML-DSA-65 (FIPS 204) for QArmor, and an interim package by design: it exists to be deleted.
A cryptographic primitive with two implementations has two behaviours, and you find out which one you were running during the incident. This package exists so that QArmor has exactly one, until the place it belongs can hold it.
iden-q-post-quantum is the L2 generator's emission (iqcgen) and the only
place in the estate where a cryptographic primitive has one source and one KAT
gate. It carries ML-KEM-768 and ML-DSA-44, in Python and in TypeScript.
The DVN's on-chain attestations sign with ML-DSA-65 (SAD §6.4.2), and no
language emits that primitive. Per ADR-22 it is a spec missing from the
generator (SAD D-7), not a second library, and the generator is private.
While that gate stays closed, this package is where ML-DSA-65 lives.
When the emission publishes ML-DSA-65, this package is deleted. It is not
versioned in parallel, not migrated, not maintained: q_armor_lib and
q_armor_chain change the import to the emission and packages/q-armor-pqc/
disappears from the workspace. It is published to PyPI only because
q-armor-lib pins it, and a
published library cannot depend on an editable.
Install
pip install q-armor-pqc # ML-DSA-65 with a development Ed25519 fallback
pip install 'q-armor-pqc[pqc]' # + liboqs-python: real ML-DSA-65
Read this before you deploy it.
QARMOR_REQUIRE_PQC=1demands liboqs and fails if it is absent. Without that variable, ML-DSA-65 falls back to Ed25519, which is classical, and says so with aRuntimeWarningat import. It is the only live exception to "NIST algorithms only" (CLAUDE.md), and it is stated rather than hidden. The KEM has no fallback.
What is here, and what is delegated
MLDSA65: liboqs, or Ed25519 in development. The only thing that justifies the package.MLKEM768: delegates to the emission. ML-KEM-768 has not been implemented here since 2026-08-23: a second implementation over liboqs meant the same primitive on two backends, one inside the KAT gate and one outside (ADR-13, the SAD's S-6 scenario). The wrapper stays becauseencapsulatenever returned the raw secret butHKDF-SHA3-256(ss), while the emission derives with HKDF-SHA256: changing the import alone would have changed the AES key, a cryptographic change dressed up as a refactor. The emission's keygen is deterministic from a seed, and the seed comes fromsecrets.aead_encrypt/aead_decrypt: AES-256-GCM fromcryptography.
The KEM's X25519 fallback is gone, and that went with the delegation: the emission is pure Python and a hard dependency, so the "liboqs is absent" case no longer exists for the KEM.
About the name
In this estate "crypto" means blockchain, not cryptography:
quantum-armor-crypto is the DVN (SAD §4.2, §6.4, ADR-18). And q-crypto-*
is the prefix of the L2 generator (q-crypto-gen) and of what it emits. This
distribution's previous name, q-crypto-py, broke both rules and was also the
one the SAD used for the Python emission; the directory
packages/q-armor-crypto/ inverted ADR-18 in the tree itself. Both were
renamed on 2026-09-02, before the first publication, so that name would never
reach an index. pqc is what it contains; armor, who it belongs to.
A dependency on a private module, said out loud
pqc_engine imports iden_q_post_quantum._runtime, not the emission's public
API. Checked against iden-q-post-quantum 1.2.0: what is public is
seal/unseal (a hybrid X25519 and ML-KEM-768 envelope), encrypt/decrypt
by passphrase, derive_recipient_keys and the ML-DSA-44 signature. There is
no public raw KEM: mlkem768_keygen, encapsulate and decapsulate live
only in _runtime. QArmor's session needs the raw KEM to derive its own key,
so this dependency contradicts "consumers depend on published outputs"
(ADR-22) and is recorded as what it is: a gap in the generator with the same
shape as D-7, a missing public KEM API spec, to be closed upstream rather than
wrapped here.
Tests
tests/ carries the package's own suite: that the KEM's AES key is
HKDF-SHA3-256 of the emission's secret (and not the raw secret, nor
HKDF-SHA256), that encapsulate and decapsulate converge, that a signature
verifies only its own message, and that AES-GCM rejects a tampered tag.
What is still open is an ML-DSA-65 KAT against the FIPS 204 vectors: with
QARMOR_REQUIRE_PQC undefined the signature runs over Ed25519, and a test
that has only passed there has not seen ML-DSA-65. It is exercised as well
from q-armor-lib's session and proofs tests, from the DVN worker's, and from
api/.
The QArmor family
Five packages, one version, published together from a single tag.
| Package | What it is |
|---|---|
q-armor-lib |
Detection, scoring and the control-plane client |
q-armor-cli |
q-armor, the operator's command line |
q-armor-proxy |
Enterprise runtime: protection proxy, firewall, SIEM webhook |
q-armor-chain |
DVN worker and QArmorDVN.sol |
q-armor-pqc |
This one, and it is interim |
Next
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file q_armor_pqc-0.1.2-cp313-none-any.whl.
File metadata
- Download URL: q_armor_pqc-0.1.2-cp313-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: CPython 3.13
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49621c4c3be2fdd7b267ba2b851831411ae7bb9c252483c0b8e7255f2293690a
|
|
| MD5 |
9bc86f2b65893c36995bd8405840d807
|
|
| BLAKE2b-256 |
1dcbba2e6e9312fea618bd07caf3aeef70a43ed5bbf3acaf889b51b5371f8a9b
|
Provenance
The following attestation bundles were made for q_armor_pqc-0.1.2-cp313-none-any.whl:
Publisher:
release.yml on iden-q/iden-q-quantum-armor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
q_armor_pqc-0.1.2-cp313-none-any.whl -
Subject digest:
49621c4c3be2fdd7b267ba2b851831411ae7bb9c252483c0b8e7255f2293690a - Sigstore transparency entry: 2732435574
- Sigstore integration time:
-
Permalink:
iden-q/iden-q-quantum-armor@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/iden-q
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Trigger Event:
push
-
Statement type:
File details
Details for the file q_armor_pqc-0.1.2-cp312-none-any.whl.
File metadata
- Download URL: q_armor_pqc-0.1.2-cp312-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: CPython 3.12
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fcf799811b5ed73468a139eaa2d371102c5333091d62fb181e285b90bb5c025
|
|
| MD5 |
0e473318db2365b18fc360f4f92c6c38
|
|
| BLAKE2b-256 |
51b7ddc44137bc682440ad85ca4b5ced6cf5bcce148773c41a5db34958d9ecee
|
Provenance
The following attestation bundles were made for q_armor_pqc-0.1.2-cp312-none-any.whl:
Publisher:
release.yml on iden-q/iden-q-quantum-armor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
q_armor_pqc-0.1.2-cp312-none-any.whl -
Subject digest:
8fcf799811b5ed73468a139eaa2d371102c5333091d62fb181e285b90bb5c025 - Sigstore transparency entry: 2732435494
- Sigstore integration time:
-
Permalink:
iden-q/iden-q-quantum-armor@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/iden-q
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Trigger Event:
push
-
Statement type:
File details
Details for the file q_armor_pqc-0.1.2-cp311-none-any.whl.
File metadata
- Download URL: q_armor_pqc-0.1.2-cp311-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: CPython 3.11
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6981188d21c8a953b801c1af8e19768fc4e5b874c4396a13548c4fe1357e96ad
|
|
| MD5 |
d81bbe0e381063c6f0948fd944d6c940
|
|
| BLAKE2b-256 |
21e09fa3472478d82930d9dfaa7613401d69124b6021179f191645c7ad9a0493
|
Provenance
The following attestation bundles were made for q_armor_pqc-0.1.2-cp311-none-any.whl:
Publisher:
release.yml on iden-q/iden-q-quantum-armor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
q_armor_pqc-0.1.2-cp311-none-any.whl -
Subject digest:
6981188d21c8a953b801c1af8e19768fc4e5b874c4396a13548c4fe1357e96ad - Sigstore transparency entry: 2732435544
- Sigstore integration time:
-
Permalink:
iden-q/iden-q-quantum-armor@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Branch / Tag:
refs/heads/main - Owner: https://github.com/iden-q
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e7d68072afc1ee2b21db4c145a8dddeda2b95e5e -
Trigger Event:
push
-
Statement type: