saqura
v0.1.0
Published
SaQura post-quantum cryptography SDK for JavaScript/TypeScript — byte-compatible with the .NET/Kotlin/Swift SDKs.
Maintainers
Readme
saqura
SaQura post-quantum cryptography for JavaScript / TypeScript — byte-compatible with the SaQura .NET, Kotlin, and Swift SDKs (same liboqs engine). Runs in Node and the browser (Web Crypto + WebAssembly).
v0.1 surface: Gen8 hybrid encryption (X25519 + ML-KEM); the full BSI conservative profile — Gen4 + Gen6 (FrodoKEM), Gen7 (RSA-4096 + FrodoKEM hybrid), Gen2 + Gen5 (Classic McEliece); ML-DSA / SLH-DSA signature verify (and sign); AES-256-GCM; SQS1 streaming (AES-256-GCM and ChaCha20-Poly1305); and the free-tier provenance watermark. See Not yet in v0.1 below.
Install
npm install saquraThe package ships an obfuscated bundle (ESM + CJS), TypeScript types, and the
liboqs WebAssembly engine. In bundlers/browsers, make sure wasm/liboqs.wasm
is served next to wasm/liboqs.mjs (or pass initLibOQS({ baseUrl })).
Quick start
import {
gen8GenerateKeyPair, gen8Encrypt, gen8Decrypt,
signatureGenerateKeyPair, signatureSign, signatureVerify,
streamEncrypt, streamDecrypt, createStreamEncryptor, createStreamDecryptor,
ApiLicense,
QuantumStrength, SignatureAlgorithm, StreamCipherSuite,
} from "saqura";
// Gen8 hybrid encryption (X25519 + ML-KEM-768 at Medium)
const kp = await gen8GenerateKeyPair(QuantumStrength.Medium);
const ct = await gen8Encrypt("hello", kp.publicKey);
const msg = await gen8Decrypt(ct.encapsulatedSecret, kp.privateKey, ct.encryptedMessage);
// Post-quantum signatures (ML-DSA / SLH-DSA)
const sk = await signatureGenerateKeyPair(QuantumStrength.Standard, SignatureAlgorithm.MLDsa);
const sig = await signatureSign(new TextEncoder().encode("data"), sk.privateKey);
const valid = await signatureVerify(new TextEncoder().encode("data"), sig, sk.publicKey);
// SQS1 streaming (AES-256-GCM or ChaCha20-Poly1305), 32-byte master key
const blob = await streamEncrypt(plaintext, key, { suite: StreamCipherSuite.AesGcm });
const back = await streamDecrypt(blob, key);
// …or constant-memory streaming I/O via Web Streams (no full buffer in memory):
await readable
.pipeThrough(createStreamEncryptor(key, { suite: StreamCipherSuite.ChaCha20Poly1305 }))
.pipeTo(writable);
// Forensic provenance of free-tier output (cross-platform verifiable)
const isSaQuraFreeTier = await ApiLicense.verifyFreeTierProvenance(output);Cross-platform / byte-compatibility
The crypto primitives are the same liboqs used by the native SDKs, and the
wire formats match byte-for-byte. Validated both directions against the shared
test corpus and the .NET reference (_test_corpus/{gen8_interop,streaming_interop}
Gen8InteropTest/StreamingFormatTestharnesses):
- Gen8 — JS decrypts .NET/Swift/Kotlin vectors (all strengths) and .NET decrypts JS output.
- Gen6 (FrodoKEM, BSI profile) — JS decrypts .NET FrodoKEM vectors and .NET decrypts JS output (all strengths).
- Gen7 (RSA-4096 + FrodoKEM hybrid, BSI profile) — both directions, all strengths (RSA-OAEP-SHA256 + FrodoKEM, XOR-combined).
- Gen2 / Gen5 (Classic McEliece, BSI profile) — both directions (AES-256-CBC
- HMAC-SHA256, encrypt-then-MAC). McEliece keygen is slow (~seconds) and keys are large (~1 MB) by design.
- ML-DSA / SLH-DSA — JS verifies .NET signatures and .NET verifies JS signatures (all parameter sets).
- SQS1 streaming (AES-256-GCM + ChaCha20-Poly1305) — both suites, both
directions: JS decrypts native (.NET/Kotlin/Swift) vectors across segment
boundaries and .NET decrypts JS output. ChaCha20 uses node:crypto in Node and
a bundled @noble/ciphers fallback in the browser (Web Crypto has no ChaCha20).
Available as a buffer API (
streamEncrypt/streamDecrypt) and a constant- memory Web Streams API (createStreamEncryptor/createStreamDecryptor,TransformStream); both produce identical, interoperable SQS1 bytes. - Watermark — the keyed provenance tag verifies across all SDKs.
Licensing
ApiLicense.activate(licenseJson) verifies a genuine server-signed .lic
(RSA-4096 PKCS#1 v1.5 / SHA-256) against the embedded public key and, on
success, sets the tier and features from the license — byte-compatible with the
.NET/Kotlin/Swift validators (a .lic issued for any SaQura SDK activates here
identically). It checks the signature (mandatory for every tier), the
issued/expires window, and revocation. Distribution licenses skip hardware /
activation checks; hardware binding is not enforced in JS (no stable browser/Node
hardware id).
const result = await ApiLicense.activate(licenseJson);
if (result.isValid && ApiLicense.isQuantumAvailable) { /* … */ }
ApiLicense.assertQuantumAvailable(); // opt-in: throws LicenseError if not Pro+Security model
Client-side tier gating (ApiLicense.isAESAvailable / isQuantumAvailable /
requireFeature / …) is advisory — JavaScript is readable even when
obfuscated. The crypto functions do not self-gate; requireFeature is opt-in.
Real entitlement enforcement is server-side (the SaQura Crypto-API + signed
license). The free-tier watermark is a tamper-evident forensic mark, not a
confidentiality control (its key ships in the bundle by design). See the SaQura
Kryptokonzept §7.1.
Build & test (maintainers)
npm install
npm run build:wasm # build liboqs 0.15 → wasm/ (needs Emscripten; one-time)
npm run build # tsup ESM+CJS+dts → obfuscate
npm run verify:obfuscation # hard gate: obfuscated, public API kept, internals renamed
npm run leak-scan # hard gate: no secrets / source / maps in the shipped set
node test/gen8.corpus.ts # byte-compat tests (also run via ci/gates/70-js.sh)Not yet in v0.1 (planned)
- Standalone RSA, PBKDF2, and the deprecated legacy gens (Gen1 / Gen3).
License
Proprietary — © KyotoTech. Distribution is the obfuscated bundle only; SDK source is not published (see RELEASE_PLAYBOOK).
