Sign Documents with Dilithium3
This guide shows you how to create post-quantum digital signatures using Qpher Dilithium3 (ML-DSA-65, NIST FIPS 204), producing quantum-resistant signatures for document integrity and authenticity.
Prerequisitesâ
- A Qpher account with an active API key
- At least one active Dilithium3 key pair (see Key Management), or an active Composite-ML-DSA key pair for hybrid signatures
- The
key_versionof the active signing key you want to use
How Dilithium3 Signing Worksâ
Dilithium3 is a lattice-based digital signature scheme standardized by NIST as ML-DSA-65 (FIPS 204):
- Your message is sent to the Qpher API (base64-encoded)
- The KMS-Orchestrator signs the message using your private key inside the secure enclave
- A 3,293-byte signature is returned â your private key never leaves the enclave
Your Dilithium3 private key (4,000 bytes) is stored encrypted with AES-256-GCM inside the KMS-Orchestrator. It never leaves the secure enclave, and no API endpoint can export it. All signing operations happen server-side.
Step 1: Prepare Your Messageâ
Base64-encode the content you want to sign. This can be a document, a hash, a JSON payload, or any binary data.
import base64
import hashlib
# Option A: Sign the raw document
message = base64.b64encode(document_bytes).decode()
# Option B: Sign a SHA-256 hash of the document (recommended for large files)
doc_hash = hashlib.sha256(document_bytes).digest()
message = base64.b64encode(doc_hash).decode()
Step 2: Send the Sign Requestâ
curl -X POST https://api.qpher.ai/api/v1/signature/sign \
-H "Content-Type: application/json" \
-H "x-api-key: qph_your_key_here" \
-d '{
"message": "SGVsbG8sIFdvcmxkIQ==",
"key_version": 1
}'Using Composite-ML-DSA Hybrid Signatures (Pro/Enterprise)â
For defense-in-depth, add "algorithm": "Composite-ML-DSA" to combine ECDSA P-256 classical signatures with ML-DSA-65. Both signature components must verify for the result to be valid. Requires an active Composite-ML-DSA key pair â see Hybrid Cryptography.
curl -X POST https://api.qpher.ai/api/v1/signature/sign \
-H "Content-Type: application/json" \
-H "x-api-key: qph_your_key_here" \
-d '{
"message": "SGVsbG8sIFdvcmxkIQ==",
"key_version": 1,
"algorithm": "Composite-ML-DSA"
}'Step 3: Understand the Responseâ
/api/v1/signature/signContent-Type: application/json
x-api-key: qph_your_key_here{
"message": "SGVsbG8sIFdvcmxkIQ==",
"key_version": 1
}{
"data": {
"signature": "base64-encoded-signature-3293-bytes...",
"key_version": 1,
"algorithm": "Dilithium3"
},
"request_id": "770e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-15T10:32:00Z"
}| Field | Description |
|---|---|
signature | Base64-encoded Dilithium3 signature (3,293 bytes when decoded) |
key_version | The key version used for signing â store this alongside the signature |
algorithm | The signature algorithm used (Dilithium3 or Composite-ML-DSA) |
request_id | Unique request identifier for tracing and support |
Dilithium3 signatures are 3,293 bytes (approximately 4,391 characters in base64). This is larger than RSA or ECDSA signatures but provides quantum resistance at NIST Security Level 3.
Storing Signaturesâ
Store the signature, the original message (or its hash), and the key_version together. You will need all three to verify the signature later.
{
"document_id": "doc-abc-123",
"message_hash": "sha256:e3b0c44298fc...",
"signature": "base64-encoded-signature...",
"key_version": 1,
"algorithm": "Dilithium3",
"signed_at": "2026-02-15T10:32:00Z"
}
SLH-DSA Hash-Based Signaturesâ
SLH-DSA is generally available on /signature/sign, /signature/verify,
/signature/sign-hash, and /signature/verify-hash. Pass an algorithm
value of SLH-DSA-SHA2-128s, 128f, 192s, or 256s. Like Composite-ML-DSA,
SLH-DSA requires the Starter plan or above (the Explorer/Free plan returns
403 ERR_POLICY_001).
Alongside Dilithium3 (ML-DSA-65), Qpher supports SLH-DSA, the stateless hash-based digital signature algorithm standardized by NIST in FIPS 205 (August 2024). Where Dilithium3 is built on module lattices, SLH-DSA's security reduces to the security of well-studied hash functions (SHA-2) â giving you a second, mathematically independent signature option for defense-in-depth. This is NIST-standardised hash-based cryptography: Qpher implements the FIPS 205 algorithms.
Dilithium3 and SLH-DSA rest on different mathematical foundations (module lattices vs hash functions). If future cryptanalysis ever weakened one family, a signature produced with the other would be unaffected. SLH-DSA is the conservative, long-horizon choice â well suited to documents (wills, deeds, board resolutions) intended to remain verifiable for decades.
Parameter setsâ
Four SHA-2 parameter sets are available, spanning NIST Security Levels 1, 3, and 5. They trade signature size and signing time for assurance level:
| Algorithm value | NIST security level | Signature size | Recommended use case |
|---|---|---|---|
SLH-DSA-SHA2-128s | Level 1 | 7,856 bytes | General-purpose API signing |
SLH-DSA-SHA2-128f | Level 1 | 17,088 bytes | Latency-sensitive Level 1 workloads |
SLH-DSA-SHA2-192s | Level 3 | 16,224 bytes | Long-term / archival document signing |
SLH-DSA-SHA2-256s | Level 5 | 29,792 bytes | Maximum assurance (legal, government, sovereign) |
SLH-DSA-SHA2-128s is the recommended
starting point for general API signing (NIST Level 1, smallest signature
in the family). For documents that must remain verifiable for 30+ years,
SLH-DSA-SHA2-192s (NIST Level 3) is the recommended choice â it carries
enough quantum-security headroom for a multi-decade horizon. Reach for
SLH-DSA-SHA2-256s (NIST Level 5) only when a specific legal or sovereign
requirement calls for it; its larger signatures and longer signing times
are a deliberate trade-off for maximum assurance.
SLH-DSA signatures are substantially larger than Dilithium3's 3,293 bytes (roughly 8â30 KB depending on parameter set), and hash-based signing is slower than lattice-based signing â expect roughly one second per signing operation for the larger parameter sets. These are inherent properties of hash-based signatures; plan storage and interactive UX accordingly.
How to select SLH-DSAâ
You select a parameter set through the existing
optional algorithm parameter on the sign/verify endpoints â the same
parameter you already use to opt into Composite-ML-DSA hybrid signatures.
Omitting algorithm continues to default to Dilithium3, so existing
integrations keep working unchanged. The Qpher SDKs (Python, Node.js, Go)
expose this same optional algorithm parameter on their sign/verify
methods.
// Example request:
{
"message": "SGVsbG8sIFdvcmxkIQ==",
"key_version": 1,
"algorithm": "SLH-DSA-SHA2-192s"
}
Qpher's initial SLH-DSA offering covers the SHA-2 parameter sets above.
The SHAKE-based variants (SLH-DSA-SHAKE-*) are available on request â
contact support@qpher.ai if your use
case specifically requires them.
Error Handlingâ
| HTTP Status | Error Code | Cause | Resolution |
|---|---|---|---|
| 400 | ERR_INVALID_001 | Missing or invalid message or key_version | Ensure message is valid base64 and key_version is a positive integer |
| 401 | ERR_AUTH_001 | Invalid or missing API key | Check your x-api-key header |
| 404 | ERR_SIG_003 | Key version not found | Verify the key_version exists for your tenant |
| 409 | ERR_SIG_004 | Key is not in active status | Only active keys can sign â generate or rotate to get a new active key |
| 429 | ERR_RATE_001 | Rate limit exceeded | Reduce request frequency or upgrade your plan |
| 500 | ERR_CRYPTO_001 | Signing operation failed | Retry the request; contact support if persistent |
Related Guidesâ
- Verify Signatures â Verify Dilithium3 signatures
- Key Management â Generate and manage your signing keys
- Key Versioning â Understand key lifecycle for signatures
- Migration Guide â Migrate from RSA/ECDSA to Dilithium3