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

@zkp-system/node-sdk

v2.2.0

Published

Node.js SDK for the ZKClaimUpload zero-knowledge proof middleware

Downloads

1,068

Readme

@zkp-system/node-sdk

Node.js / Browser SDK for the ZKClaimUpload zero-knowledge proof middleware.

Requirements

  • Node.js >= 18.0.0
  • Runtime dependencies: @noble/hashes, @noble/curves (both zero-dep, audited)

Installation

npm install @zkp-system/node-sdk

Import paths

| Path | Environment | Crypto engine | |---|---|---| | @zkp-system/node-sdk | Node.js server (API routes, CLI) | node:crypto — synchronous | | @zkp-system/node-sdk/browser | Browser / React Native / Cloudflare Workers | crypto.subtle — async | | @zkp-system/node-sdk/crypto | Node.js crypto utilities (named exports) | node:crypto — synchronous |


Quick Start

Server-side (Next.js API route / Node.js)

import { ZKPClient, ZKPCrypto } from '@zkp-system/node-sdk';

// 1. Generate a secp256k1 key pair (done once per user)
const { privateKey, publicKey } = ZKPCrypto.generateKeyPair();

// 2. Create a client
const client = new ZKPClient({
  baseUrl: 'http://localhost:3002',
  apiKey: 'your-api-key',
});

// 3. Check service health
const health = await client.health();
console.log(health.status); // 'ok'
console.log(health.chainId); // 42161

// 4. Issue a credential
const { claimFile, insertCalldata } = await client.issueCredential({
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  fileId: '0xabcdef...', // 0x-prefixed bytes32
  userPublicKey: publicKey,
});

// 5. Decrypt the credential secrets
const { n, s } = ZKPCrypto.decryptSecret(claimFile.encryptedSecret, privateKey);

// 6. Generate a ZK proof
const proofResult = await client.generateProof({
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  fileId: claimFile.fileId,
  n: n.toString(),
  s: s.toString(),
  leafIndex: claimFile.leafIndex,
});

// 7. Submit proofResult.calldata on-chain via ethers.js / viem
console.log(proofResult.calldata);

Browser / React Native (per-user keys from wallet)

import {
  deriveKeyFromSignature,
  deriveFileKey,
  encryptFile,
  decryptFile,
  decryptSecret,
} from '@zkp-system/node-sdk/browser';

// 1. Derive a deterministic key pair from the user's wallet signature
//    Same wallet + same message = same key pair, every session
const sig = await wallet.signMessage('Unlock ZKP Vault v1\nThis key encrypts your files.');
const { privateKey, publicKey } = deriveKeyFromSignature(sig);

// 2. Encrypt a file before uploading (client-side, key never leaves browser)
const fileId = '0xabcdef...';
const fileKey  = await deriveFileKey(privateKey, fileId);
const encrypted = await encryptFile(fileKey, new Uint8Array(fileBytes));

// 3. Decrypt a downloaded file
const sameKey  = await deriveFileKey(privateKey, fileId);
const plaintext = await decryptFile(sameKey, encrypted);

// 4. Decrypt a ZK credential secret (to recover n, s for proof generation)
const { n, s } = await decryptSecret(privateKey, claimFile.encryptedSecret);

Key Derivation from Wallet Signature

deriveKeyFromSignature produces a deterministic secp256k1 key pair from any wallet signature. The same wallet address signing the same message will always produce the same key pair — making it the foundation for per-user encryption where keys never leave the client.

Algorithm: privateKey = keccak256(signature_bytes) — identical to:

import { keccak256, hexToBytes } from 'viem';
const privateKey = keccak256(hexToBytes(sig));
// Node.js (synchronous)
import { ZKPCrypto } from '@zkp-system/node-sdk';
const { privateKey, publicKey } = ZKPCrypto.deriveKeyFromSignature(sig);

// Browser / React Native (synchronous)
import { deriveKeyFromSignature } from '@zkp-system/node-sdk/browser';
const { privateKey, publicKey } = deriveKeyFromSignature(sig);

Works with any wallet that supports personal_sign / signMessage:

| Wallet | Platform | |---|---| | MetaMask (browser extension) | Web | | WalletConnect | Web + Mobile | | Privy / Dynamic embedded wallets | Web + Mobile | | Coinbase Wallet | Web + Mobile |


File Crypto

Deterministic AES-256-GCM encryption tied to a user's private key and file ID. Each user gets a unique encryption key per file — no shared secrets.

Node.js (server-side, synchronous)

import { ZKPCrypto } from '@zkp-system/node-sdk';

const { deriveFileKey, encryptFile, decryptFile, generateFileId, computeMetaHashHex } = ZKPCrypto;

// Generate a BN254-field-valid fileId (required for on-chain registration)
const fileId   = generateFileId('report.pdf', Date.now());
const metaHash = computeMetaHashHex('report.pdf', fileBytes.length, Date.now());

// Derive a 32-byte AES key unique to (user, file)
const fileKey = deriveFileKey(privateKey, fileId);
// Key = SHA-256(privateKey_bytes || fileId_bytes)

// Encrypt
const encrypted = encryptFile(fileKey, Buffer.from(fileBytes));
// Wire: IV(12) || authTag(16) || ciphertext(N)

// Decrypt
const plaintext = decryptFile(fileKey, encrypted);

Browser / React Native (async)

import {
  deriveFileKey,
  deriveFileKeyBytes,
  encryptFile,
  decryptFile,
  generateFileId,
  computeMetaHashHex,
} from '@zkp-system/node-sdk/browser';

const fileId    = await generateFileId('report.pdf', Date.now());
const metaHash  = await computeMetaHashHex('report.pdf', size, Date.now());

// CryptoKey variant (for single encrypt/decrypt)
const key       = await deriveFileKey(privateKey, fileId);
const encrypted = await encryptFile(key, plaintextBytes);
const decrypted = await decryptFile(key, encrypted);

// Raw bytes variant (for sharing — pass AES key to another user)
const keyBytes  = await deriveFileKeyBytes(privateKey, fileId);

ECIES — Encrypt / Decrypt Arbitrary Payloads

In addition to the fixed (n, s) credential encryption, the SDK supports ECIES for arbitrary byte payloads. Used for the sharing flow (re-encrypting AES file keys for grantees).

Node.js

import { ZKPCrypto } from '@zkp-system/node-sdk';

const { eciesEncryptRaw, eciesDecryptRaw } = ZKPCrypto;

// Encrypt any payload for a recipient's public key
const aesKey = ZKPCrypto.deriveFileKey(ownerPrivKey, fileId);
const wire   = eciesEncryptRaw(granteePubKey, aesKey);
// Wire: ephPubKey(65) | IV(12) | ciphertext(N) | GCMtag(16)

// Decrypt
const recovered = eciesDecryptRaw(granteePrivKey, wire);

Browser / React Native

import { encryptRaw, decryptRaw } from '@zkp-system/node-sdk/browser';

const wire      = await encryptRaw(granteePubKey, aesKeyBytes);
const recovered = await decryptRaw(granteePrivKey, wire);

Cross-platform compatible: Node eciesEncryptRaw output can be decrypted by browser decryptRaw, and vice versa. Wire format is identical.


File Sharing

Re-encrypt a file's AES key for a grantee, so they can access the file using their own key pair.

Browser (owner re-encrypts for grantee)

import { reencryptForGrantee } from '@zkp-system/node-sdk/browser';

// Owner re-encrypts their AES file key for the grantee's public key
// Result is stored on-chain via ZKPClient.prepareShare()
const encryptedKeyForGrantee = await reencryptForGrantee(
  ownerPrivateKey,
  fileId,
  granteePubKey,
);
// encryptedKeyForGrantee = ECIES(granteePubKey, SHA-256(ownerKey || fileId))
// = 125 bytes (65 + 12 + 32 + 16)

Decrypt a share credential (grantee, on download)

After the owner calls prepareShare(), the grantee receives an encrypted credential bundle in the on-chain ShareGranted event.

// Node.js (server-side)
import { ZKPCrypto } from '@zkp-system/node-sdk';

const credential = ZKPCrypto.decryptShareCredential(
  granteePrivKey,
  encryptedCredentialHex, // from ShareGranted event
);
// credential: { n: bigint, s: bigint, leafIndex: number, encryptedKeyForGrantee: string }

// Use n, s for proof generation
const proof = await client.generateProof({ n: n.toString(), s: s.toString(), ...rest });

// Decrypt the AES file key to decrypt the file blob
const aesKey = ZKPCrypto.eciesDecryptRaw(granteePrivKey, credential.encryptedKeyForGrantee);
// Browser / React Native
import { decryptShareCredential, decryptRaw } from '@zkp-system/node-sdk/browser';

const credential = await decryptShareCredential(granteePrivKey, encryptedCredentialHex);
const aesKey     = await decryptRaw(granteePrivKey, credential.encryptedKeyForGrantee);

Admin Client

import { AdminClient } from '@zkp-system/node-sdk';

const admin = new AdminClient({ baseUrl: 'http://localhost:3002' });

await admin.login('admin', 'password');

// Create an API key (plaintext returned once only)
const key = await admin.createKey({ name: 'my-service', description: 'Service key' });
console.log(key.key); // store securely — never returned again

// List, update, revoke keys
const keys = await admin.listKeys();
await admin.updateKey(keys[0]!.id, { active: false });
await admin.revokeKey(keys[0]!.id);

await admin.logout();

Full Crypto API Reference

Node.js — ZKPCrypto (from @zkp-system/node-sdk)

All functions are synchronous.

Key management

| Function | Description | |---|---| | generateKeyPair() | Generate a fresh secp256k1 key pair | | privateKeyToPublicKey(privKey) | Derive uncompressed public key from private key | | isValidPublicKey(pubKey) | Validate a 0x04-prefixed 65-byte public key | | deriveKeyFromSignature(sig) | Derive key pair from wallet signature via keccak256 |

ECIES

| Function | Description | |---|---| | encryptSecret(pubKey, n, s) | ECIES-encrypt (n, s) credential secrets | | decryptSecret(encHex, privKey) | ECIES-decrypt → { n: bigint, s: bigint } | | eciesEncryptRaw(pubKey, buf) | ECIES-encrypt arbitrary bytes | | eciesDecryptRaw(privKey, encHex) | ECIES-decrypt arbitrary bytes |

File crypto

| Function | Description | |---|---| | deriveFileKey(privKey, fileId) | SHA-256(privKey ‖ fileId) → 32-byte Buffer | | encryptFile(key, plaintext) | AES-256-GCM encrypt, wire: IV(12)‖tag(16)‖ciphertext | | decryptFile(key, encrypted) | AES-256-GCM decrypt, throws on tamper | | generateFileId(filename, ts?) | SHA-256(name+ts) mod BN254 — on-chain compatible | | computeMetaHashHex(name, size, ts) | SHA-256(name+size+ts) mod BN254 — matches publicSignals[6] |

Sharing

| Function | Description | |---|---| | decryptShareCredential(privKey, encHex) | Decrypt on-chain ShareGranted credential bundle |

BN254 field utilities

| Function | Description | |---|---| | isValidFieldElement(n) | Returns true if n ∈ [1, BN254ScalarField) | | assertFieldElement(n, name) | Throws ValidationError if out of range | | parseFieldElement(s, name) | Parse decimal string → validated bigint | | BN254_SCALAR_FIELD | 21888242871839275222246405745257275088548364400416034343698204186575808495617n |


Browser — @zkp-system/node-sdk/browser

All crypto functions are async (Web Crypto API). No node:crypto — works in browsers, React Native, Cloudflare Workers.

Key management (sync)

| Function | Description | |---|---| | generateKeyPair() | Generate secp256k1 key pair via @noble/curves | | privateKeyToPublicKey(privKey) | Derive public key | | isValidPublicKey(pubKey) | Validate public key | | deriveKeyFromSignature(sig) | Derive key pair from wallet signature (keccak256) |

ECIES (async)

| Function | Description | |---|---| | encryptSecret(pubKey, n, s) | ECIES-encrypt (n, s) | | decryptSecret(privKey, encHex) | ECIES-decrypt → Promise<{ n, s }> | | encryptRaw(pubKey, bytes) | ECIES-encrypt arbitrary Uint8Array | | decryptRaw(privKey, encHex) | ECIES-decrypt arbitrary bytes |

File crypto (async)

| Function | Description | |---|---| | deriveFileKey(privKey, fileId) | Returns Promise<CryptoKey> for AES-GCM | | deriveFileKeyBytes(privKey, fileId) | Returns Promise<Uint8Array> (raw 32 bytes) | | encryptFile(key, plaintext) | AES-256-GCM encrypt → Promise<Uint8Array> | | decryptFile(key, encrypted) | AES-256-GCM decrypt → Promise<Uint8Array> | | generateFileId(filename, ts?) | SHA-256(name+ts) mod BN254 | | computeMetaHashHex(name, size, ts) | SHA-256(name+size+ts) mod BN254 |

Sharing (async)

| Function | Description | |---|---| | decryptShareCredential(privKey, encHex) | Decrypt ShareGranted credential bundle | | reencryptForGrantee(ownerKey, fileId, granteePub) | Re-encrypt AES key for a grantee |


Error Handling

All errors extend ZKPError with a .code string:

| Class | .code | When | |---|---|---| | AuthError | AUTH_ERROR | 401 Unauthorized | | RateLimitError | RATE_LIMIT_ERROR | 429 — has .retryAfterMs | | ValidationError | VALIDATION_ERROR | Invalid input (client-side) | | NetworkError | NETWORK_ERROR | Timeout, DNS, connection refused | | NotFoundError | NOT_FOUND_ERROR | 404 Not Found | | ServerError | SERVER_ERROR | 5xx — has .statusCode | | CryptoError | CRYPTO_ERROR | ECIES / ECDH / AES-GCM failure | | TreeFullError | TREE_FULL | Merkle tree at capacity |

import { RateLimitError, ValidationError, CryptoError } from '@zkp-system/node-sdk';

try {
  await client.generateProof(req);
} catch (e) {
  if (e instanceof RateLimitError) {
    await sleep(e.retryAfterMs);
  } else if (e instanceof ValidationError) {
    console.error('Bad input:', e.message);
  } else if (e instanceof CryptoError) {
    console.error('Crypto failure:', e.message);
  }
}

Client Options

const client = new ZKPClient({
  baseUrl: 'http://localhost:3002',  // required
  apiKey: 'your-key',                // required for issueCredential / generateProof
  timeoutMs: 30_000,                 // default: 30s (proof generation can take ~600ms)
  maxRetries: 3,                     // default: 3 (retries on 5xx / network errors)
  retryDelayMs: 200,                 // default: 200ms (exponential backoff with jitter)
});

// Hot-swap API key without creating a new client instance
client.setApiKey('new-key');

ECIES Wire Format

The encryptedSecret in a ClaimFile is a 157-byte ECIES blob:

[ ephPubKey: 65 bytes ] [ IV: 12 bytes ] [ ciphertext: 64 bytes ] [ GCM tag: 16 bytes ]
= 157 bytes total = 0x + 314 hex chars

eciesEncryptRaw / encryptRaw produce the same format for arbitrary payloads:

[ ephPubKey: 65 bytes ] [ IV: 12 bytes ] [ ciphertext: N bytes ] [ GCM tag: 16 bytes ]

KDF: aesKey = SHA-256(ECDH_x_coordinate) — byte-identical to the Go middleware and the testapp's browserCrypto.ts.


File Crypto Wire Format

Encrypted file blobs use the following layout:

[ IV: 12 bytes ] [ authTag: 16 bytes ] [ ciphertext: N bytes ]   ← Node.js
[ IV: 12 bytes ] [ ciphertext+tag: N+16 bytes ]                   ← Browser (Web Crypto appends tag)

Both layouts are produced and consumed by the SDK — decryptFile handles both.


Security Notes

  • Private keys are never logged by the SDK. Pass them in memory only.
  • deriveKeyFromSignature produces keys deterministically — keep your wallet's seed phrase as the ultimate backup.
  • JWT tokens in AdminClient are in-memory only — never written to disk or localStorage.
  • API key plaintexts are returned once (201 response) and never stored by the SDK.
  • BN254 bounds on n and s are validated client-side before any network call.
  • GCM authentication: tampered ciphertext or wrong key throws CryptoError immediately.
  • Runtime deps (@noble/hashes, @noble/curves): zero-dependency, audited by Cure53, used internally by viem and ethers.

Building from Source

npm install
npm run build          # Clean dist/ then build ESM + CJS + .d.ts for Node + Browser
npm run build:check    # TypeScript strict check without building
npm test               # Run all 267 tests
npm run test:coverage  # Coverage (targets: statements ≥90%, functions 100%)
npm run lint           # ESLint (0 errors required)