# pq-key-encoder
[](https://crates.io/crates/pq-key-encoder)
[](https://docs.rs/pq-key-encoder)
[](https://github.com/multivmlabs/post-quantum-packages/actions/workflows/ci.yml)
[](LICENSE)
[](https://blog.rust-lang.org/2024/05/02/Rust-1.78.0.html)
Zero-dependency\* post-quantum key encoding library for Rust. Encodes and decodes **ML-KEM**, **ML-DSA**, and **SLH-DSA** keys across DER (SPKI/PKCS#8), PEM, and JWK formats.
\*Only depends on [`pq-oid`](https://crates.io/crates/pq-oid) and [`zeroize`](https://crates.io/crates/zeroize).
## Supported Algorithms
| ML-KEM | ML-KEM-512, ML-KEM-768, ML-KEM-1024 |
| ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-87 |
| SLH-DSA | SHA2/SHAKE variants: 128s, 128f, 192s, 192f, 256s, 256f |
All 18 NIST post-quantum algorithm parameter sets are supported.
## Installation
```toml
[dependencies]
pq-key-encoder = "0.0.1"
```
### Feature Flags
| `std` | Yes | Enables `std::error::Error` impl |
| `pem` | Yes | PEM encoding/decoding |
| `jwk` | No | JWK encoding/decoding |
For `no_std` environments:
```toml
[dependencies]
pq-key-encoder = { version = "0.0.1", default-features = false }
```
## Usage
### Public Keys
```rust
use pq_key_encoder::{Algorithm, PublicKey, PublicKeyRef};
use pq_oid::MlKem;
let alg = Algorithm::MlKem(MlKem::Kem768);
// From raw bytes
let key = PublicKey::from_bytes(alg, &raw_bytes)?;
// DER (SPKI) round-trip
let der = key.to_der();
let decoded = PublicKey::from_spki(&der)?;
// Zero-copy decode (borrows from input)
let key_ref = PublicKeyRef::from_spki(&der)?;
assert_eq!(key_ref.algorithm(), alg);
// Encode into an existing buffer (zero intermediate allocations)
let mut buf = Vec::new();
key.encode_der_to(&mut buf);
```
### Private Keys
```rust
use pq_key_encoder::{Algorithm, PrivateKey, PrivateKeyRef};
use pq_oid::MlDsa;
let alg = Algorithm::MlDsa(MlDsa::Dsa44);
// From raw bytes
let key = PrivateKey::from_bytes(alg, &raw_bytes)?;
// DER (PKCS#8) round-trip
let der = key.to_der();
let decoded = PrivateKey::from_pkcs8(&der)?;
// Private keys are zeroized on drop
// into_bytes() returns Zeroizing<Vec<u8>> for safe ownership transfer
let bytes = key.into_bytes(); // automatically zeroized when dropped
```
### PEM
Requires the `pem` feature (enabled by default).
```rust
use pq_key_encoder::{PublicKey, PrivateKey, Key};
// Encode
let pem = public_key.to_pem();
// -----BEGIN PUBLIC KEY-----
// MIIEMjALBgkr...
// -----END PUBLIC KEY-----
// Decode (type-specific)
let key = PublicKey::from_pem(&pem)?;
// Auto-detect public or private
let key = Key::from_pem(&pem)?;
```
### JWK
Requires the `jwk` feature.
```toml
pq-key-encoder = { version = "0.0.1", features = ["jwk"] }
```
```rust
use pq_key_encoder::{PublicKey, PrivateKey, Key};
// Public JWK
let jwk = public_key.to_jwk();
let json = jwk.to_json();
// {"kty":"PQC","alg":"ML-KEM-768","x":"<base64url>"}
let decoded = PublicKey::from_jwk(&jwk)?;
// Private JWK (requires corresponding public key)
let priv_jwk = private_key.to_jwk(&public_key)?;
// Parse from JSON string (auto-detects public/private)
let key = Key::from_jwk_str(&json)?;
```
### Auto-Detection
The `Key` enum auto-detects key type from encoded data:
```rust
use pq_key_encoder::Key;
// Auto-detect SPKI (public) or PKCS#8 (private) from DER
let key = Key::from_der(&der_bytes)?;
// Also works with PEM labels and JWK fields
let key = Key::from_pem(&pem_string)?;
```
## Security
- Private key bytes are **zeroized on drop** via the `zeroize` crate
- `Debug` output **redacts** private key material (`[REDACTED]`)
- `PrivateKey::into_bytes()` returns `Zeroizing<Vec<u8>>` for safe ownership transfer
- `PrivateJwk` fields are zeroized on drop
- Base64 decoding enforces **strict padding** validation
- JWK parser **rejects duplicate fields** to prevent key confusion attacks
- JWK parsing enforces **resource limits** to prevent denial-of-service:
- Input size capped at **64 KiB**
- Field count capped at **32**
- Nesting depth capped at **8**
## Design
- **`no_std` compatible** with `alloc` — suitable for embedded and blockchain runtimes
- **Zero-copy decoding** — `PublicKeyRef` / `PrivateKeyRef` borrow directly from input buffers
- **Zero intermediate allocations** on encode paths — two-pass pattern computes size first, then writes directly to the output buffer
- **No format-specific dependencies** — DER, PEM, base64, and JSON are all hand-rolled with minimal code
- OID matching uses **raw DER byte comparison** against compile-time constants (no string allocation)
## Minimum Supported Rust Version
This crate requires **Rust 1.78** or later. The MSRV is tested in CI and will only be bumped in minor or major version releases.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/multivmlabs/post-quantum-packages).
## License
MIT