Skip to main content
[AVAILABLE] for the core primitives below. [ROADMAP] for KMS/HSM key custody, clearly separated.

Signature algorithms

Evidence: packages/crypto/test/SignatureProvider.test.ts, Dilithium3SignatureProvider.test.ts, dilithium3-cross-instance.test.ts.

Hash algorithms

HashAlgorithms: sha256, sha3-512, blake3 (packages/shared/src/config/CryptoAlgorithms.ts). Configured via HASH_PROVIDER. Used for trustRecordHash, businessTransactionHash (via ExecutableContentHasherTrustRecordHasher, the same computation on signing and verifying sides), and receipt hashing.

Key/algorithm binding guard

Signing or verifying with the wrong key type for the configured provider (e.g. an Ed25519 key against an ML-DSA-65 provider) fails closed with an error naming both the expected and actual key type, it does not silently dispatch based on the key’s own type (assertKeyType, used by both signature providers).

Key custody today: local PEM files

FileKeyProvider reads <PARMANA_KEY_DIR>/<keyId>.private.pem / <PARMANA_KEY_DIR>/<keyId>.public.pem from disk, one directory, keys distinguished by ID (default, pq, gateway), not by separate per-file path variables. This is the only implemented key provider. See Deploy patterns for the full set of keys a running server needs.

Per-tenant signing keys (opt-in)

A transaction whose metadata.tenantId is set signs its Execution Authorization under a dedicated tenant.<tenantId> key when one has been provisioned (same FileKeyProvider layout: scripts/generate-keypair.ts --algorithm ed25519 --key-id tenant.<tenantId>), instead of the single shared default key every deployment uses otherwise (TenantKeyResolver, packages/runtime/src/TenantKeyResolver.ts). Verification already resolves the right public key per-authorization via its own keyId field (the key/algorithm binding guard above, and 2.28’s keyProvider/keyExpiryStore options), so a tenant key just needs to exist — no verifier-side configuration change.
This is naming-convention key selection, not KMS/HSM custody: the tenant’s private key still lives as a local PEM file, provisioned by hand, one generate-keypair run per tenant. An unprovisioned or malformed tenantId falls back to the shared default key rather than failing closed, deliberate, so adoption is incremental, but worth knowing before relying on it for isolation.
KeyProviders (packages/shared/src/config/KeyProviders.ts) declares aws-kms, azure-key-vault, gcp-kms, and hsm as valid config values, but no provider class implements any of them yet, a declared config surface for planned work, not a working integration. Setting KEY_PROVIDER to one of these fails startup loudly (KeyBootstrap.create() throws, naming the value) rather than silently falling back to FileKeyProvider. See Roadmap for the KMS custody plan.
There is a live incident on record: the default signing key committed to this repository before 2026-07-05 was publicly exposed and is permanently compromised. It was rotated on 2026-07-05. This is exactly the kind of exposure KMS/HSM custody is designed to prevent, see Roadmap, Move 1.

Key rotation, same algorithm

Distinct from the algorithm-migration gap below: rotating to a new key pair under the same algorithm, because a key may be compromised, or on a routine schedule, is built and tested for every durable evidence signer (Trust Records, Refusal Records, Audit Events), mirroring the pattern the Authorization/Gateway envelope already had via keyProvider/keyExpiryStore. PARMANA_VERIFICATION_KEY_ID / PARMANA_VERIFICATION_SECONDARY_KEY_ID point new signing at a freshly generated keyId (scripts/rotate-verification-key.ts); every already-issued record keeps verifying unaffected, since verification always resolves the public key by the record’s own stored keyId, never a hardcoded “current” one. FileKeyProvider.listKeys() enumerates every key a deployment currently holds, and GET /keys/:keyId / GET /.well-known/jwks.json make them fetchable by a third party.

No algorithm migration path yet

AuthorizationVerifier does not dispatch verification based on the envelope’s algorithm field, a verifying process supports exactly one configured PRIMARY_SIGNATURE_PROVIDER at a time. Re-keying from Ed25519 to ML-DSA-65 while retaining the ability to verify previously-signed records is an explicit Future Claim, not yet built. The key rotation above is same-algorithm only (a new Ed25519 key replacing an old Ed25519 key, for example) — it does not close this gap, which is specifically about changing which algorithm a verifying process trusts while old records under the previous algorithm remain verifiable.