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

zk-crypto-helpers

v0.1.2

Published

Unified TypeScript library for ZK and crypto operations with minimal conversions. Features privacy-preserving workflows, Merkle trees, ECDH encryption, and Solana integration.

Readme

zk-crypto-helpers

Unified TypeScript library for ZK and crypto operations with minimal conversions.

Features

  • Bigint-First Architecture: All crypto operations work with bigint natively, minimizing type conversions
  • Layered Abstraction: Clean separation between core primitives, domain operations, and high-level workflows
  • Type-Safe: Full TypeScript support with comprehensive type definitions
  • ZK-Friendly: Optimized for zero-knowledge proof workflows using Poseidon and Baby Jubjub
  • Privacy-Preserving Workflows: Complete deposit, withdrawal, and recovery workflows
  • Sparse Merkle Trees: Highly optimized sparse Merkle tree implementation with Poseidon hashing, node caching, and support for depths up to 30 (1B+ leaves)
  • ECDH Encryption: Secure key exchange and encryption using Baby Jubjub elliptic curve
  • Solana Integration: Native support for Solana PublicKey conversions
  • Cross-Platform: Works in both Node.js and browser environments
  • Well-Tested: 261 comprehensive tests with full coverage

Installation

npm install zk-crypto-helpers

Or with yarn:

yarn add zk-crypto-helpers

Quick Start

import {
  generateDeposit,
  generateWithdrawal,
  MerkleTree,
} from "zk-crypto-helpers";
import { Keypair } from "@solana/web3.js";

// Generate a privacy-preserving deposit
const mintPubkey = Keypair.generate().publicKey;
const deposit = await generateDeposit({
  deposit: 1000000n,
  mint: mintPubkey,
});

// Save the deposit note securely (needed to withdraw later)
console.log("Commitment:", deposit.commitment);
console.log("Nullifier:", deposit.nullifier);
console.log("Secret:", deposit.secret);

// Create a Merkle tree and add the commitment
const tree = new MerkleTree(20); // 2^20 = ~1M leaves
await tree.insert(deposit.commitment);

// Generate withdrawal proof
const recipientPubkey = Keypair.generate().publicKey;
const withdrawal = await generateWithdrawal({
  nullifier: deposit.nullifier,
  secret: deposit.secret,
  deposit: deposit.deposit,
  mint: mintPubkey,
  recipient: recipientPubkey,
  merkleTree: tree,
  commitmentIndex: 0,
});

console.log("Withdrawal proof generated!");
console.log("Root:", withdrawal.root);
console.log("Nullifier hash:", withdrawal.nullifierHash);

High-Level Workflows

Deposit Workflow

Create privacy-preserving deposits with optional recovery and compliance encryption:

import { generateDeposit, verifyDeposit } from "zk-crypto-helpers";

// Simple deposit
const deposit = await generateDeposit({
  deposit: 1000000n,
  mint: mintPubkey,
});

// Deposit with recovery mechanism
const senderKeypair = await generateKeypair();
const recoveryKeypair = await generateKeypair();

const depositWithRecovery = await generateDeposit({
  deposit: 1000000n,
  mint: mintPubkey,
  senderPrivKey: senderKeypair.privKey,
  recoveryPubKey: recoveryKeypair.pubKey,
});

// Verify deposit
const isValid = await verifyDeposit(deposit);

Withdrawal Workflow

Generate ZK proofs for withdrawals:

import {
  generateWithdrawal,
  verifyWithdrawal,
  createWithdrawalCircuitInputs,
  validateWithdrawalPossible,
} from "zk-crypto-helpers";

// Validate withdrawal is possible
const usedNullifiers = new Set<bigint>();
const validation = validateWithdrawalPossible(
  deposit.commitment,
  tree,
  deposit.nullifierHash,
  usedNullifiers,
);

if (!validation.valid) {
  throw new Error(validation.error);
}

// Generate withdrawal
const withdrawal = await generateWithdrawal({
  nullifier: deposit.nullifier,
  secret: deposit.secret,
  deposit: deposit.deposit,
  mint: mintPubkey,
  recipient: recipientPubkey,
  merkleTree: tree,
  commitmentIndex: validation.commitmentIndex,
});

// Format for ZK circuit
const circuitInputs = createWithdrawalCircuitInputs(withdrawal);

// Verify withdrawal data
const isValid = await verifyWithdrawal(withdrawal, deposit.commitment);

Recovery Workflow

Recover deposit information from encrypted data:

import {
  recoverDeposit,
  verifyRecoveredDeposit,
  recoveredToDepositData,
} from "zk-crypto-helpers";

// Recover single deposit
const recovered = await recoverDeposit({
  encryptedData: encryptedRecoveryData,
  recoveryPrivKey: recoveryKeypair.privKey,
});

if (recovered) {
  // Verify recovered data
  const isValid = await verifyRecoveredDeposit(recovered);

  // Convert to deposit format
  const depositData = recoveredToDepositData(recovered);
}

// Batch recovery
const { successful, failed } = await batchRecoverDeposits({
  encryptedDataList: [data1, data2, data3],
  recoveryPrivKey: recoveryKeypair.privKey,
});

Core Modules

Merkle Trees

Highly optimized sparse Merkle tree implementation with Poseidon hashing:

import { MerkleTree } from "zk-crypto-helpers";

// Create tree with 20 levels (2^20 = 1,048,576 max leaves)
// Sparse implementation supports depths up to 30 efficiently
const tree = new MerkleTree(20);

// Insert commitments
await tree.insert(commitment1);
await tree.insert(commitment2);

// Update existing leaf (new feature!)
await tree.update(0, newCommitment);

// Get root
const root = tree.getRoot();

// Generate Merkle proof
const proof = tree.getProof(0);

// Verify proof
const isValid = tree.verify(proof, commitment1, root);

// Batch operations (highly optimized)
await tree.insertBatch([c1, c2, c3]);

// Tree info
console.log("Leaves:", tree.getLeafCount());
console.log("Capacity:", tree.getCapacity());
console.log("Is empty:", tree.isEmpty());
console.log("Is full:", tree.isFull());

// Access individual leaves
const leaf = tree.getLeaf(0); // Returns bigint | undefined

// Get sparse leaf map (efficient for sparse trees)
const sparseLeaves = tree.getSparseLeaves();

// Get statistics
const stats = tree.getStats();
console.log("Cache size:", stats.cacheSize);
console.log("Utilization:", stats.utilization);

ECDH and Encryption

Secure encryption using Baby Jubjub elliptic curve:

import {
  ecdhEncrypt,
  ecdhDecrypt,
  computeSharedSecret,
  deriveEncryptionKey,
} from "zk-crypto-helpers";

// Generate keypairs
const alice = await generateKeypair();
const bob = await generateKeypair();

// Encrypt message from Alice to Bob
const nonce = randomFieldElement();
const encrypted = await ecdhEncrypt(message, alice.privKey, bob.pubKey, nonce);

// Bob decrypts the message
const decrypted = await ecdhDecrypt(
  encrypted,
  bob.privKey,
  alice.pubKey,
  nonce,
);

// Manual ECDH
const sharedSecret = await computeSharedSecret(alice.privKey, bob.pubKey);
const encryptionKey = await deriveEncryptionKey(sharedSecret, nonce);

Field Operations

BN254 field arithmetic and utilities:

import {
  mod,
  modBytes,
  isInField,
  add,
  mul,
  sub,
  neg,
} from "zk-crypto-helpers";

// Reduce to field element
const fieldElement = mod(someValue);

// Convert bytes to field element
const fromBytes = modBytes(new Uint8Array([1, 2, 3]));

// Field arithmetic
const sum = add(a, b);
const product = mul(a, b);
const difference = sub(a, b);
const negation = neg(a);

// Validation
if (isInField(value)) {
  // value is valid
}
assertInField(value, "myValue"); // throws if invalid

Hashing

Poseidon hash functions optimized for ZK circuits:

import {
  poseidon1,
  poseidon2,
  poseidon4,
  computeCommitment,
  computeNullifierHash,
} from "zk-crypto-helpers";

// Hash single value
const hash1 = await poseidon1(value);

// Hash two values (e.g., Merkle tree nodes)
const parentHash = await poseidon2(leftChild, rightChild);

// Hash four values (commitment)
const commitment = await poseidon4(nullifier, secret, deposit, mint);

// Convenience functions
const commitment = await computeCommitment(nullifier, secret, deposit, mintId);
const nullifierHash = await computeNullifierHash(nullifier);

// Hash arrays
const hash = await poseidonMany([val1, val2, val3, val4]);

// Hash bytes
const hash = await poseidonBytes(new Uint8Array([1, 2, 3]));

Solana Integration

Seamless PublicKey conversions:

import {
  pubkeyToBigint,
  pubkeyToBytes,
  pubkeyToHex,
  bigintToPubkey,
  generateCommitment,
} from "zk-crypto-helpers";
import { PublicKey } from "@solana/web3.js";

// Convert PublicKey to various formats
const bigintValue = pubkeyToBigint(pubkey);
const bytes = pubkeyToBytes(pubkey);
const hex = pubkeyToHex(pubkey, true); // with 0x prefix

// Convert back to PublicKey
const pubkey = bigintToPubkey(bigintValue);

// Generate commitment with Solana PublicKey
const commitment = await generateCommitment(
  nullifier,
  secret,
  deposit,
  mintPubkey, // Solana PublicKey
);

Type Conversions

Comprehensive conversion utilities:

import {
  // BigInt conversions
  bigintToBytes32,
  bytesToBigint,
  bigintToHex,
  hexToBigint,

  // Hex conversions
  bytesToHex,
  hexToBytes,
  padHex,
  stripHexPrefix,

  // Number conversions
  numberToBigint,
  bigintToNumber,
  canConvertToNumber,
} from "zk-crypto-helpers";

// BigInt <-> Bytes
const bytes = bigintToBytes32(value);
const value = bytesToBigint(bytes);

// BigInt <-> Hex
const hex = bigintToHex(value, 32);
const value = hexToBigint(hex);

// Safe number conversions
if (canConvertToNumber(bigValue)) {
  const num = bigintToNumber(bigValue);
}

API Reference

Workflow Module

Deposit Functions

  • generateDeposit(options: DepositOptions): Promise<DepositData> - Generate deposit with optional recovery/compliance encryption
  • verifyDeposit(depositData: DepositData): Promise<boolean> - Verify deposit consistency
  • createDepositNote(depositData: DepositData): string - Serialize deposit for storage
  • parseDepositNote(note: string, mintPubkey: PublicKey): DepositData - Parse deposit note

Withdrawal Functions

  • generateWithdrawal(input: WithdrawalInput): Promise<WithdrawalData> - Generate withdrawal with Merkle proof
  • verifyWithdrawal(withdrawalData: WithdrawalData, commitment: bigint): Promise<boolean> - Verify withdrawal data
  • createWithdrawalCircuitInputs(withdrawalData: WithdrawalData): Record<string, string | string[]> - Format for circuit
  • validateWithdrawalPossible(commitment, merkleTree, nullifierHash, usedNullifiers) - Validate withdrawal prerequisites
  • findCommitmentIndex(commitment: bigint, merkleTree: MerkleTree): number - Find commitment in tree
  • generateBatchWithdrawal(input: BatchWithdrawalInput): Promise<WithdrawalData[]> - Batch withdrawal generation

Recovery Functions

  • recoverDeposit(options: RecoveryOptions): Promise<RecoveredDepositData | null> - Recover deposit from encrypted data
  • verifyRecoveredDeposit(recovered: RecoveredDepositData): Promise<boolean> - Verify recovered deposit
  • recoveredToDepositData(recovered: RecoveredDepositData): DepositData - Convert recovered to deposit format
  • batchRecoverDeposits(options: BatchRecoveryOptions): Promise<BatchRecoveryResult> - Batch recovery
  • createRecoveryNote(encryptedData: EncryptedRecoveryData): string - Serialize recovery data
  • parseRecoveryNote(note: string, mintPubkey: PublicKey): EncryptedRecoveryData - Parse recovery note

Field Module

Constants

  • BN254_ORDER: The BN254 curve order (field modulus)
  • ZERO: Field element zero (0n)
  • ONE: Field element one (1n)

Functions

  • mod(value: bigint): bigint - Reduce value modulo BN254_ORDER
  • modNumber(value: number): bigint - Convert number to field element
  • modBytes(bytes: Uint8Array | number[]): bigint - Convert bytes to field element
  • isInField(value: bigint): boolean - Check if value is in valid field range
  • assertInField(value: bigint, name?: string): void - Assert value is in field
  • add(a: bigint, b: bigint): bigint - Field addition
  • sub(a: bigint, b: bigint): bigint - Field subtraction
  • mul(a: bigint, b: bigint): bigint - Field multiplication
  • neg(a: bigint): bigint - Field negation
  • isZero(a: bigint): boolean - Check if zero
  • isOne(a: bigint): boolean - Check if one

Crypto Module

Functions

  • initCrypto(): Promise<CryptoContext> - Initialize crypto libraries
  • randomFieldElement(): bigint - Generate random field element
  • generateKeypair(): Promise<Keypair> - Generate Baby Jubjub keypair
  • derivePublicKey(privKey: bigint): Promise<Point> - Derive public key from private key
  • getRandomBytes(length?: number): Uint8Array - Generate random bytes
  • bytesToHex(bytes: Uint8Array): string - Convert bytes to hex string
  • isBrowser(): boolean - Check if running in browser
  • isNode(): boolean - Check if running in Node.js

Hash Module

Functions

  • poseidon1(input: bigint): Promise<bigint> - Hash single field element
  • poseidon2(a: bigint, b: bigint): Promise<bigint> - Hash two field elements
  • poseidon4(a: bigint, b: bigint, c: bigint, d: bigint): Promise<bigint> - Hash four field elements
  • computeCommitment(nullifier, secret, deposit, mint): Promise<bigint> - Compute commitment hash
  • computeNullifierHash(nullifier: bigint): Promise<bigint> - Compute nullifier hash
  • poseidonMany(inputs: bigint[]): Promise<bigint> - Hash array of field elements
  • poseidonBytes(bytes: Uint8Array | number[]): Promise<bigint> - Hash bytes

ECDH Module

Functions

  • computeSharedSecret(privKey: bigint, pubKey: Point): Promise<bigint> - Compute ECDH shared secret
  • deriveEncryptionKey(sharedSecret: bigint, nonce: bigint): Promise<bigint> - Derive encryption key
  • ecdhKeyExchange(privKey: bigint, pubKey: Point, nonce: bigint): Promise<bigint> - Complete ECDH key exchange
  • verifyECDHSymmetry(keypair1: Keypair, keypair2: Keypair, nonce: bigint): Promise<boolean> - Verify ECDH symmetry

Encryption Module

Functions

  • authenticatedEncrypt(plaintext, encryptionKey, nonce): Promise<EncryptedData> - Encrypt with authentication
  • authenticatedDecrypt(encrypted, encryptionKey, nonce): Promise<bigint | null> - Decrypt and verify
  • ecdhEncrypt(plaintext, senderPrivKey, recipientPubKey, nonce): Promise<EncryptedData> - ECDH encrypt
  • ecdhDecrypt(encrypted, recipientPrivKey, senderPubKey, nonce): Promise<bigint | null> - ECDH decrypt
  • encrypt(plaintext, senderPrivKey, recipientPubKey, nonce): Promise<EncryptedData> - Convenience wrapper
  • decrypt(encrypted, recipientPrivKey, senderPubKey, nonce): Promise<bigint | null> - Convenience wrapper

Merkle Tree

Class: MerkleTree

constructor(levels: number, zeroValue?: bigint)

Methods

  • async insert(leaf: bigint): Promise<void> - Insert single leaf at next available position
  • async insertBatch(leaves: bigint[]): Promise<void> - Insert multiple leaves efficiently
  • async update(index: number, element: bigint): Promise<void> - Update leaf at specific index
  • getRoot(): bigint - Get current root (synchronous)
  • async getRootAsync(): Promise<bigint> - Force async root computation
  • getProof(leafIndex: number): MerkleProof - Generate Merkle proof for leaf
  • async verify(proof: MerkleProof, leaf: bigint, root: bigint): Promise<boolean> - Verify Merkle proof
  • getLeafCount(): number - Get number of non-zero leaves
  • getCapacity(): number - Get maximum capacity (2^levels)
  • getLevels(): number - Get tree height
  • isEmpty(): boolean - Check if tree has no non-zero leaves
  • isFull(): boolean - Check if tree is at maximum capacity
  • getLeaf(index: number): bigint | undefined - Get specific leaf (undefined if not set)
  • getLeaves(): bigint[] - Get all leaves from 0 to highest index
  • getSparseLeaves(): Map<number, bigint> - Get only non-zero leaves as sparse map
  • getZeroValue(): bigint - Get the zero value for empty leaves
  • getStats(): TreeStats - Get tree statistics (levels, capacity, utilization, cache size, etc.)
  • async getNodeAt(level: number, index: number): Promise<bigint> - Get any node in the tree (advanced)

Types

// Point on Baby Jubjub curve
interface Point {
  x: bigint;
  y: bigint;
}

// Keypair
interface Keypair {
  privKey: bigint;
  pubKey: Point;
}

// Encrypted data with authentication
interface EncryptedData {
  ciphertext: bigint;
  authTag: bigint;
}

// Merkle proof
interface MerkleProof {
  root: bigint;
  leaf: bigint;
  pathElements: bigint[];
  pathIndices: number[];
  leafIndex: number;
}

// Deposit data
interface DepositData {
  nullifier: bigint;
  secret: bigint;
  deposit: bigint;
  mint: PublicKey;
  commitment: bigint;
  nullifierHash: bigint;
  recoveryEncryption?: {
    encryptedNullifier: EncryptedData;
    encryptedSecret: EncryptedData;
    nonce: bigint;
  };
  complianceEncryption?: {
    encryptedNullifier: EncryptedData;
    encryptedSecret: EncryptedData;
    nonce: bigint;
  };
}

// Withdrawal data
interface WithdrawalData {
  nullifier: bigint;
  secret: bigint;
  pathElements: bigint[];
  pathIndices: number[];
  root: bigint;
  nullifierHash: bigint;
  recipient: bigint;
  mint: bigint;
  deposit: bigint;
  leafIndex: number;
}

Development

Building

npm run build

Testing

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Watch mode
npm run test:watch

Linting and Formatting

npm run lint
npm run format

Performance Notes

  • Sparse Merkle Trees: The library uses an optimized sparse implementation with:
    • Sparse Storage: Only stores non-zero leaves in a Map, dramatically reducing memory usage
    • Node Caching: Computed intermediate nodes are cached with selective cache invalidation
    • Empty Subtree Detection: Entire empty branches use precomputed zero hashes, skipping unnecessary computation
    • Recommended Depths:
      • Testing: depth 3-10 (8 to 1,024 leaves)
      • Development: depth 15-20 (32K to 1M leaves)
      • Production: depth 20-30 (1M to 1B+ leaves) - sparse implementation handles this efficiently!
    • Memory Efficiency: O(actual leaves + cached nodes) instead of O(2^levels)
  • Async Operations: All cryptographic operations are async. Use Promise.all() for parallel operations.
  • Batch Operations: Always use insertBatch() instead of multiple insert() calls for better performance
  • Bigint-First: The library uses bigint throughout to minimize conversions and ensure precision.

Examples

See the /examples directory for complete working examples:

  • basic-deposit-withdrawal.ts - Simple deposit and withdrawal flow
  • recovery-workflow.ts - Using recovery encryption
  • batch-operations.ts - Batch processing examples
  • merkle-tree-usage.ts - Merkle tree operations

Roadmap

  • [x] Phase 1: Core Foundation (Field, Crypto, Hash)
  • [x] Phase 2: ECDH and Encryption
  • [x] Phase 3: Merkle Trees
  • [x] Phase 4: Circuit Integration
  • [x] Phase 5: Type Conversions
  • [x] Phase 6: Solana Integration
  • [x] Phase 7: High-level Workflows
  • [x] Phase 8: Documentation & Examples

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.