npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pixagram/pixa-vault

v1.0.4

Published

Post-quantum hardened secure vault for Pixagram — Argon2id + ChaCha20-Poly1305 + BLAKE3 in Rust/WASM. Drop-in replacement for PBKDF2-based key encryption, designed for short PINs.

Readme

@pixagram/pixa-vault

Post-quantum hardened secure vault for Pixagram — a blockchain-based Web3 social media platform built on a HIVE/STEEM fork.

All cryptography runs in Rust compiled to WebAssembly: Argon2id key derivation, ChaCha20-Poly1305 authenticated encryption, BLAKE3 hashing, and HKDF-SHA512 domain separation. Secret material is zeroized on drop — not left in the JS heap.

Built as a drop-in replacement for PBKDF2-SHA512 when your users authenticate with short PINs (6 characters).

Why not PBKDF2?

PBKDF2 is compute-bound. Every evaluation needs a few kilobytes of state, so a GPU can run hundreds of thousands of them in parallel. For long passwords this is acceptable. For 6-character PINs it is not:

| Metric | PBKDF2-SHA512 (1 M iter) | Argon2id (64 MiB, t=3) | |---|---|---| | Memory per attempt | ~1 KB | 64 MiB | | RTX 4090 throughput | ~500 000 PIN/s | ~375 PIN/s | | 6-char alphanumeric (2.2 B) | ~72 minutes | ~67 days | | 6-char numeric (1 M) | < 2 seconds | ~44 minutes | | ASIC resistance | None | Memory-hard |

Argon2id forces 64 MiB of allocation per evaluation. A 24 GB GPU maxes out at ~375 parallel lanes — a 1 300× throughput reduction for the same wall-clock cost to the user (~1 s).

Install

npm install @pixagram/pixa-vault

The package ships a pre-built WASM binary (pixa_vault_bg.wasm). No Rust toolchain needed at install time.

Building from source

# One-time setup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo install wasm-pack

# Build
npm run build          # Release (size-optimized WASM)
npm run build:dev      # Debug (faster compilation)
npm run test           # Rust unit tests

Quick start

import init from '@pixagram/pixa-vault/wasm';
import { initPQVault, PQSecureVault } from '@pixagram/pixa-vault';

// 1. Initialize WASM (once, at app startup)
await initPQVault(init);
const vault = new PQSecureVault();

// 2. Auto-tune Argon2 params for this device (optional, recommended)
const profile = await vault.autoTuneParams(1500);   // target ≤ 1.5 s
console.log(profile);  // { label: 'standard', memoryKib: 65536, iterations: 3, measuredMs: 1120 }

// 3. Generate a salt (once per vault — persist in IndexedDB)
const salt = vault.generateSalt();   // 64 hex chars

// 4. Seal blockchain keys with the user's PIN
const sealed = vault.sealKeys('abc123', salt, 'alice', {
    posting: '5JPostingKeyWIF...',
    active:  '5JActiveKeyWIF...',
    memo:    '5JMemoKeyWIF...',
});
// sealed is a JSON string — store it in IndexedDB

// 5. Later: unseal with the same PIN
const keys = vault.unsealKeys('abc123', salt, sealed);
console.log(keys.posting);  // '5JPostingKeyWIF...'

Architecture

PIN (6+ chars)
  │
  ▼
┌────────────────────────────────────────────┐
│  Argon2id  (m=64 MiB, t=3, p=1)           │  ◄── Memory-hard KDF (Rust/WASM)
└─────────────────────┬──────────────────────┘
                      │  master_key  (32 B, zeroized after use)
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
     HKDF-SHA512  HKDF-SHA512  HKDF-SHA512
     "encrypt"    "verify"     "session"
          │           │           │
          ▼           ▼           ▼
   ChaCha20-Poly1305  BLAKE3     Session key
   • Seal/unseal      • Safe to   • In-memory only
     private keys       store      • Ephemeral
   • AAD = account    • Cannot
     (tamper-proof)     derive key

HKDF purpose strings provide domain separation: the encryption key, verification hash, and session key are cryptographically independent even though they derive from the same master.

Stack comparison

| Layer | PixaProxyAPI v3.x | pixa-vault v4.0 | |---|---|---| | KDF | PBKDF2-SHA512 (1 M iter) | Argon2id (64 MiB, t=3) | | AEAD | AES-GCM (Web Crypto) | ChaCha20-Poly1305 (WASM) | | PIN verify | PBKDF2 + salt‖0x02 | BLAKE3 over HKDF sub-key | | Domain separation | Byte suffix | HKDF-SHA512 purpose strings | | Memory safety | JS heap (no zeroing) | zeroize on Rust drop | | AAD binding | None | Account name bound to ciphertext | | Vault storage | LacertaDB encrypted DB | Sealed JSON in regular IndexedDB |

API

Initialization

import init from '@pixagram/pixa-vault/wasm';
import { initPQVault, PQSecureVault } from '@pixagram/pixa-vault';

await initPQVault(init);

const vault = new PQSecureVault({
    memoryKib: 65536,    // Argon2 memory in KiB  (default: 64 MiB)
    iterations: 3,       // Argon2 time cost       (default: 3)
});

Salt generation

vault.generateSalt(byteLength?: number): string

Returns a hex-encoded CSPRNG salt. Default 32 bytes (64 hex chars). Generate once per vault and store alongside the sealed data.

Key derivation

vault.deriveKey(pin: string, salt: string): string
vault.deriveKeyAsArrayBuffer(pin: string, salt: string): ArrayBuffer

Full pipeline: PIN → Argon2id(salt) → HKDF("encrypt") → 256-bit key.

deriveKey returns hex; deriveKeyAsArrayBuffer returns an ArrayBuffer compatible with crypto.subtle.importKey(). Replaces pbkdf2Derive().

PIN verification

vault.generateVerifyHash(pin: string, salt: string): string
vault.verifyPin(pin: string, salt: string, storedHash: string): boolean

generateVerifyHash returns a hex-encoded BLAKE3 digest derived through a separate HKDF branch. It is safe to store in plaintext — it cannot be reversed to obtain the encryption key.

verifyPin re-derives the hash and performs a constant-time comparison. Timing is dominated by Argon2id (~1 s), making side-channel attacks on the comparison irrelevant.

Replaces _derivePinVerifyHash().

Low-level encrypt / decrypt

vault.encrypt(keyHex: string, plaintext: string, aad?: string): string
vault.decrypt(keyHex: string, ciphertextB64: string, aad?: string): string

ChaCha20-Poly1305 AEAD. The optional aad (Additional Authenticated Data) binds the ciphertext to a context — decrypting with a different aad throws. Output is base64-encoded nonce(12) ‖ ciphertext ‖ tag(16).

Seal / unseal secrets

vault.sealSecret(pin, salt, account, plaintext): SealedRecord
vault.unsealSecret(pin, salt, sealedRecord): string

vault.sealKeys(pin, salt, account, keys): string
vault.unsealKeys(pin, salt, sealedJson): Record<string, string>

High-level one-shot API. Each call runs the full Argon2id pipeline internally.

sealKeys / unsealKeys handle a { posting, active, memo, owner } key bundle with per-type AAD binding (account:type). The sealed JSON string is ready for IndexedDB storage.

Replaces lacerta.getSecureDatabase() + vaultMaster.add() / vaultMaster.get().

Session management

vault.unlockSession(pin: string, salt: string): string
vault.isUnlocked(): boolean
vault.sessionEncrypt(plaintext: string, aad?: string): string
vault.sessionDecrypt(ciphertextB64: string, aad?: string): string
vault.lock(): void

unlockSession derives the encryption key once and caches it in memory for fast repeated operations (avoids re-running Argon2id on every read). lock zero-fills and discards the cached key.

Auto-tuning

vault.autoTuneParams(targetMs?: number): Promise<TuneResult>

Benchmarks Argon2id on the current device and selects the largest memory profile that completes within targetMs (default: 1500 ms). Profiles tested in order:

| Profile | Memory | Iterations | |---|---|---| | standard | 64 MiB | 3 | | medium | 32 MiB | 3 | | low | 16 MiB | 4 |

Mutates vault.memoryKib and vault.iterations. Call during app initialization.

Utilities

vault.getInfo(): VaultInfo     // Version, algorithm identifiers, default params
vault.blake3(data: string): string   // Quick BLAKE3 hex digest

TypeScript

Full type declarations ship with the package (pq-secure-vault.d.ts). Key types:

import type { SealedRecord, VaultInfo, TuneResult, PQSecureVaultOptions } from '@pixagram/pixa-vault';

Migration from PixaProxyAPI v3.x

The upgrade is designed to be automatic and transparent. A detailed migration module is included:

import { migratePBKDF2ToPQ } from '@pixagram/pixa-vault/migration';

What changes in pixaproxyapi.js

| v3.x code | v4.0 replacement | |---|---| | CONFIG.PBKDF2_ITERATIONS | CONFIG.ARGON2_MEMORY_KIB + CONFIG.ARGON2_ITERATIONS | | pbkdf2Derive(pin, salt, iter, len) | vault.deriveKey(pin, salt) | | _derivePinVerifyHash(pin, salt) | vault.generateVerifyHash(pin, salt) | | lacerta.getSecureDatabase(name, pin, salt, opts) | vault.sealKeys(pin, salt, account, keys) | | vaultMaster.get(account) | vault.unsealKeys(pin, salt, sealedJson) | | vaultMaster.add(doc) | Store vault.sealKeys() output in plain IndexedDB |

Migration flow

On first initializeVault(pin) after upgrade:

  1. Detects old vault_config collection (PBKDF2 salt) in settingsDb.
  2. Opens old LacertaDB encrypted vault with PBKDF2 using the entered PIN.
  3. Reads all master and individual keys from the old vault.
  4. Re-seals every key with Argon2id + ChaCha20-Poly1305 into new sealed_keys collection.
  5. Generates a new BLAKE3 verification hash in pq_vault_config.
  6. Marks the old vault as migrated (preserved for disaster recovery — never deleted).

All subsequent operations use the PQ vault exclusively. See js/migration-guide.js for the complete integration diff with replacement code for initializeVault, unlockWithPin, _derivePinVerifyHash, and hasVaultConfig.

New IndexedDB collections

| Collection | Purpose | |---|---| | pq_vault_config | Salt, BLAKE3 verify hash, format version | | sealed_keys | ChaCha20-Poly1305 sealed key blobs |

The old vault_config and encrypted pixa_vault collections are preserved indefinitely.

Security properties

| Property | Guarantee | |---|---| | Brute-force resistance | 64 MiB memory per attempt limits GPU parallelism | | Side-channel safety | Constant-time AEAD (ChaCha20) and constant-time hash comparison | | Domain separation | HKDF purpose strings prevent key reuse across roles | | Tamper detection | Poly1305 MAC authenticates all ciphertext | | Cross-account binding | Account name as AAD — keys can't be replayed between users | | Cross-type binding | account:type AAD — posting key can't masquerade as active | | Memory safety | Rust zeroize crate — all key material zeroed on drop | | Forward secrecy | Session keys are ephemeral, not derived from PIN | | No AES-NI dependency | ChaCha20 performs identically on all WASM hosts |

Post-quantum considerations

This vault does not use lattice-based schemes (ML-KEM, ML-DSA) because:

  1. The vault is purely symmetric — no public-key cryptography to attack with Shor's algorithm.
  2. Argon2id's security is dominated by memory cost, not computational cost. Grover's quadratic speedup is irrelevant when each evaluation requires 64 MiB.
  3. 256-bit ChaCha20 provides 128-bit post-quantum security (Grover's halving), which is sufficient.
  4. Lattice schemes would add ~2 MB to the WASM binary for no practical security gain in this context.

Threat model

The vault protects against:

  • Offline brute-force of a stolen IndexedDB dump (Argon2id memory cost).
  • GPU/ASIC farms attempting to crack short PINs (memory-hardness).
  • Cross-account replay where sealed data from one account is injected into another (AAD binding).
  • Key-type confusion where a posting-key ciphertext is substituted for an active key (per-type AAD).
  • JS heap inspection by browser extensions or devtools (keys zeroized in Rust, session cache AES-GCM encrypted in JS).
  • Timing attacks on PIN verification (constant-time BLAKE3 compare, dominated by ~1 s Argon2id).

It does not protect against:

  • A compromised host page (XSS with full DOM access can intercept the PIN at entry time).
  • Physical access to an unlocked session (session keys are in memory by design).
  • Quantum computers breaking the entropy of a 6-character PIN itself (expand to 8+ characters for long-term PQ security).

Project structure

@pixagram/pixa-vault/
├── Cargo.toml                 # Rust crate config
├── package.json               # npm package config
├── build.sh                   # Build script (wasm-pack)
├── src/                       # Rust source
│   ├── lib.rs                 # WASM exports (12 functions)
│   ├── kdf.rs                 # Argon2id + HKDF derivation
│   ├── cipher.rs              # ChaCha20-Poly1305 AEAD
│   ├── vault.rs               # Seal / unseal operations
│   ├── verify.rs              # BLAKE3 PIN verification
│   └── error.rs               # Error types
├── js/
│   └── migration-guide.js     # v3 → v4 migration code
└── pkg/                       # Build output (wasm-pack + JS wrapper)
    ├── pixa_vault.js           # WASM-bindgen glue
    ├── pixa_vault_bg.wasm      # WASM binary (~180 KB gzipped)
    ├── pixa_vault.d.ts         # WASM TypeScript types
    ├── pq-secure-vault.js      # High-level JS wrapper
    └── pq-secure-vault.d.ts    # JS wrapper TypeScript types

Related packages

| Package | Role | |---|---| | @pixagram/dpixa | Blockchain client SDK (PrivateKey, Client, broadcast) | | @pixagram/lacerta-db | Browser database (IndexedDB, collections, queries) | | @pixagram/renderart | WebGL/WASM pixel art rendering (CRT, hexagon, xBRZ) |

Acknowledgements

Cryptographic architecture inspired by anubis-vault (Argon2id + ChaCha20-Poly1305 + BLAKE3 + zeroize), adapted for a browser-WASM environment with short-PIN constraints.

License

MIT — Pixagram SA, Zug, Switzerland