sx-blackbox
v1.0.0-beta.1
Published
Quantum-safe encryption for JavaScript/TypeScript. One-line API over audited primitives: XChaCha20-Poly1305, Argon2id, ML-KEM-768.
Maintainers
Readme
sx-blackbox
Quantum-safe encryption for JavaScript and TypeScript. One-line API over audited cryptographic primitives.
sx-blackbox is a thin, opinionated facade over the @noble/* family of audited libraries. We never roll our own cryptography.
| Layer | Primitive | Source |
|---|---|---|
| Symmetric AEAD | XChaCha20-Poly1305 | @noble/ciphers |
| Password-based KDF | Argon2id | @noble/hashes |
| Post-quantum KEM | ML-KEM-768 (NIST FIPS 203) | @noble/post-quantum |
| KEM key derivation | SHA3-256 | @noble/hashes |
Tamper detection is a property of the AEAD construction — any single-bit modification to ciphertext, nonce, or salt causes decryption to throw SXBlackboxError.
Install
npm install sx-blackboxWorks in Node.js 18+, modern browsers, Cloudflare Workers, Deno, Bun, React Native, Electron.
Quick start
import { SXBlackbox } from "sx-blackbox";
const box = new SXBlackbox();
// Symmetric (password-based, AEAD)
const ct = await box.encrypt("my-api-key", "password");
const pt = await box.decrypt(ct, "password");
// Object encryption (preserves Date and BigInt)
const blob = await box.encryptObject({ id: 1, when: new Date() }, "pw");
const obj = await box.decryptObject<{ id: number; when: Date }>(blob, "pw");
// Post-quantum public-key encryption (ML-KEM-768)
const { publicKey, privateKey } = box.generateKeyPair();
const sealed = box.encryptAsymmetric("for bob's eyes only", publicKey);
const opened = box.decryptAsymmetric(sealed, privateKey);
// Streaming for large payloads
const cipherBytes = await box.encryptStream(largeBytes, "pw");
const plainBytes = await box.decryptStream(cipherBytes, "pw");
// Key wrapping
const wrapped = await box.exportKey(privateKey, "wrap-pw");
const restored = await box.importKey(wrapped, "wrap-pw");
// Key rotation (re-encrypts under a new password)
const rotated = await box.rotateKey(ct, "old-pw", "new-pw");API surface
new SXBlackbox({ memoryHardness: "low" | "medium" | "high" })
// Symmetric
encrypt(data: string, password: string) : Promise<string>
decrypt(blob: string, password: string) : Promise<string>
encryptBytes(data: Uint8Array, password: string) : Promise<string>
decryptBytes(blob: string, password: string) : Promise<Uint8Array>
// Object
encryptObject<T>(obj: T, password: string) : Promise<string>
decryptObject<T>(blob: string, password: string) : Promise<T>
// Streaming
encryptStream(bytes: Uint8Array, password, chunk?) : Promise<Uint8Array>
decryptStream(bytes: Uint8Array, password) : Promise<Uint8Array>
// Asymmetric (post-quantum)
generateKeyPair() : { publicKey, privateKey }
encryptAsymmetric(data: string, publicKey: string) : string
decryptAsymmetric(blob: string, privateKey: string): string
// Key management
rotateKey(blob, oldPw, newPw) : Promise<string>
exportKey(keyB64u, password) : Promise<string>
importKey(blob, password) : Promise<string>
deriveKey(password, salt, opts?) : Promise<Uint8Array>
// Inspection
isTampered(blob, password) : Promise<boolean>
getMetadata(blob) : { version, algorithm, kdf }
validatePassword(password) : { score: 0-4, suggestions }All functions are also exported standalone for tree-shaking:
import { encrypt, decrypt, generateKeyPair } from "sx-blackbox";Wire format
Symmetric: sx1.xc20p.argon2id-<t>-<m>.<salt_b64u>.<nonce_b64u>.<ct+tag_b64u>
Asymmetric: sx1k.<kemCt_b64u>.<nonce_b64u>.<ct+tag_b64u>
Streaming: binary "SX1S" header + length-prefixed AEAD chunksThe format is versioned — sx1 blobs will continue to decrypt after future format additions.
Error handling
import { SXBlackboxError } from "sx-blackbox";
try {
await box.decrypt(blob, "guess");
} catch (e) {
if (e instanceof SXBlackboxError) {
console.log(e.code);
// "INVALID_PASSWORD" | "TAMPERED_DATA" | "INVALID_FORMAT"
// | "UNSUPPORTED_VERSION" | "INVALID_KEY" | "WEAK_PASSWORD"
}
}Honest scope
What is verified by the test suite (83 tests):
- Symmetric AEAD round-trip + tamper detection on every blob segment
- Argon2id determinism, parameter validation, three cost profiles
- ML-KEM-768 keygen/encapsulate/decapsulate + implicit rejection on wrong key
- Streaming with chunk-bound nonces, truncation detection, append detection
- Object round-trip preserving
DateandBigInt, circular-structure rejection - Key rotation, key wrapping (export/import), every error code path
- Unicode plaintext + unicode passwords, all 256 byte values
What this library does NOT do:
- It has not undergone a third-party security audit. Use the underlying
@noble/*libraries directly if you need that today. - No path-based file APIs (
encryptFile("./x")) — they don't work in browsers or Workers. UseencryptStream(bytes, ...)instead and pair with your platform's file APIs. - Argon2id is not itself quantum-resistant; quantum safety here comes from ML-KEM-768.
- Doesn't protect against compromised passwords or memory dumps.
License
MIT
