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

@cryptforge/auth

v0.2.1

Published

Browser-compatible authentication and key management for cryptocurrency wallets

Readme

@cryptforge/auth

Browser-compatible authentication and key management for cryptocurrency wallets. Built on industry-standard BIP39/BIP44 with secure keystore encryption.

Features

  • 🔐 Secure Key Management - BIP39 mnemonics encrypted with PBKDF2 + AES-256-CBC
  • 🌐 Browser-First - Zero configuration, works everywhere (browser, Node.js, Electron, React Native)
  • 🔗 Multi-Blockchain - Pluggable adapter system for any blockchain
  • 👤 Multi-Identity - Manage multiple wallets with separate keystores
  • 🔒 Auto-Lock - Configurable session timeouts for security
  • ⏱️ Key Expiration - Automatic tracking with live countdowns
  • 💾 IndexedDB Storage - Encrypted keystores persisted locally
  • 🎯 Type-Safe - Full TypeScript support
  • 📦 Lightweight - ~26 KB minified

Installation

npm install @cryptforge/auth @cryptforge/blockchain-evm @cryptforge/blockchain-btc

Quick Start

import { createAuthClient } from "@cryptforge/auth";
import { EVMAdapter } from "@cryptforge/blockchain-evm";
import { BitcoinAdapter } from "@cryptforge/blockchain-btc";

// Create auth client
const auth = createAuthClient();

// Register blockchain adapters
auth.registerAdapter(
  "ethereum",
  new EVMAdapter({
    chainData: { name: "Ethereum", symbol: "ETH", cmc_id: 1027 },
    coinType: 60,
  })
);

auth.registerAdapter("bitcoin", new BitcoinAdapter());

// Generate mnemonic
const mnemonic = auth.generateMnemonic({ wordCount: 12 });

// Create identity
const { identity, keys } = await auth.createIdentity({
  mnemonic,
  password: "secure-password",
  label: "Personal Wallet",
  chainId: "ethereum",
});

console.log("Address:", keys.address);

Usage Guide

First Time Setup

1. Generate and Backup Mnemonic

// Generate a new mnemonic
const mnemonic = auth.generateMnemonic({ wordCount: 12 });
// Example: "abandon ability able about above absent absorb abstract absurd abuse access accident"

// ⚠️ CRITICAL: Display to user and require backup confirmation
// User must write down or securely store the mnemonic

2. Create Identity

const { identity, keys } = await auth.createIdentity({
  mnemonic: mnemonic,
  password: "user-chosen-password",
  label: "Personal Wallet",
  metadata: {
    createdBy: "MyApp",
    version: "1.0.0",
  },
  chainId: "ethereum", // Optional: unlock with specific chain
});

// Identity created and encrypted in IndexedDB
// If chainId provided, wallet is unlocked and ready to use

Returning User Flow

1. List Available Identities

const identities = await auth.listIdentities();
// [
//   {
//     id: 'identity_A1B2C3D4',
//     label: 'Personal Wallet',
//     fingerprint: 'A1B2C3D4',
//     createdAt: Date,
//     lastAccess: Date
//   }
// ]

2. Select and Unlock

// Select an identity
await auth.switchIdentity(identities[0].id);

// Unlock with password
const { keys } = await auth.unlock({
  password: "user-chosen-password",
  chainId: "ethereum",
  duration: 10 * 60 * 1000, // Auto-lock after 10 minutes
});

// Wallet is now unlocked and ready to sign
console.log("Address:", keys.address);
console.log("Expires in:", auth.currentExpiresIn, "seconds");

Core Features

Identity Management

Create Identity

const { identity, keys } = await auth.createIdentity({
  mnemonic: "word1 word2 ... word12",
  password: "secure-password",
  label: "Trading Wallet",
  metadata: { purpose: "trading" },
  chainId: "ethereum", // Optional
});

Import Identity

// From mnemonic backup
const { identity } = await auth.importIdentity(
  "word1 word2 ... word12",
  "new-password",
  "mnemonic"
);

// From keystore JSON
const { identity } = await auth.importIdentity(
  keystoreJsonString,
  "password",
  "keystore"
);

Export Identity

// Export as mnemonic (for backup)
const { data: mnemonic } = await auth.exportIdentity(identity.id, {
  password: "password",
  format: "mnemonic",
});

// Export as keystore JSON
const { data: keystore } = await auth.exportIdentity(identity.id, {
  password: "password",
  format: "keystore",
});

Delete Identity

// Permanently delete (requires password)
await auth.deleteIdentity(identity.id, "password");
// ⚠️ Make sure mnemonic is backed up first!

Update Identity

await auth.updateIdentity(identity.id, {
  label: "Updated Wallet Name",
  metadata: { theme: "dark" },
});

Change Password

await auth.changePassword(identity.id, "old-password", "new-password");

Session Management

Unlock

const { keys } = await auth.unlock({
  password: "password",
  chainId: "ethereum",
  duration: 15 * 60 * 1000, // Optional: auto-lock after 15 min
});

// Access derived keys
console.log("Address:", keys.address);
console.log("Public Key:", keys.publicKeyHex);
console.log("Derivation Path:", keys.derivationPath);
console.log("Expires At:", keys.expiresAt);

Lock

await auth.lock();
// Clears keys from memory
// Keystore remains encrypted in IndexedDB

Blockchain Operations

Switch Chain

// Switch to different blockchain (if already unlocked)
await auth.switchChain("bitcoin");

// Switch when locked (requires password)
await auth.switchChain("bitcoin", "password");

Get Addresses

// Get multiple addresses for a chain
const addresses = await auth.getAddresses("ethereum", 0, 5);
// [
//   { address: '0x...', path: "m/44'/60'/0'/0/0", index: 0 },
//   { address: '0x...', path: "m/44'/60'/0'/0/1", index: 1 },
//   ...
// ]

// Get specific address by index
const { address, publicKey, derivationPath } = await auth.getAddressForChain(
  "bitcoin",
  0
);

Find Used Addresses (Account Discovery)

// Find all addresses with balance
const usedAddresses = await auth.findUsedAddresses(
  "ethereum",
  async (address) => {
    // Your balance checking logic
    const balance = await checkBalance(address);
    return balance > 0;
  }
);
// Scans with BIP44 gap limit of 20

Cryptographic Operations

Sign Message

const { signature, address, publicKey } = await auth.signMessage({
  message: "Hello CryptForge!",
});

// Sign with different derivation path
const result = await auth.signMessage({
  message: "Custom path",
  derivationPath: "m/44'/60'/0'/0/1",
});

Sign Transaction

const { signedTransaction, signature } = await auth.signTransaction({
  transaction: {
    to: "0x...",
    value: "1000000000000000000", // 1 ETH in wei
    gasLimit: 21000,
  },
});

// Sign with different key
const result = await auth.signTransaction({
  transaction: tx,
  derivationPath: "m/44'/60'/0'/0/5",
});

Verify Signature

const isValid = await auth.verifySignature(
  "Hello CryptForge!",
  signature,
  publicKey
);

Key Management

Rotate Keys

// Rotate to next address index
const { keys } = await auth.rotateKeys();
console.log("New address:", keys.address);

// Rotate to custom derivation path
const { keys } = await auth.rotateKeys("m/44'/60'/0'/0/5");

Derive Custom Key

// One-time key derivation (not stored in session)
const { privateKey, publicKey, address, path } = await auth.deriveKey({
  path: "m/44'/60'/1'/0/0", // Different account
});

State Management

Getters

// Identity state
auth.currentIdentity; // Identity | null
auth.hasIdentity; // boolean

// Keys state
auth.currentKeys; // Keys | null
auth.currentAddress; // string | null
auth.currentPublicKey; // string | null (hex)

// Chain state
auth.currentChain; // Chain | null

// Lock state
auth.isLocked; // boolean
auth.isUnlocked; // boolean

// Expiration state
auth.currentExpiresAt; // Date | null
auth.currentExpiresIn; // number | null (seconds remaining)

// Available chains
auth.getRegisteredChains(); // string[]

Event Subscription

const unsubscribe = auth.onAuthStateChange((event, keys) => {
  console.log("Event:", event);

  switch (event) {
    case "IDENTITY_CREATED":
      console.log("New identity created");
      break;
    case "UNLOCKED":
      console.log("Wallet unlocked:", keys?.address);
      break;
    case "LOCKED":
      console.log("Wallet locked");
      break;
    case "CHAIN_SWITCHED":
      console.log("Switched to:", keys?.chain.name);
      break;
    case "KEYS_ROTATED":
      console.log("Keys rotated to:", keys?.address);
      break;
    case "KEYS_EXPIRED":
      console.log("Keys expired, please unlock again");
      break;
    // ... other events
  }
});

// Unsubscribe when done
unsubscribe();

Available Events

  • IDENTITY_CREATED - New identity created
  • IDENTITY_RESTORED - Identity imported/restored
  • IDENTITY_SWITCHED - Switched to different identity
  • IDENTITY_UPDATED - Identity metadata updated
  • IDENTITY_DELETED - Identity removed
  • PASSWORD_CHANGED - Password updated
  • UNLOCKED - Wallet unlocked with keys
  • LOCKED - Wallet locked
  • KEYS_ROTATED - Keys rotated to new address
  • KEYS_EXPIRED - Keys passed expiration time
  • KEY_DERIVED - Custom key derived
  • CHAIN_SWITCHED - Switched to different blockchain

Blockchain Adapters

Registering Adapters

import { EVMAdapter } from "@cryptforge/blockchain-evm";
import { BitcoinAdapter } from "@cryptforge/blockchain-btc";

// Ethereum
auth.registerAdapter(
  "ethereum",
  new EVMAdapter({
    chainData: { name: "Ethereum", symbol: "ETH", cmc_id: 1027 },
    coinType: 60,
    networks: {
      mainnet: {
        name: "Ethereum Mainnet",
        rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
        chainId: 1,
      },
    },
  })
);

// Bitcoin
auth.registerAdapter("bitcoin", new BitcoinAdapter());

// Polygon (EVM-compatible)
auth.registerAdapter(
  "polygon",
  new EVMAdapter({
    chainData: { name: "Polygon", symbol: "MATIC", cmc_id: 3890 },
    coinType: 60,
    networks: {
      mainnet: {
        name: "Polygon Mainnet",
        rpcUrl: "https://polygon-rpc.com",
        chainId: 137,
      },
    },
  })
);

Creating Custom Adapters

Implement the BlockchainAdapter interface to add support for new blockchains:

import type { BlockchainAdapter, KeyData, ChainData } from "@cryptforge/core";

class SolanaAdapter implements BlockchainAdapter {
  readonly chainData: ChainData = {
    name: "Solana",
    symbol: "SOL",
    cmc_id: 5426,
  };

  async deriveKeys(mnemonic: string): Promise<KeyData> {
    // Implement Solana key derivation
  }

  async deriveKeysAtIndex(mnemonic: string, index: number): Promise<KeyData> {
    // Implement indexed derivation
  }

  // ... implement all required methods
}

// Register your custom adapter
auth.registerAdapter("solana", new SolanaAdapter());

API Reference

Methods

Identity Management

| Method | Parameters | Returns | Description | | ------------------ | ------------------------------------------------------------------- | ----------------------------- | ----------------------- | | generateMnemonic | options?: { wordCount?: 12 \| 24 } | string | Generate BIP39 mnemonic | | createIdentity | CreateIdentityOptions & { chainId?: string } | Promise<{ identity, keys }> | Create new identity | | importIdentity | data: string, password: string, format?: 'mnemonic' \| 'keystore' | Promise<{ identity }> | Import from backup | | exportIdentity | identityId: string, options: ExportOptions | Promise<{ format, data }> | Export identity | | listIdentities | - | Promise<Identity[]> | Get all identities | | switchIdentity | identityId: string | Promise<{ identity }> | Switch active identity | | updateIdentity | identityId: string, updates: { label?, metadata? } | Promise<{ identity }> | Update metadata | | deleteIdentity | identityId: string, password: string | Promise<void> | Delete identity | | changePassword | identityId: string, oldPassword: string, newPassword: string | Promise<void> | Change password |

Session Management

| Method | Parameters | Returns | Description | | -------- | --------------- | ------------------- | ---------------------- | | unlock | UnlockOptions | Promise<{ keys }> | Decrypt and load keys | | lock | - | Promise<void> | Clear keys from memory |

Chain Management

| Method | Parameters | Returns | Description | | --------------------- | --------------------------------------------- | ------------------- | ------------------------ | | registerAdapter | chainId: string, adapter: BlockchainAdapter | void | Register blockchain | | switchChain | chainId: string, password?: string | Promise<{ keys }> | Switch blockchain | | getRegisteredChains | - | string[] | Get registered chain IDs |

Key Operations

| Method | Parameters | Returns | Description | | ------------ | ---------------------------- | --------------------------------------------------- | ---------------------- | | rotateKeys | newDerivationPath?: string | Promise<{ keys }> | Rotate to next address | | deriveKey | DeriveKeyOptions | Promise<{ privateKey, publicKey, address, path }> | One-time derivation |

Cryptographic Operations

| Method | Parameters | Returns | Description | | ----------------- | ------------------------------- | -------------------------------------------- | ---------------- | | signMessage | SignMessageOptions | Promise<{ signature, address, publicKey }> | Sign message | | signTransaction | SignTransactionOptions | Promise<{ signedTransaction, signature }> | Sign transaction | | verifySignature | message, signature, publicKey | Promise<boolean> | Verify signature |

Address Management

| Method | Parameters | Returns | Description | | -------------------- | ------------------------------------------------- | ------------------------------------------------- | ---------------------- | | getAddressForChain | chainId: string, index?: number | Promise<{ address, publicKey, derivationPath }> | Get single address | | getAddresses | chainId: string, start?: number, count?: number | Promise<Array<{ address, path, index }>> | Get multiple addresses | | findUsedAddresses | chainId: string, checkBalance: Function | Promise<Array<{ address, path, index }>> | Account discovery |

Event Management

| Method | Parameters | Returns | Description | | ------------------- | ------------------------------ | ------------ | ----------------------------------------- | | onAuthStateChange | callback: AuthChangeCallback | () => void | Subscribe to events (returns unsubscribe) |

Types

CreateIdentityOptions

interface CreateIdentityOptions {
  mnemonic: string; // BIP39 mnemonic (12 or 24 words)
  password: string; // Password to encrypt keystore
  label?: string; // Human-readable label
  metadata?: Record<string, any>; // Custom metadata
  chainId?: string; // Optional: unlock with specific chain
}

UnlockOptions

interface UnlockOptions {
  password: string; // Keystore decryption password
  identityId?: string; // Which identity to unlock (defaults to current)
  chainId?: string; // Which blockchain to use (required)
  derivationPath?: string; // Custom derivation path (advanced)
  duration?: number; // Auto-lock duration in ms
}

Identity

interface Identity {
  id: string; // Unique identifier
  publicKey: string; // Master public key (xpub)
  fingerprint: string; // Master key fingerprint
  label?: string; // User-defined label
  metadata: Record<string, any>;
  createdAt: Date;
  lastAccess?: Date;
}

Keys

interface Keys {
  privateKey: Uint8Array; // Derived private key (binary)
  privateKeyHex: string; // Derived private key (hex)
  publicKey: Uint8Array; // Derived public key (binary)
  publicKeyHex: string; // Derived public key (hex)
  address: string; // Blockchain address
  derivationPath: string; // BIP44 path (e.g., "m/44'/60'/0'/0/0")
  chain: Chain; // Current blockchain
  expiresAt: Date; // When keys expire
  expiresIn: number; // Seconds until expiration (at creation)
  identity: Identity; // Associated identity
}

Chain

interface Chain {
  id: string; // Chain identifier (e.g., 'ethereum')
  name: string; // Display name (e.g., 'Ethereum')
  symbol: string; // Token symbol (e.g., 'ETH')
}

Security Best Practices

1. Mnemonic Backup

// Always require user confirmation before creating identity
const mnemonic = auth.generateMnemonic({ wordCount: 12 });

// Show to user with clear warnings:
// ⚠️ Write down these words in order
// ⚠️ Never share with anyone
// ⚠️ Store in a secure location
// ⚠️ Losing these words = losing access to funds

// Only proceed after confirmation
await auth.createIdentity({ mnemonic, password, ... });

2. Password Requirements

// Enforce strong passwords
function isStrongPassword(password: string): boolean {
  return (
    password.length >= 12 &&
    /[A-Z]/.test(password) &&
    /[a-z]/.test(password) &&
    /[0-9]/.test(password) &&
    /[^A-Za-z0-9]/.test(password)
  );
}

3. Auto-Lock

// Always use auto-lock for security
await auth.unlock({
  password,
  chainId: "ethereum",
  duration: 5 * 60 * 1000, // Lock after 5 minutes of inactivity
});

4. Key Expiration Monitoring

// Monitor expiration in your UI
setInterval(() => {
  const remaining = auth.currentExpiresIn;
  if (remaining !== null && remaining < 60) {
    console.warn("Keys expiring soon!");
    // Show UI warning
  }
}, 1000);

// Listen for expiration event
auth.onAuthStateChange((event) => {
  if (event === "KEYS_EXPIRED") {
    // Prompt user to unlock again
  }
});

5. Secure Key Handling

// Never log private keys in production
if (process.env.NODE_ENV !== "production") {
  console.log("Private Key:", keys.privateKeyHex);
}

// Use public key for verification
console.log("Public Key:", keys.publicKeyHex); // Safe to log

// Lock when not in use
window.addEventListener("beforeunload", () => {
  auth.lock();
});

Advanced Usage

Multiple Identities

// Create multiple wallets
const personal = await auth.createIdentity({
  mnemonic: generateMnemonic(),
  password: "password1",
  label: "Personal",
});

const trading = await auth.createIdentity({
  mnemonic: generateMnemonic(),
  password: "password2",
  label: "Trading",
});

// Switch between them
await auth.switchIdentity(trading.identity.id);
await auth.unlock({ password: "password2", chainId: "ethereum" });

Custom Derivation Paths

// Derive keys at custom path
const customKey = await auth.deriveKey({
  path: "m/44'/60'/1'/0/0", // Account 1 instead of 0
});

// Sign with custom path
const { signature } = await auth.signMessage({
  message: "Hello",
  derivationPath: "m/44'/60'/1'/0/0",
});

React/Vue Integration

import { ref, onMounted, onUnmounted } from "vue";

const currentAddress = ref(auth.currentAddress);
const expiresIn = ref(auth.currentExpiresIn);

// Subscribe to changes
const unsubscribe = auth.onAuthStateChange((event, keys) => {
  currentAddress.value = auth.currentAddress;
  expiresIn.value = auth.currentExpiresIn;
});

// Live expiration countdown
const interval = setInterval(() => {
  expiresIn.value = auth.currentExpiresIn;
}, 1000);

// Cleanup
onUnmounted(() => {
  unsubscribe();
  clearInterval(interval);
});

Master Public Key

Get the master public key derived from the mnemonic.

Security Note

This returns ONLY the public key bytes (33 bytes, compressed secp256k1), NOT the extended public key (xpub). This is safe to expose publicly because:

  1. ✅ Cannot derive child keys (no chaincode included)
  2. ✅ Cannot compute private keys
  3. ✅ Standard public key cryptography - public keys are meant to be public
  4. ✅ Chain-independent - same key regardless of blockchain

Use Cases

  • Document ownership verification
  • Identity verification across different blockchains
  • Signature verification
  • Access control (e.g., "owner" field in documents)

What This Is NOT

  • ❌ NOT an extended public key (xpub) - does not include chaincode
  • ❌ NOT a blockchain address - this is the raw master public key
  • ❌ NOT blockchain-specific - same for all chains

Usage

await auth.unlock({ password: 'my-password' });

const masterPubKey = auth.masterPublicKey;
// "02a1b2c3d4e5f6..." (33 bytes hex, compressed secp256k1)

// Use for document ownership
const document = {
  id: 'doc_123',
  owner: masterPubKey, // ← Chain-independent identity!
  data: { ... }
};

// Sign with current blockchain key
const signature = await auth.signMessage({
  message: `${document.id}:update:${Date.now()}`
});

// Server can verify ownership regardless of chain

Browser Compatibility

This package is 100% browser-compatible with zero configuration:

  • ✅ Chrome, Firefox, Safari, Edge
  • ✅ Node.js (v16+)
  • ✅ Electron (main and renderer)
  • ✅ React Native
  • ✅ Web Workers
  • ✅ Service Workers

No polyfills required. Uses native browser APIs:

  • crypto.subtle for encryption
  • indexedDB for storage
  • Uint8Array for binary data

Dependencies

{
  "@scure/bip39": "^1.2.1",
  "@scure/bip32": "^1.3.2",
  "@cryptforge/core": "workspace:*"
}

All dependencies are browser-safe, audited, and actively maintained.

Additional Documentation

  • DATA_ENCRYPTION.md - Detailed documentation on HKDF data encryption and master public key features for chain-independent encryption and identity verification

Examples

See the complete working example in examples/vue-electron-example/src/AuthTest.vue.

License

MIT

Contributing

Contributions welcome! Please ensure:

  • All code is browser-compatible (use @scure and @noble libraries)
  • TypeScript types are properly defined
  • Examples are updated
  • Tests pass (when implemented)

Support

For issues, questions, or feature requests, please open an issue on GitHub.