This document describes security considerations for using qrypto.js, including JavaScript-specific limitations and best practices.
qrypto.js implements post-quantum digital signature algorithms (Dilithium5 and ML-DSA-87). While the cryptographic algorithms are secure, JavaScript/Node.js environments have inherent limitations that affect how secret key material can be handled.
JavaScript does not provide:
- Direct memory access - Cannot guarantee memory is overwritten at a specific location
- Compiler barrier semantics - JIT compilers may optimize away "dead store" writes
- Control over garbage collection - Memory may persist until GC runs (non-deterministic)
- Protection against memory swapping - Secrets may be written to disk swap space
Even with Uint8Array.fill(0):
- The operation may be optimized away by the JIT compiler if the array is not used afterward
- Previous values may remain in memory until the garbage collector reclaims the buffer
- The JavaScript runtime may have copied the data internally (e.g., during Buffer operations)
Buffer.alloc()initializes memory to zero but doesn't prevent later exposureBuffer.allocUnsafe()may contain previous memory contents- Buffer pooling may cause memory reuse across different operations
Converting secret key data to/from strings creates copies that:
- Cannot be overwritten (strings are immutable in JavaScript)
- May be interned or cached by the runtime
- Persist until garbage collected
Despite these limitations, qrypto.js implements defense-in-depth measures:
All secret key material is stored in Uint8Array buffers, not strings. This allows:
- Explicit zeroing (even if not guaranteed to be secure)
- Avoiding string interning
- Clearer intent about data sensitivity
Signature verification uses constant-time comparison to prevent timing attacks:
// From cryptoSignVerify:
let diff = 0;
for (i = 0; i < length; ++i) {
diff |= a[i] ^ b[i];
}
return diff === 0;The Montgomery reduction and other arithmetic operations use JavaScript's BigInt type. Important: The JavaScript specification does not guarantee that BigInt operations are constant-time. The execution time of operations like multiplication and division may vary based on operand values.
Implications:
- Signing operations that use these arithmetic functions may have timing variations
- This is a known limitation of JavaScript cryptographic implementations
- Signature verification uses constant-time comparison (see above), which is the critical path for timing attacks
Mitigations for sensitive deployments:
- For applications with strict constant-time requirements, consider using the Go implementation (go-qrllib) which provides better timing guarantees
- Rate-limit signature operations at the application layer to reduce timing attack feasibility
- Run signing operations in isolated environments where timing cannot be observed
All cryptographic functions validate input lengths and types to prevent:
- Buffer overflow/underflow issues
- Type confusion attacks
- Invalid parameter combinations
The zeroize() function is provided for clearing sensitive buffers:
import { zeroize } from '@aspect-build/qrypto-common';
const sk = new Uint8Array(CryptoSecretKeyBytes);
cryptoSignKeypair(seed, pk, sk);
// Use the secret key...
const signature = cryptoSign(message, sk);
// Clear when done (best effort)
zeroize(sk);Important: Due to JavaScript limitations, this is a best-effort operation. There is no guarantee that:
- The memory is actually zeroed (JIT optimization)
- Copies don't exist elsewhere (GC, Buffer pooling)
- The data wasn't swapped to disk
- Minimize secret lifetime - Generate keys only when needed, zero them as soon as possible
- Avoid serialization - Don't convert secrets to strings, JSON, or other formats
- Don't log secrets - Never log, print, or transmit secret key material
- Use secure storage - For persistent keys, consider:
- Hardware Security Modules (HSMs)
- Operating system keychains
- Encrypted storage with proper key management
- Consider WebCrypto - For browser environments, WebCrypto provides non-extractable keys
If your threat model requires strong memory protection:
- Use native implementations - Consider go-qrllib or C implementations that provide better memory control
- Use HSMs - Hardware Security Modules provide the strongest protection
- Isolate processes - Run cryptographic operations in isolated processes/containers
- Disable swap - On systems handling secrets, consider disabling swap or using encrypted swap
- NIST PQC Round 3 finalist
- Security level: Category 5 (equivalent to AES-256)
- Key sizes: PK=2592, SK=4896, Sig=4595 bytes
- Cross-verified against pq-crystals reference (
ac743d5)
- NIST FIPS 204 standardized algorithm
- Security level: Category 5 (equivalent to AES-256)
- Key sizes: PK=2592, SK=4896, Sig=4627 bytes
- Includes context parameter for domain separation
- Cross-verified against pq-crystals reference (latest)
If you discover a security vulnerability in qrypto.js:
- Do not open a public GitHub issue
- Contact the QRL security team privately
- Provide detailed reproduction steps
- Allow reasonable time for a fix before public disclosure
All npm packages are published with npm provenance, which cryptographically links published packages to their source repository and build workflow.
Verify provenance on npm:
npm audit signaturesAll releases include GitHub attestations backed by Sigstore:
- Build provenance for checksums and package files
- SBOM attestations in SPDX and CycloneDX formats
- SLSA Level 3 provenance for build verification
Each release includes Software Bill of Materials (SBOM) files:
sbom-spdx.json- SPDX formatsbom-cyclonedx.json- CycloneDX format
All releases include cryptographic attestations and checksums for verification.
# Verify attestations for package files
gh attestation verify package.json --owner theQRL
gh attestation verify package-lock.json --owner theQRL
# Verify SBOM attestation
gh attestation verify sbom-spdx.json --owner theQRLDownload and verify checksums from the release:
# Download checksums file
curl -LO https://github.com/theQRL/qrypto.js/releases/download/vX.Y.Z/checksums-sha256.txt
# Verify package files
sha256sum -c checksums-sha256.txt# Install slsa-verifier: https://github.com/slsa-framework/slsa-verifier#installation
# Download provenance
curl -LO https://github.com/theQRL/qrypto.js/releases/download/vX.Y.Z/provenance.intoto.jsonl
# Verify provenance
slsa-verifier verify-artifact package.json \
--provenance-path provenance.intoto.jsonl \
--source-uri github.com/theQRL/qrypto.jsEach release includes SBOMs in two formats:
- SPDX:
sbom-spdx.json - CycloneDX:
sbom-cyclonedx.json
These can be analyzed with tools like:
# Using grype for vulnerability scanning
grype sbom:sbom-spdx.json
# Using syft for inspection
syft convert sbom-cyclonedx.json -o table| Artifact | Attestation Type | Purpose |
|---|---|---|
package.json, package-lock.json |
Build provenance | Verify package dependencies |
checksums-sha256.txt |
Build provenance | Integrity verification |
sbom-spdx.json |
SBOM | Software composition |
sbom-cyclonedx.json |
SBOM | Software composition |
| Source code | SLSA provenance | Build reproducibility |
| npm package | npm provenance | Package authenticity |
Attestations are signed using GitHub's Sigstore integration:
- Identity: GitHub Actions OIDC token
- Transparency: Logged in Sigstore's Rekor transparency log
- Verification: Proves release came from official CI workflow