Skip to main content

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_version of 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):

  1. Your message is sent to the Qpher API (base64-encoded)
  2. The KMS-Orchestrator signs the message using your private key inside the secure enclave
  3. A 3,293-byte signature is returned — your private key never leaves the enclave
Private Key Protection

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​

POST/api/v1/signature/signSign a message using Dilithium3 digital signatures
Sign a Document
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)​

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.

Sign with Composite-ML-DSA
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​

RequestPOST/api/v1/signature/sign
Content-Type: application/json
x-api-key: qph_your_key_here
{
  "message": "SGVsbG8sIFdvcmxkIQ==",
  "key_version": 1
}
Response200
{
  "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"
}
FieldDescription
signatureBase64-encoded Dilithium3 signature (3,293 bytes when decoded)
key_versionThe key version used for signing — store this alongside the signature
algorithmThe signature algorithm used (Dilithium3 or Composite-ML-DSA)
request_idUnique request identifier for tracing and support
Signature Size

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​

Available on Starter and above

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.

Why a second signature family?

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 valueNIST security levelSignature sizeRecommended use case
SLH-DSA-SHA2-128sLevel 17,856 bytesGeneral-purpose API signing
SLH-DSA-SHA2-128fLevel 117,088 bytesLatency-sensitive Level 1 workloads
SLH-DSA-SHA2-192sLevel 316,224 bytesLong-term / archival document signing
SLH-DSA-SHA2-256sLevel 529,792 bytesMaximum assurance (legal, government, sovereign)
Which one should I pick?

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.

Signature size and signing time

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"
}
SHAKE variants available on request

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 StatusError CodeCauseResolution
400ERR_INVALID_001Missing or invalid message or key_versionEnsure message is valid base64 and key_version is a positive integer
401ERR_AUTH_001Invalid or missing API keyCheck your x-api-key header
404ERR_SIG_003Key version not foundVerify the key_version exists for your tenant
409ERR_SIG_004Key is not in active statusOnly active keys can sign — generate or rotate to get a new active key
429ERR_RATE_001Rate limit exceededReduce request frequency or upgrade your plan
500ERR_CRYPTO_001Signing operation failedRetry the request; contact support if persistent