# Post-Quantum Cryptography in TypeScript [![npm](https://img.shields.io/npm/v/@darkbio/crypto)](https://www.npmjs.com/package/@darkbio/crypto) [![tests](https://github.com/dark-bio/crypto-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/dark-bio/crypto-ts/actions/workflows/ci.yml) [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](https://github.com/dark-bio/crypto-ts/blob/main/LICENSE) This repository is parameter selection and lightweight wrapper around a number of (WASM wrapped) Rust cryptographic libraries. Its purpose isn't to implement primitives, rather to unify the API surface of existing libraries; limited to the tiny subset needed by the Dark Bio project. The library is opinionated. Parameters and primitives were selected to provide matching levels of security in a post-quantum world. APIs were designed to make the library easy to use and hard to misuse. Flexibility will always be rejected in favor of safety. ![](docs/overview.png) - Digital signatures - **xDSA ([RFC-DRAFT](https://datatracker.ietf.org/doc/html/draft-ietf-lamps-pq-composite-sigs))**: `MLDSA`, `EdDSA`, `SHA512` - **EdDSA ([RFC-8032](https://datatracker.ietf.org/doc/html/rfc8032))**: `Ed25519` - **MLDSA ([RFC-9881](https://datatracker.ietf.org/doc/html/rfc9881))**: Security level 3 (`ML-DSA-65`) - **RSA¹ ([RFC-8017](https://datatracker.ietf.org/doc/html/rfc8017))**: 2048-bit, `SHA256` - Encryption - **xHPKE ([RFC-9180](https://datatracker.ietf.org/doc/html/rfc9180))**: `X-WING`, `HKDF`, `SHA256`, `ChaCha20`, `Poly1305`, `dark-bio-v1:` domain prefix - **X-WING ([RFC-DRAFT](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem))**: `MLKEM`, `ECC` - **ECC ([RFC-7748](https://datatracker.ietf.org/doc/html/rfc7748))**: `X25519` - **MLKEM ([FIPS-203](https://csrc.nist.gov/pubs/fips/203/final))**: Security level 3 (`ML-KEM-768`) - **STREAM (*RFC N/A*, [Rage](https://github.com/str4d/rage))**: `ChaCha20`, `Poly1305`, `16B` tag, `64KB` chunk - Key derivation - **Argon2 ([RFC-9106](https://datatracker.ietf.org/doc/html/rfc9106))**: `id` variant - **HKDF ([RFC-5869](https://datatracker.ietf.org/doc/html/rfc5869))**: `SHA256` - Serialization - **CBOR² ([RFC-8949](https://datatracker.ietf.org/doc/html/rfc8949))**: restricted to `bool`,`null`, `integer`, `text`, `bytes`, `array`, `map[int]`, `option` - **COSE ([RFC-9052](https://datatracker.ietf.org/doc/html/rfc9052))**: `COSE_Sign1`, `COSE_Encrypt0`, `dark-bio-v1:` domain prefix - Credential / Attestation - **CWT ([RFC-8392](https://datatracker.ietf.org/doc/html/rfc8392))**: `xDSA`, `xHPKE` - **EAT ([RFC-9711](https://datatracker.ietf.org/doc/html/rfc9711))** *¹ Whilst RSA is used in the Dark Bio project for secure boot signatures on pre-quantum hardware, there was no reason to expose that in the TypeScript wrappers. It's available in Rust so anyone needing it can expose it easily themselves.* *² CBOR encoding and decoding is delegated to [cborg](https://github.com/rvagg/cborg), a full reimplementation in TypeScript being out of scope. To ensure correctness, this package provides a type system. `cbor` codecs declare the shape of a value the way a Rust type deriving `Cbor` does, `cose` and `cwt` functions take values bound to their codec, and the canonical form of every byte crossing the WASM boundary is checked by the Rust validator, which `cbor.verify` also exposes.* ## Quick start Signatures come from `xdsa`, encryption from `xhpke`, and `cose` wraps both into COSE envelopes using the Dark Bio wire profile. Payloads and authenticated messages are bound to a `cbor` codec, which declares their shape. ```sh npm install @darkbio/crypto ``` COSE signing and verification and xHPKE encryption and decryption use an application domain that both sides must agree on. It is prefixed with `dark-bio-v1:` internally and binds the operation to one purpose. Choose distinct domains for distinct purposes. Raw `xdsa` signatures carry no such application domain, which is why the `cose` envelopes are the recommended entry point. ```ts import { cbor, cose, xdsa, xhpke } from "@darkbio/crypto"; // Long term identities, one for signing and one for receiving const signer = await xdsa.SecretKey.generate(); const recipient = await xhpke.SecretKey.generate(); const domain = new TextEncoder().encode("example"); // A detached signature over a message that travels separately const signature = await cose.signDetached(cbor.text.value("payload"), signer, domain); await cose.verifyDetached(signature, cbor.text.value("payload"), signer.publicKey(), domain, 60); // Sign and encrypt a payload to the recipient, then open and verify it back. // The second argument is authenticated but must be supplied separately. const metadata = cbor.text.value("metadata"); const padding = cose.Padding.buckets({ floor: 8192, step: 20 }); const sealed = await cose.seal(cbor.text.value("payload"), metadata, signer, recipient.publicKey(), domain, padding); const opened = await cose.open(cbor.text.bytes(sealed), metadata, recipient, signer.publicKey(), domain, 60); ``` `cose.seal` and `cose.encrypt` take a `cose.Padding` policy after the domain. `cose.Padding.none()` adds no padding. Buckets pad the signed envelope with zeros inside the encryption, to the smallest of a series of sizes that starts at `floor` bytes and grows by `1/step` of each, rounded up. The example's first sizes are 8,192, 8,602 and 9,033 bytes. Both bucket parameters must be integers from 1 to 4,294,967,295, or `cose.Padding.buckets` throws. `cose.open` and `cose.decrypt` need no policy. They strip any number of zero bytes after the signed envelope and refuse a nonzero byte. ## Native packages The underlying implementation exists in two sibling repos, which track the same feature set and API surfaces, released at corresponding version points. - Rust [`github.com/dark-bio/crypto-rs`](https://github.com/dark-bio/crypto-rs) - Go [`github.com/dark-bio/crypto-go`](https://github.com/dark-bio/crypto-go) Sibling wrapper exists in one other repo: - Flutter [`github.com/dark-bio/crypto-fl`](https://github.com/dark-bio/crypto-fl) ## Acknowledgements Shoutout to Filippo Valsorda ([@filosottile](https://github.com/filosottile)) for lots of tips and nudges on what kind of cryptographic primitives to use and how to combine them properly; and also for his work in general on cryptography standards. Naturally, many thanks to the authors of all the libraries this project depends on. ## License This library is licensed under the [BSD 3-Clause License](https://github.com/dark-bio/crypto-ts/blob/main/LICENSE).