summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md183
1 files changed, 183 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bec0fe9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,183 @@
+![](banner.png)
+
+A JWT module for Deno that signs and verifies tokens using **ML-DSA** (FIPS 204) or a **hybrid ML-DSA + Ed25519** scheme for defence-in-depth during the post-quantum transition.
+
+
+
+## Features
+
+- **ML-DSA only** — `ML-DSA-44` / `ML-DSA-65` / `ML-DSA-87` via [`@oqs/liboqs-js`](https://www.npmjs.com/package/@oqs/liboqs-js)
+- **Hybrid mode** — ML-DSA + Ed25519 signed in parallel. Both signatures must verify. If either algorithm is later broken (classical or quantum), the other still protects the token.
+- Standard JWT claims: `exp`, `iat`, `iss`, `sub`
+- Zero-dependency verification helpers, typed errors via `JwtError`
+
+
+
+## Install
+
+```sh
+deno add jsr:@netopwibby/pq-jwt
+```
+
+Or import directly:
+
+```ts
+import { sign, verify } from "jsr:@netopwibby/pq-jwt";
+```
+
+
+
+## Usage
+
+### ML-DSA only
+
+```ts
+import { generateKeyPair, sign, verify } from "jsr:@netopwibby/pq-jwt";
+
+const { publicKey, secretKey } = await generateKeyPair("ML-DSA-65");
+
+const token = await sign(
+ {
+ role: "member",
+ sub: "user_123"
+ },
+ secretKey,
+ {
+ algorithm: "ML-DSA-65",
+ expiresIn: 3600,
+ issuer: "kern.neue"
+ }
+);
+
+const claims = await verify(token, publicKey, {
+ algorithm: "ML-DSA-65",
+ issuer: "kern.neue"
+});
+
+console.log(claims.sub); // "user_123"
+```
+
+### Hybrid ML-DSA + Ed25519
+
+```ts
+import {
+ generateHybridKeyPair,
+ hybridSign,
+ hybridVerify
+} from "jsr:@netopwibby/pq-jwt";
+
+const keys = await generateHybridKeyPair("ML-DSA-65");
+
+const token = await hybridSign(
+ {
+ role: "admin",
+ sub: "user_123"
+ },
+ keys,
+ {
+ expiresIn: 3600,
+ issuer: "kern.neue"
+ }
+);
+
+const claims = await hybridVerify(
+ token,
+ {
+ ed25519PublicKey: keys.ed25519.publicKey,
+ mlDsaPublicKey: keys.mlDsa.publicKey
+ },
+ {
+ issuer: "kern.neue",
+ variant: "ML-DSA-65"
+ }
+);
+```
+
+Both signatures must verify. If either fails, `hybridVerify` throws `JwtError`.
+
+### Decode without verifying
+
+```ts
+import { decode } from "jsr:@netopwibby/pq-jwt";
+
+const { header, payload } = decode(token);
+```
+
+Use only for inspection. Never use the result for authorization.
+
+### Error handling
+
+```ts
+import { JwtError, verify } from "jsr:@netopwibby/pq-jwt";
+
+try {
+ await verify(token, publicKey);
+} catch (err) {
+ if (err instanceof JwtError) {
+ console.error(err.code, err.message);
+ // ERR_MALFORMED, ERR_TYPE, ERR_ALGORITHM,
+ // ERR_SIGNATURE, ERR_SIGNATURE_MLDSA, ERR_SIGNATURE_ED25519,
+ // ERR_EXPIRED, ERR_ISSUER, ERR_SUBJECT
+ }
+}
+```
+
+
+
+## API
+
+| Export | Description |
+| ------------------------------------------- | ------------------------------------------------ |
+| `generateKeyPair(variant?)` | Generate an ML-DSA keypair (default `ML-DSA-65`) |
+| `generateHybridKeyPair(variant?)` | Generate a hybrid ML-DSA + Ed25519 keypair |
+| `sign(payload, secretKey, options?)` | Sign a payload with ML-DSA |
+| `verify(token, publicKey, options?)` | Verify an ML-DSA JWT |
+| `hybridSign(payload, keyPair, options?)` | Sign with both ML-DSA and Ed25519 |
+| `hybridVerify(token, publicKeys, options?)` | Verify a hybrid JWT (both must pass) |
+| `decode(token)` | Decode header + payload without verifying |
+| `KEY_SIZES` | Raw byte sizes of keys per variant |
+| `JwtError` | Error thrown on any verification failure |
+
+
+
+## Key sizes (raw bytes)
+
+| Variant | ML-DSA public | ML-DSA secret | Ed25519 public | Ed25519 secret |
+| --------- | ------------- | ------------- | -------------- | -------------- |
+| ML-DSA-44 | 1312 | 2560 | 32 | 32 |
+| ML-DSA-65 | 1952 | 4032 | 32 | 32 |
+| ML-DSA-87 | 2592 | 4896 | 32 | 32 |
+
+ML-DSA signature lengths: `44 → 2420`, `65 → 3293`, `87 → 4595` bytes.
+
+
+
+## Hybrid signature layout
+
+```
+[0..3] uint32 big-endian ML-DSA signature length
+[4..4+mlDsaSigLen-1] ML-DSA signature bytes
+[4+mlDsaSigLen..] Ed25519 signature bytes (always 64)
+```
+
+The entire blob is Base64url-encoded as the JWT signature segment.
+
+
+
+## Run the example
+
+```sh
+deno run example.ts
+```
+
+
+
+## Security notes
+
+`@oqs/liboqs-js` is research/prototyping software and has not been formally audited. The hybrid mode provides defence-in-depth during the PQC transition. Follow [NIST PQC guidance](https://csrc.nist.gov/projects/post-quantum-cryptography) for production deployments.
+
+
+
+## License
+
+MIT