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)
- The
key_versionof the active Dilithium3 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
}'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) |
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"
}
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