A pure Nim port of the CRYSTALS-Dilithium digital signature scheme (post-quantum). Uses SHAKE (Keccak) and constant-time helpers implemented in Nim.
Dilithium lets you create a public key, sign messages with a secret key, and anyone can verify those signatures with the public key. It’s NIST-standardized.
- Implements the full signature API (keygen, sign, verify) for Dilithium2/3/5.
- Passes the official NIST KAT (.rsp) vectors.
- Supports both deterministic and randomized signing (compile-time toggle).
- Optional context label (up to 255 bytes).
| Set | Public Key | Secret Key | Signature |
|---|---|---|---|
| Dilithium2 | 1312 B | 2528 B | 2420 B |
| Dilithium3 | 1952 B | 4000 B | 3293 B |
| Dilithium5 | 2592 B | 4864 B | 4595 B |
Message size is unchanged by signing; only the signature is added.
Simple, human-readable API (see dilithium.nim).
import dilithium # exports: generateKeypair, signDetached, verifyDetached, signMessage, openSignedMessage
# 1) Make a key pair (uses OS randomness).
let (publicKey, secretKey) = generateKeypair()
# 2) Sign a message (detached signature).
let message = "Meet at 12:30 near the cafe."
let signature = signDetached(message, secretKey) # optional label: signDetached(message, secretKey, "orders")
# 3) Verify the detached signature.
let ok = verifyDetached(signature, message, publicKey)
doAssert ok
# 4) Sign a message as a single blob (attached: signature stored before the message).
let signedBlob = signMessage(message, secretKey) # optional label: signMessage(message, secretKey, "chat")
# 5) Verify and recover the original message from a signed blob.
let (valid, recovered) = openSignedMessage(signedBlob, publicKey)
doAssert validUse a short label to keep signatures scoped to a feature in your app (e.g., "login", "invoice").
You must pass the same label to both sign and verify.
let label = "orders"
let sig = signDetached("Order #4821: ship today", secretKey, label)
let ok = verifyDetached(sig, "Order #4821: ship today", publicKey, label) # true
let no = verifyDetached(sig, "Order #4821: ship today", publicKey, "WRONG")# falseBy default we follow the reference: randomized signing adds fresh randomness to each signature.
disable randomized signing at compile time:
# Deterministic signing (rnd = all zero bytes):
-d:nors or -d:norandsigEnable it (default, can be omitted):
# Randomized signing (fresh randomness per signature):
-d:rs or -d:randsigDefault is Dilithium2. Choose at compile time:
-d:mode=2 # Dilithium2
-d:mode=3 # Dilithium3
-d:mode=5 # Dilithium5A lightweight KAT harness is included (parses NIST .rsp and checks pk/sk/sm):
# Example: point to the Dilithium2 vectors
KAT_RSP=/nistkat/PQCsignKAT_Dilithium2.rsp \
nim c -r -d:kat nistkat/test_kat_runner.nimTips:
- Use
KAT_LIMIT=Nto run only the first N vectors while debugging.
- Public API uses the OS RNG (e.g.,
/dev/urandom) for keygen and (if enabled) for randomized signing. - The KAT harness uses the same deterministic DRBG as the reference so results match the
.rspfiles.
- Constant-time primitives are used where required, but this code has not been audited.
- Nim is GC’d; secret zeroization is not guaranteed. Be careful with long-lived secrets.
src/
ntt.nim # Number-theoretic transform and tables
packing.nim # (Un)packing of keys/signatures
params.nim # Parameter set & sizes; selected by DILITHIUM_MODE (2/3/5)
poly.nim # Polynomial ops
polyvec.nim # Vectors of polynomials
randombytes.nim # OS RNG for public API (e.g., /dev/urandom)
reduce.nim # Modular reduction helpers
rounding.nim # power2round, decompose, hints (make_hint/use_hint)
sign.nim # Keygen, sign, verify (public API)
symmetric.nim # SHAKE256 wrappers (XOF/PRF)
dilithium.nim # high-level, friendly API
test/ # basic tests
nistkat/ # KAT tests
private/ # crypto backends
This project is for reference/education. Do not use in production without an independent security review.