pqc-sig
Post-quantum digital signatures — a standalone, WASM-compatible Rust library implementing NIST-standardized post-quantum signature algorithms.
Reading the badges: CI is the build-and-test signal — it should be green, and a red one means something is actually broken. cargo-deny is the dependency-advisory signal — also expected green. It previously carried an accepted-and-documented exception (the optional
fndsafeature depended on the now-archivedpqcrypto-falcon); that dependency was migrated to a pure-Rust, actively-maintained replacement, so there is currently nothing accepted or suppressed — seeSECURITY.md.
What runs today vs. what is designed
Runs today:
- ML-DSA-44/65/87 (FIPS 204) and all 12 SLH-DSA parameter sets (FIPS 205) — pure Rust,
no_std+alloc, directly compilable towasm32-unknown-unknown, keygen/sign/verify covered by integration tests. - FN-DSA-512/1024 (Falcon, FIPS 206 draft), gated behind the
fndsafeature flag — pure Rust via thefn-dsacrate,no_std+alloc, directly compilable towasm32-unknown-unknown. No C compiler needed. - Hybrid Ed25519 + ML-DSA-65, gated behind the
hybridfeature flag —HybridSignercombines a classical and a post-quantum signature; verification requires both to pass. For bridging classical deployments to PQC during migration. Pure Rust, WASM-compatible. SigPublicKey::to_multibase()/from_multibase()— W3C Multikey encoding for ML-DSA and SLH-DSA public keys, independently verified against themultibaseandssi-multicodeccrates (seetests/ssi_interop_test.rs). Not implemented for FN-DSA — no multicodec code is registered for it upstream yet; both functions return an error forFnDsa512/FnDsa1024.- WASM bindings (
wasmfeature,wasm-bindgen) for ML-DSA and SLH-DSA — seesrc/wasm.rs. FN-DSA and the hybrid combiner have no WASM bindings yet (though both now compile towasm32— see above). - A WIT Component Model interface at
wit/pqc-sig.witdescribing the ML-DSA and SLH-DSA interfaces for non-Rust WASM runtimes. - CI (
.github/workflows/ci.yml) builds and tests both default features and--all-featureson every push/PR, including against the packagedcargo packageartifact.
Designed, not yet implemented:
- FN-DSA Multikey support — blocked on a multicodec code being registered upstream for FIPS 206/Falcon; tracked, not something this crate can unilaterally resolve.
- FIPS 206 itself is a NIST draft standard, not yet finalized (see the FN-DSA badge above) — the implementation tracks the draft and may need breaking changes once it's finalized.
- MSRV,
unsafecode policy, and docs.rs metadata are now stated below (see Build Requirements and Safety).
Release
| Version | Date | Artifacts |
|---|---|---|
| v0.2.1 | 2026-09-01 | crates.io |
| v0.2.0 | 2026-09-01 | crates.io |
| v0.1.0 | 2026-08-01 | pqc-sig-v0.1.0-wasm.zip |
Algorithms
| Algorithm | Standard | Security Level | Public Key | Signature | WASM |
|---|---|---|---|---|---|
| ML-DSA-44 | FIPS 204 | 2 (128-bit) | 1312 B | 2420 B | ✅ |
| ML-DSA-65 | FIPS 204 | 3 (192-bit) | 1952 B | 3309 B | ✅ |
| ML-DSA-87 | FIPS 204 | 5 (256-bit) | 2592 B | 4627 B | ✅ |
| SLH-DSA-SHA2-128s | FIPS 205 | 1 | 32 B | 7856 B | ✅ |
| SLH-DSA-SHA2-128f | FIPS 205 | 1 | 32 B | 17088 B | ✅ |
| SLH-DSA-SHA2-192s | FIPS 205 | 3 | 48 B | 16224 B | ✅ |
| SLH-DSA-SHA2-192f | FIPS 205 | 3 | 48 B | 35664 B | ✅ |
| SLH-DSA-SHA2-256s | FIPS 205 | 5 | 64 B | 29792 B | ✅ |
| SLH-DSA-SHA2-256f | FIPS 205 | 5 | 64 B | 49856 B | ✅ |
| SLH-DSA-SHAKE-128s | FIPS 205 | 1 | 32 B | 7856 B | ✅ |
| SLH-DSA-SHAKE-128f | FIPS 205 | 1 | 32 B | 17088 B | ✅ |
| SLH-DSA-SHAKE-192s | FIPS 205 | 3 | 48 B | 16224 B | ✅ |
| SLH-DSA-SHAKE-192f | FIPS 205 | 3 | 48 B | 35664 B | ✅ |
| SLH-DSA-SHAKE-256s | FIPS 205 | 5 | 64 B | 29792 B | ✅ |
| SLH-DSA-SHAKE-256f | FIPS 205 | 5 | 64 B | 49856 B | ✅ |
| FN-DSA-512 (Falcon) | FIPS 206‡ | 1 | 897 B | 666 B | ✅* |
| FN-DSA-1024 (Falcon) | FIPS 206‡ | 5 | 1793 B | 1280 B | ✅* |
*FN-DSA is pure Rust and WASM-compatible. Enable with --features fndsa.
‡FIPS 206 (FN-DSA/Falcon) is not yet finalized; it is currently a draft standard pending ratification.
Recommended: ML-DSA-65 for general use (best balance of security and performance).
Build Requirements
- Rust 1.85 or later (edition 2021) — this is the crate's declared
rust-version(MSRV), enforced by themsrvjob inci.yml, which builds the working tree with a pinned Rust 1.85.0 toolchain on every push/PR. The MSRV is driven by a transitive dependency in the default build (ablock-bufferversion pulled in viaslh-dsa's hashing chain requires theedition2024Cargo feature, stabilized in Rust 1.85.0) — not by this crate's own code. No nightly features are used. The MSRV promise coverscargo build/cargo checkonly, notcargo test: a couple of this crate's dev-only interop-test dependencies need a newer toolchain, but dev-dependencies are never pulled in by downstream consumers. - No C compiler, credentials, network services, or local files outside the repo are needed
to build or test this crate — every optional feature (
fndsa,hybrid) is pure Rust.
Safety
This crate's own code contains zero unsafe, enforced by #![forbid(unsafe_code)] in
src/lib.rs — this is true of the default build and every combination of this
crate's own features, and there is no C FFI anywhere in the dependency tree (the fndsa
feature dropped its C FFI dependency — see release notes). It is not
true of the dependency graph as a whole in the stronger sense of "no unsafe anywhere": the
optional fndsa feature (fn-dsa) and hybrid feature (curve25519-dalek, via
ed25519-dalek) both use unsafe internally for architecture-specific performance paths
(AVX2 intrinsics, native floating point) — safe Rust, not FFI, and gated behind non-default
features. The default build has zero unsafe in its entire dependency tree.
Continuous Integration
.github/workflows/ci.yml runs on every push and pull request, on
a fresh GitHub-hosted runner with no dependency or build caching — every run is a genuine
clean-room build.
Five jobs:
- Default features (build + test) —
cargo build/cargo testwith default features. The crate's supported surface; must always pass. --all-features(build + test) — same, with every optional algorithm feature enabled.- Packaged artifact (default features) — builds and tests the actual packaged
.crateoutput (whatcargo add pqc-sigships), not just the working tree — catches.gitignore/package-excludemistakes that only surface for someone installing the published crate. - Packaged artifact (
--all-features) — same, with every optional feature enabled. - MSRV (Rust 1.85.0, build) — builds (not tests — see
Build Requirements) against the pinned MSRV toolchain, not
stable. Fails the moment any code or dependency bump relies on a newer language feature than the declaredrust-version.
Dependency scanning
.github/workflows/cargo-deny.yml runs
cargo-deny against deny.toml
— advisories, bans, licenses and sources — on every push to main, every pull request, and
weekly on Mondays at 06:00 UTC. A failing scheduled run opens or updates a tracking issue
labelled security / cargo-deny.
It is a separate workflow from ci.yml on purpose, and it is not a required status
check — that stays true even now that the advisories check passes cleanly, since a future
advisory landing on any dependency shouldn't block merges by default; it should surface
visibly instead. Any accepted advisory would be listed by RUSTSEC ID in
SECURITY.md — not hidden with an ignore
list, and not papered over with continue-on-error.
Quick Start
[]
= "0.2"
use MlDsa65Keypair;
use OsRng;
// Generate a keypair
let keypair = generate.unwrap;
let pk = keypair.public_key;
// Sign a message
let message = b"Hello, post-quantum world!";
let signature = keypair.sign.unwrap;
// Verify the signature
verify.unwrap;
WASM Usage
Build with the wasm feature for wasm32-unknown-unknown targets:
= { = "0.2", = false, = ["wasm"] }
Or build the WASM binary directly:
powershell -ExecutionPolicy Bypass -File pqc-sig/build.ps1
JavaScript/TypeScript
import init from './pqc_sig.js';
await ;
// Generate keypair
const keypair = ;
const pubKeyBytes = keypair.;
// Sign
const message = .;
const signature = keypair.;
// Verify
const valid = ;
console.log; // true
console.log; // "0.1.0"
Hybrid Signatures
The opt-in hybrid feature adds an Ed25519 + ML-DSA-65 combiner for bridging classical
deployments to PQC during migration: HybridSigner produces both signatures, and
verification requires both to pass — an attacker must break both primitives to forge one.
= { = "0.2", = ["hybrid"] }
// Not run as a doctest: this README is included as the crate's top-level doc comment
// (`#![doc = include_str!("../README.md")]`), which compiles unconditionally — the
// `hybrid` feature isn't guaranteed enabled there. See `src/hybrid.rs` for the
// feature-gated, actually-tested version of this example.
use OsRng;
use HybridSigner;
let signer = generate.unwrap;
let pk = signer.public_key;
let message = b"Hello, hybrid world!";
let signature = signer.sign.unwrap;
verify.unwrap;
WASM Component Model
The wit/pqc-sig.wit file defines the WIT (WebAssembly Interface Types) interface for this library, enabling use as a WASM Component with any compliant runtime.
Package: x307:pqc-sig@0.1.0
WIT Interface Summary
package x307:pqc-sig@0.1.0;
interface types { ... } // sig-algorithm enum, sig-error variant
interface ml-dsa { ... } // ML-DSA-44/65/87 keypair resource + sign/verify
interface slh-dsa { ... } // SLH-DSA all 12 parameter sets
interface pqc-sig { ... } // Unified dispatch interface
world pqc-sig-world {
export ml-dsa;
export slh-dsa;
export pqc-sig;
}
Using with wasmtime (CLI)
# Run a component that imports pqc-sig
Using with jco (Node.js / Browser)
# Transpile the component for browser/Node.js use
# Then import in your JS/TS project:
;
WIT File Location
The WIT interface is at wit/pqc-sig.wit and is also included in the WASM release artifact (pqc-sig-v0.1.0-wasm.zip).
no_std Support
This crate is no_std-compatible with alloc. Disable the std feature:
= { = "0.2", = false }
Features
| Feature | Description |
|---|---|
std (default) |
Enable std-dependent trait impls |
wasm |
Enable wasm-bindgen exports + JS entropy |
fndsa |
Enable FN-DSA/Falcon (pure Rust, WASM-compatible) |
hybrid |
Enable the Ed25519 + ML-DSA-65 hybrid combiner (pure Rust, WASM-compatible) |
Algorithm Selection Guide
| Use Case | Recommended Algorithm |
|---|---|
| General purpose (balanced) | ML-DSA-65 (FIPS 204) |
| Maximum security | ML-DSA-87 (FIPS 204) |
| Minimum key size | ML-DSA-44 (FIPS 204) |
| WASM module integrity | SLH-DSA-SHA2-128s (FIPS 205) |
| Compact signatures | FN-DSA-512 (FIPS 206) |
| Audit/long-term archival | ML-DSA-87 (FIPS 204) |
| Bridging a classical deployment to PQC | Hybrid Ed25519 + ML-DSA-65 (hybrid feature) |
Security Notes
- PQC-only by default — classical cryptography (Ed25519) is opt-in only via the
hybridfeature, for migration bridging - No KEM — signatures only (see
pqc-kemfor key encapsulation) - Caller-provided RNG — no
OsRnghardcoding in library code - Zeroize on drop — secret keys are automatically zeroed when dropped
- All operations return
SigResult<T>— never panics
Architecture
pqc-sig/
├── src/
│ ├── lib.rs — crate root, re-exports, no_std gate
│ ├── error.rs — SigError enum, SigResult alias
│ ├── types.rs — wire types (SigPublicKey, SigSecretKey, Signature, SignedMessage)
│ ├── wasm.rs — wasm-bindgen exports (feature = "wasm")
│ ├── fips204/ — ML-DSA (FIPS 204): ML-DSA-44/65/87
│ ├── fips205/ — SLH-DSA (FIPS 205): 12 parameter sets
│ ├── fips206/ — FN-DSA (FIPS 206): Falcon-512/1024 (feature = "fndsa")
│ └── hybrid.rs — Ed25519 + ML-DSA-65 combiner (feature = "hybrid")
├── tests/ — integration tests
└── wit/ — WIT interface for WASM Component Model
Release Notes
v0.1.0 (2026-08-01) — Initial Release
- 17 algorithms implemented: ML-DSA-44/65/87, all 12 SLH-DSA variants, FN-DSA-512/1024
- 85 tests passing (66 without Falcon, 85 with
--features fndsa) - WASM-compatible: ML-DSA (all 3 variants) + SLH-DSA (all 12 variants) compile to
wasm32-unknown-unknown - WIT Component Model interface at
wit/pqc-sig.wit - Standalone crate — no workspace coupling,
no_std+alloc - Caller-provided RNG, secret keys zeroized on drop
See release-notes.md for full details, and CHANGELOG.md for the authoritative Keep-a-Changelog-format record.
Stability and support
This project ships 0.x. See STABILITY.md for what counts as a breaking
change, deprecation notice, release cadence, and support posture.
Security
See SECURITY.md to report a vulnerability. It also lists any known,
accepted dependency advisories (currently none — see
SECURITY.md) and the dependency-scanning
setup.
Contributing
See CONTRIBUTING.md, including the current external-contribution
posture.
License
MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.
Maintainer
Ed Johnson