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

@cheny56/zk-confidential-offchain

v1.0.0

Published

Confidential smart contracts using ZK proofs - Off-chain proof generation with snarkjs/Circom. Works on ANY EVM chain.

Downloads

104

Readme

@cheny56/zk-confidential-offchain

Confidential Smart Contracts using Zero-Knowledge Proofs (Off-Chain Verification)

A privacy-preserving token system where:

  • Inputs are private - Transfer amounts are hidden
  • State is hidden - Balances stored as commitments
  • Only proofs are public - ZK proofs verify correctness without revealing data

This package uses snarkjs/Circom for proof generation and works on ANY EVM chain.

npm version License: MIT

Features

  • 🌐 Any EVM Chain - Works on Ethereum, Polygon, BSC, Quorum, etc.
  • 🌍 Browser Compatible - Proofs can be generated in web browsers
  • 🔒 Full Privacy - Balances, transfers, identities all hidden
  • 📦 Self-Contained - No external dependencies on special nodes
  • 🛠️ Customizable - Modify Circom circuits as needed

Installation

npm install @cheny56/zk-confidential-offchain

Quick Start

const { 
    NoteWallet, 
    Note, 
    MerkleTree,
    poseidonHash 
} = require('@cheny56/zk-confidential-offchain');

// Create a wallet
const wallet = new NoteWallet();
await wallet.init();

// Create a private note (hidden balance)
const note = await wallet.createNote(1000n);
console.log('Commitment:', note.getCommitmentHex());

// Check balance (only you know this)
console.log('Balance:', wallet.getBalance());

Table of Contents

How It Works

┌─────────────────────────────────────────────────────────────────────────┐
│                         CONFIDENTIAL TOKEN FLOW                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  DEPOSIT (Public ETH → Private Balance)                                 │
│  ┌────────────┐     ┌────────────┐     ┌────────────────────────┐      │
│  │ ETH        │ --> │ ZK Proof   │ --> │ Commitment stored      │      │
│  │ (public)   │     │ (snarkjs)  │     │ in Merkle tree         │      │
│  └────────────┘     └────────────┘     └────────────────────────┘      │
│                                                                         │
│  TRANSFER (Private → Private)                                           │
│  ┌────────────────────────────────────────────────────────────────┐    │
│  │  Sender creates proof showing:                                  │    │
│  │    1. They own the input note (know secret)                     │    │
│  │    2. Input note exists in Merkle tree                         │    │
│  │    3. Input value = Output values (conservation)               │    │
│  │    4. Nullifier prevents double-spending                        │    │
│  │                                                                 │    │
│  │  Public: nullifier, new commitments                            │    │
│  │  Hidden: sender, recipient, amounts                            │    │
│  └────────────────────────────────────────────────────────────────┘    │
│                                                                         │
│  WITHDRAW (Private Balance → Public ETH)                                │
│  ┌────────────┐     ┌────────────┐     ┌────────────────────────┐      │
│  │ Commitment │ --> │ ZK Proof   │ --> │ ETH sent to            │      │
│  │ (private)  │     │            │     │ recipient (public)     │      │
│  └────────────┘     └────────────┘     └────────────────────────┘      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Core Concepts

Note

A Note is a private balance unit (like a UTXO):

Note = {
    value: 1000,           // Hidden amount
    ownerSecret: 0x...,    // Only owner knows this
    nonce: 0x...,          // Random value
    commitment: Hash(value, ownerSecret, nonce)  // Public
}

Commitment

A Commitment hides the note's contents:

  • commitment = Poseidon(value, ownerSecret, nonce)
  • Anyone can see the commitment
  • Nobody can determine value or owner from it

Nullifier

A Nullifier prevents double-spending:

  • nullifier = Poseidon(commitment, ownerSecret, leafIndex)
  • Revealed when spending a note
  • Can't be linked back to the commitment

Merkle Tree

All commitments are stored in a Merkle Tree:

  • Efficient membership proofs (O(log n))
  • ZK-friendly with Poseidon hash

API Reference

NoteWallet

const { NoteWallet } = require('@cheny56/zk-confidential-offchain');

const wallet = new NoteWallet();
await wallet.init();

// Create a note
const note = await wallet.createNote(amount);

// Get balance
const balance = wallet.getBalance();

// Get spendable notes
const notes = wallet.getSpendableNotes();

// Select notes for transfer
const selected = wallet.selectNotesForAmount(amount);

// Export for backup
const backup = wallet.toJSON();

// Restore from backup
const restored = NoteWallet.fromJSON(backup);

Note

const { Note } = require('@cheny56/zk-confidential-offchain');

// Create manually
const note = new Note(value, ownerSecret, nonce);

// Get commitment
const commitment = note.getCommitmentHex();

// Get nullifier (after tree insertion)
const nullifier = note.getNullifierHex();

// Check if spendable
const canSpend = note.canSpend();

// Get private inputs for ZK proof
const inputs = note.getPrivateInputs();

MerkleTree

const { MerkleTree } = require('@cheny56/zk-confidential-offchain');

const tree = new MerkleTree(20); // depth 20
await tree.init();

// Insert commitment
const index = await tree.insert(commitment);

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

// Generate proof
const { path, indices } = await tree.generateProof(index);

// Verify proof
const valid = MerkleTree.verifyProof(leaf, index, path, indices, root);

Poseidon Hash

const { poseidonHash, toHex32 } = require('@cheny56/zk-confidential-offchain');

// Hash values
const hash = await poseidonHash(value1, value2, value3);
console.log(toHex32(hash));

Step-by-Step Guide

Step 1: Install and Initialize

# Create project
mkdir my-confidential-app
cd my-confidential-app
npm init -y

# Install package
npm install @cheny56/zk-confidential-offchain

Step 2: Create a Wallet

const { NoteWallet } = require('@cheny56/zk-confidential-offchain');

async function main() {
    // Create wallet
    const wallet = new NoteWallet();
    await wallet.init();
    
    console.log('Wallet created!');
    console.log('Master secret (KEEP SAFE):', wallet.getMasterSecretHex());
}

main();

Step 3: Create Notes (Private Balances)

// Create notes representing private balances
const note1 = await wallet.createNote(1000n);
const note2 = await wallet.createNote(500n);
const note3 = await wallet.createNote(250n);

console.log('Created notes:');
console.log('  Note 1:', note1.value, '→', note1.getCommitmentHex().slice(0, 20) + '...');
console.log('  Note 2:', note2.value, '→', note2.getCommitmentHex().slice(0, 20) + '...');
console.log('  Note 3:', note3.value, '→', note3.getCommitmentHex().slice(0, 20) + '...');

console.log('Total balance:', wallet.getBalance());

Step 4: Insert into Merkle Tree

const { MerkleTree } = require('@cheny56/zk-confidential-offchain');

const tree = new MerkleTree(20);
await tree.init();

// Insert notes (simulates on-chain deposit)
for (const note of wallet.getSpendableNotes()) {
    const index = await tree.insert(note.commitment);
    const { path, indices } = await tree.generateProof(index);
    note.setTreePosition(index, tree.getRoot(), path, indices);
    console.log(`Inserted note at index ${index}`);
}

console.log('Merkle root:', tree.getRootHex());

Step 5: Generate Transfer Inputs

// Select notes to spend
const amountToSend = 300n;
const notesToSpend = wallet.selectNotesForAmount(amountToSend);

// Get private inputs for ZK proof
for (const note of notesToSpend) {
    const inputs = note.getPrivateInputs();
    console.log('Private inputs:', inputs);
}

Step 6: Verify Your Setup

# Run the verification example
node examples/verify-setup.js

Examples

Basic Balance Management

// examples/basic-balance.js
const { NoteWallet, MerkleTree, toHex32 } = require('@cheny56/zk-confidential-offchain');

async function main() {
    console.log('=== Private Balance Management ===\n');
    
    // 1. Create wallet
    const wallet = new NoteWallet();
    await wallet.init();
    
    // 2. Create Merkle tree
    const tree = new MerkleTree(20);
    await tree.init();
    
    // 3. Create notes
    const amounts = [1000n, 500n, 250n];
    for (const amount of amounts) {
        const note = await wallet.createNote(amount);
        const index = await tree.insert(note.commitment);
        const { path, indices } = await tree.generateProof(index);
        note.setTreePosition(index, tree.getRoot(), path, indices);
        console.log(`Created note: ${amount} → ${note.getCommitmentHex().slice(0, 30)}...`);
    }
    
    // 4. Check balance
    console.log(`\nTotal private balance: ${wallet.getBalance()}`);
    console.log(`Spendable notes: ${wallet.getSpendableNotes().length}`);
    
    // 5. Select for transfer
    const selected = wallet.selectNotesForAmount(600n);
    console.log(`\nTo send 600, would spend ${selected.length} notes totaling ${selected.reduce((s, n) => s + n.value, 0n)}`);
}

main().catch(console.error);

Transfer Simulation

// examples/transfer-simulation.js
const { NoteWallet, Note, MerkleTree, toHex32, generateOwnerSecret } = require('@cheny56/zk-confidential-offchain');

async function main() {
    console.log('=== Private Transfer Simulation ===\n');
    
    // Alice's wallet
    const aliceWallet = new NoteWallet();
    await aliceWallet.init();
    
    // Bob's receiving secret
    const bobSecret = generateOwnerSecret();
    
    // Shared Merkle tree (on-chain)
    const tree = new MerkleTree(20);
    await tree.init();
    
    // Alice deposits 1000
    console.log('1. Alice deposits 1000');
    const aliceNote = await aliceWallet.createNote(1000n);
    const aliceIndex = await tree.insert(aliceNote.commitment);
    const aliceProof = await tree.generateProof(aliceIndex);
    aliceNote.setTreePosition(aliceIndex, tree.getRoot(), aliceProof.path, aliceProof.indices);
    console.log(`   Commitment: ${aliceNote.getCommitmentHex().slice(0, 30)}...`);
    
    // Alice transfers 300 to Bob
    console.log('\n2. Alice transfers 300 to Bob');
    
    // Create Bob's note
    const bobNote = new Note(300n, bobSecret);
    console.log(`   Bob's commitment: ${bobNote.getCommitmentHex().slice(0, 30)}...`);
    
    // Create Alice's change note
    const aliceChangeNote = await aliceWallet.createNote(700n);
    console.log(`   Alice's change: ${aliceChangeNote.getCommitmentHex().slice(0, 30)}...`);
    
    // Compute nullifier (prevents double-spend)
    const nullifier = aliceNote.getNullifierHex();
    console.log(`   Nullifier: ${nullifier.slice(0, 30)}...`);
    
    // Mark Alice's original note as spent
    aliceNote.markSpent();
    
    // Insert new notes into tree
    const bobIndex = await tree.insert(bobNote.commitment);
    const changeIndex = await tree.insert(aliceChangeNote.commitment);
    
    // Update positions
    const bobProof = await tree.generateProof(bobIndex);
    bobNote.setTreePosition(bobIndex, tree.getRoot(), bobProof.path, bobProof.indices);
    
    const changeProof = await tree.generateProof(changeIndex);
    aliceChangeNote.setTreePosition(changeIndex, tree.getRoot(), changeProof.path, changeProof.indices);
    
    // Final state
    console.log('\n3. Final State');
    console.log(`   Alice's balance: ${aliceWallet.getBalance()}`);
    console.log(`   Bob's note value: ${bobNote.value}`);
    console.log(`   Merkle root: ${tree.getRootHex().slice(0, 30)}...`);
    
    // Privacy summary
    console.log('\n4. Privacy Summary');
    console.log('   PUBLIC: nullifier, new commitments, merkle root');
    console.log('   HIDDEN: Alice, Bob, 300 transferred, 700 change');
}

main().catch(console.error);

Circuit Compilation

If you need to customize the circuits:

# Install circom globally
npm install -g circom snarkjs

# Compile mint circuit
circom circuits/mint.circom --r1cs --wasm --sym -o build/

# Compile transfer circuit  
circom circuits/transfer.circom --r1cs --wasm --sym -o build/

# Download powers of tau (if needed)
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_12.ptau -O pot12_final.ptau

# Generate proving keys
snarkjs groth16 setup build/mint.r1cs pot12_final.ptau build/mint.zkey
snarkjs groth16 setup build/transfer.r1cs pot12_final.ptau build/transfer.zkey

# Export Solidity verifiers
snarkjs zkey export solidityverifier build/mint.zkey contracts/MintVerifier.sol
snarkjs zkey export solidityverifier build/transfer.zkey contracts/TransferVerifier.sol

Contract Deployment

const { ethers } = require('ethers');

async function deploy() {
    const provider = new ethers.JsonRpcProvider('http://localhost:8545');
    const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
    
    // Deploy MintVerifier
    const MintVerifier = await ethers.ContractFactory.fromSolidity(
        require('./artifacts/MintVerifier.json'),
        wallet
    );
    const mintVerifier = await MintVerifier.deploy();
    
    // Deploy TransferVerifier
    const TransferVerifier = await ethers.ContractFactory.fromSolidity(
        require('./artifacts/TransferVerifier.json'),
        wallet
    );
    const transferVerifier = await TransferVerifier.deploy();
    
    // Deploy ConfidentialToken
    const ConfidentialToken = await ethers.ContractFactory.fromSolidity(
        require('./artifacts/ConfidentialToken.json'),
        wallet
    );
    const token = await ConfidentialToken.deploy(
        'Private Token',
        'PRIV',
        await mintVerifier.getAddress(),
        await transferVerifier.getAddress()
    );
    
    console.log('Contracts deployed:');
    console.log('  MintVerifier:', await mintVerifier.getAddress());
    console.log('  TransferVerifier:', await transferVerifier.getAddress());
    console.log('  ConfidentialToken:', await token.getAddress());
}

Security Considerations

  1. Backup Your Wallet: Losing your master secret means losing all funds
  2. Trusted Setup: The proving keys require a trusted setup ceremony
  3. Nullifier Tracking: On-chain nullifier set prevents double-spending
  4. Gas Costs: Solidity verification costs ~5M gas per proof
  5. Timing Attacks: Consider using a relayer to hide sender address

Package Contents

zk-confidential-offchain/
├── lib/
│   ├── index.js           # Main exports
│   ├── poseidon.js        # Poseidon hash
│   ├── merkle.js          # Merkle tree
│   ├── note.js            # Note management
│   └── commitment.js      # Commitment utilities
├── client/
│   └── confidential-client.js  # Contract interaction
├── contracts/
│   ├── ConfidentialToken.sol
│   ├── CommitmentTree.sol
│   └── interfaces/
├── circuits/
│   ├── mint.circom
│   └── transfer.circom
├── examples/
│   ├── basic-balance.js
│   ├── transfer-simulation.js
│   └── verify-setup.js
├── package.json
└── README.md

License

MIT

Related