Portable Algorithms
keylab includes a portable implementation layer for algorithms that are not universally supported by browser and edge runtime Web Crypto APIs.
Which algorithms use the portable layer?
| Algorithm | Curve | Use case |
|---|---|---|
| ES256K | secp256k1 | Blockchain/DID signing (Bitcoin, Ethereum) |
| Ed448 | Ed448 | High-security EdDSA signing (448-bit) |
| X448 | X448 | Key agreement (448-bit Diffie-Hellman) |
| ML-DSA-44 | ML-DSA | Post-quantum signatures (FIPS 204) |
| ML-DSA-65 | ML-DSA | Post-quantum signatures (FIPS 204) |
| ML-DSA-87 | ML-DSA | Post-quantum signatures (FIPS 204) |
EdDSA with Ed25519 does not use the portable layer — it goes through jose and Web Crypto directly.
When is the portable layer used?
The portable path is activated automatically when:
- You call
getKeyPair()withES256K,Ed448,X448,ML-DSA-44,ML-DSA-65, orML-DSA-87 - You call
signJwtWithPrivateKey()with one of these algorithms - You call
verifyTokenWithPublicKey()orcheckTokenValidness()and the token header or key material indicates one of these algorithms
You do not need to opt in — keylab detects the algorithm and routes accordingly.
Implementation
The portable layer uses @noble/curves for the curves below and @noble/post-quantum for ML-DSA:
- ES256K:
secp256k1from@noble/curves/secp256k1 - Ed448 / X448:
ed448/x448from@noble/curves/ed448 - ML-DSA-44 / ML-DSA-65 / ML-DSA-87:
@noble/post-quantumwhen the runtime cannot import an AKP key itself
ML-DSA keys are JWK only. kty is AKP, alg is required, and priv is the 32-byte seed. The seed is not returned on the public key. PEM is not supported for these algorithms. The library signs with an empty context string.
These are pure JavaScript implementations that work in every runtime without native crypto dependencies.
Key formats
JWK (recommended for portability)
JWK keys work in all runtimes (Node.js, Bun, Deno, Workers, browsers):
import { getKeyPair, JwtAlgorithmsEnum as Algs } from "keylab"
const es256kKeys = await getKeyPair({
algorithmIdentifier: Algs.ES256K,
keyFormat: "jwk",
keySize: 256,
})PEM (Node.js only)
PEM export requires Node.js crypto module and will throw in Workers/browsers:
const ed448Keys = await getKeyPair({
algorithmIdentifier: Algs.Ed448,
keyFormat: "pem",
keySize: 448,
})Signing
import { signJwtWithPrivateKey, JwtAlgorithmsEnum as Algs } from "keylab"
const token = await signJwtWithPrivateKey(
{ sub: "user-1", iss: "issuer" },
Algs.ES256K,
es256kKeys.privateKey,
{ kid: es256kKeys.kid },
)Verification
Portable algorithm tokens can be verified via any of the standard methods:
import { checkTokenValidness } from "keylab"
// Adhoc (in-memory keys)
const result = await checkTokenValidness(token, {
adhoc: [es256kKeys.publicKey],
})
// JWKS endpoint (keys are bucketed automatically)
const result = await checkTokenValidness(token, {
jwksUri: "https://issuer/.well-known/jwks.json",
})EdDSA detection
When using EdDSA as the algorithm, keylab inspects the key's crv field:
Ed25519→ standardjosepathEd448→ portable@noble/curvespath
This detection works for both signing and verification.