Getting Started with zk-id
This guide walks you through everything you need to know to integrate zk-id into your application, from initial setup to production deployment.
Table of Contents
- Prerequisites
- Part 1: Initial Setup
- Part 2: Running the Demo
- Part 3: Setting Up as an Issuer
- Part 4: Setting Up as a User (Wallet)
- Part 5: Setting Up as a Verifier (Website)
- Part 6: Production Deployment
- Troubleshooting
Prerequisites
Before you begin, ensure you have:
- Node.js 20+ — Check with
node --version - npm 8+ — Check with
npm --version - Git — For cloning the repository
Optional for building circuits from source:
- circom 0.5.46+ — Circuit compiler (installation guide)
- Rust toolchain — Required by circom (rustup.rs)
Part 1: Initial Setup
1.1 Clone the Repository
git clone https://github.com/star7js/zk-id.git
cd zk-id
1.2 Install Dependencies
From the repository root:
npm install
This installs dependencies for all packages in the monorepo.
1.3 Download Circuit Artifacts (First Time Only)
Circuit artifacts (.wasm, .zkey) are hosted on GitHub Releases to keep the repo lightweight. Download them with the provided script:
# Download wasm + zkey (~18 MB) — required for proof generation
bash scripts/download-artifacts.sh
# Also download ptau ceremony files (~96 MB) — only needed to rebuild from source
bash scripts/download-artifacts.sh --all
Rebuilding from source (optional — requires circom + Rust):
# Compile .circom files → .wasm + .r1cs
npm run compile:circuits
# Run trusted setup (generates .zkey + verification_key.json)
npm run --workspace=@zk-id/circuits setup
1.4 Build All Packages
npm run build
This compiles TypeScript for all packages.
1.5 Run Tests
npm test
You should see all tests passing (circuits, contracts, core, issuer, sdk, redis).
Part 2: Running the Demo
The fastest way to see zk-id in action is to run the example web application.
2.1 Start the Demo Server
From the repository root:
npm start --workspace=@zk-id/example-web-app
Or from examples/web-app/:
cd examples/web-app
npm start
2.2 Open the Demo
Navigate to http://localhost:5050 in your browser.
2.3 Try the Workflow
-
Issue a Credential
- Enter birth year, month, day (e.g., 1995-06-15) and nationality (e.g., 840 for USA)
- Click “Issue Credential”
- The credential is stored in browser memory
-
Verify Age
- Select minimum age (e.g., 18)
- Click “Verify Age”
- Watch the browser generate a ZK proof locally (~5 seconds)
- Server verifies the proof ✓
-
Test Revocation
- Click “Revoke Credential”
- Try to verify again → should fail (credential revoked)
See the example app README for more details.
Part 3: Setting Up as an Issuer
Issuers are trusted entities (governments, banks, employers) that verify user identity and issue signed credentials.
3.1 Choose Your Signature Scheme
| Scheme | Best For | Verification | Circuit Size |
|---|---|---|---|
| Ed25519 | Most cases | Off-chain (fast) | N/A |
| BabyJub EdDSA | Trustless on-chain | In-circuit (~15s) | ~20k constraints |
| BBS+ | Selective disclosure | Off-chain (fast) | N/A |
For most use cases, start with Ed25519.
3.2 Basic Issuer Setup (Ed25519)
import { CredentialIssuer } from '@zk-id/issuer';
// For development/testing only
const issuer = CredentialIssuer.createTestIssuer('Demo Issuer');
// Issue a credential after verifying user identity
const credential = await issuer.issueCredential(
1995, // birth year from verified ID
6, // birth month (1–12)
15, // birth day (1–31)
840, // ISO 3166-1 numeric: 840 = USA
'user-123', // optional user identifier for audit
);
console.log('Issued credential:', credential.id);
3.3 Production Issuer Setup
NEVER use createTestIssuer() in production — it generates ephemeral keys.
Use file-based or envelope-encrypted key management:
import { FileKeyManager, ManagedCredentialIssuer } from '@zk-id/issuer';
import { ConsoleAuditLogger } from '@zk-id/core';
// Load keys from PEM files
const keyManager = FileKeyManager.fromPemFiles(
'Production Issuer Name',
'./config/issuer-private-key.pem',
'./config/issuer-public-key.pem',
);
const issuer = new ManagedCredentialIssuer(
keyManager,
new ConsoleAuditLogger(), // Replace with real logging in production
);
// Issue credentials
const credential = await issuer.issueCredential(
birthYear,
birthMonth,
birthDay,
nationality,
userId,
);
3.4 Key Generation
Generate production Ed25519 keys:
# Generate private key
openssl genpkey -algorithm ED25519 -out issuer-private-key.pem
# Extract public key
openssl pkey -in issuer-private-key.pem -pubout -out issuer-public-key.pem
Security: Store private keys in HSM, AWS KMS, or Azure Key Vault for production.
3.5 Enable Revocation
import { InMemoryRevocationStore } from '@zk-id/core';
// For production, use RedisRevocationStore from @zk-id/redis
const revocationStore = new InMemoryRevocationStore();
issuer.setRevocationStore(revocationStore);
// Revoke a credential
await issuer.revokeCredential(credential.credential.commitment);
See the issuer package README for more details.
Part 4: Setting Up as a User (Wallet)
Users store their credentials in a wallet and generate proofs locally.
4.1 Browser Wallet Setup
import { IndexedDBCredentialStore } from '@zk-id/sdk';
// Create a persistent credential store
const store = new IndexedDBCredentialStore();
// Store a credential
await store.put(credential);
// List all credentials
const credentials = await store.getAll();
// Retrieve a specific credential
const cred = await store.get(credentialId);
4.2 Generate a Proof
import { generateAgeProofAuto } from '@zk-id/core';
// Generate age proof (client-side, in browser)
const proof = await generateAgeProofAuto(
credential,
18, // minAge
'nonce-from-server',
Date.now(),
);
// proof.proof contains the ZK proof (~192 bytes)
// proof.publicSignals contains public values (currentYear, minAge, etc.)
4.3 Proof Generation Performance
Typical browser performance:
- First proof: 5-7 seconds (downloads ~5-10 MB of circuit artifacts)
- Subsequent proofs: 3-5 seconds (artifacts cached)
- WASM download: Cached by browser for 1 hour+
Use Web Workers to avoid blocking the UI during proof generation.
4.4 Backup and Recovery
For credential backup and recovery, use MobileWallet from @zk-id/mobile:
import { MobileWallet } from '@zk-id/mobile';
const wallet = new MobileWallet(store);
// Export credentials as JSON
const backup = await wallet.exportCredentials();
// Import from backup
await wallet.importCredentials(backup);
See the Mobile package README for more details.
Part 5: Setting Up as a Verifier (Website)
Websites verify proofs submitted by users without learning private data.
5.1 Server-Side Setup
import { ZkIdServer } from '@zk-id/sdk';
import { InMemoryNonceStore, InMemoryIssuerRegistry } from '@zk-id/sdk';
import { createPublicKey } from 'crypto';
import { readFileSync } from 'fs';
// Load issuer's public key
const issuerPublicKeyPem = readFileSync('./config/issuer-public-key.pem', 'utf8');
const issuerPublicKey = createPublicKey(issuerPublicKeyPem);
// Create issuer registry
const issuerRegistry = new InMemoryIssuerRegistry([
{
issuer: 'Production Issuer Name',
publicKey: issuerPublicKey,
status: 'active',
},
]);
// Create verification server
const server = new ZkIdServer({
verificationKeyPath: './circuits/age-verify-verification-key.json',
nonceStore: new InMemoryNonceStore({ ttlMs: 300000 }),
issuerRegistry: issuerRegistry,
maxRequestAgeMs: 60000, // Proofs expire after 1 minute
verboseErrors: false, // Don't leak circuit details to clients
});
5.2 Express Integration
import express from 'express';
const app = express();
app.use(express.json());
// Challenge endpoint (optional but recommended)
app.get('/api/challenge', async (req, res) => {
const challenge = await server.createChallenge();
res.json(challenge);
});
// Verification endpoint
app.post('/api/verify-age', async (req, res) => {
try {
const result = await server.verifyProof(req.body, req.ip);
if (result.verified) {
// Age verified! Grant access
res.json({ verified: true });
} else {
res.status(400).json({ verified: false, error: result.error });
}
} catch (error) {
res.status(500).json({ verified: false, error: 'Verification failed' });
}
});
app.listen(5000, () => {
console.log('Verification server running on http://localhost:5000');
});
5.3 Client-Side Integration
import { ZkIdClient } from '@zk-id/sdk';
const client = new ZkIdClient({
verificationEndpoint: 'https://yoursite.com/api/verify-age',
});
// Request age verification
try {
const verified = await client.verifyAge(18);
if (verified) {
console.log('Age verified! User is 18+');
// Grant access to age-restricted content
} else {
console.log('Verification failed');
}
} catch (error) {
console.error('Verification error:', error);
}
5.4 Revocation Checks
To support revocable credentials:
import { PostgresValidCredentialTree } from '@zk-id/sdk';
import { Client } from 'pg';
const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
const validCredentialTree = new PostgresValidCredentialTree(pg, {
schema: 'zkid',
depth: 10,
});
const server = new ZkIdServer({
verificationKeyPath: './circuits/age-verify-revocable-verification-key.json',
validCredentialTree: validCredentialTree,
// ... other config
});
// Expose revocation root for clients
app.get('/api/revocation/root', async (req, res) => {
const rootInfo = await server.getRevocationRootInfo();
res.json(rootInfo);
});
See the SDK package README for more details.
Part 6: Production Deployment
6.1 Production Checklist
Security:
- Use production Powers of Tau ceremony (not dev/test)
- Store issuer keys in HSM, AWS KMS, or Azure Key Vault
- Enable HTTPS/TLS for all endpoints
- Implement proper authentication for credential issuance
- Audit circuits with ZK security experts
- Enable rate limiting with Redis-backed limiter
- Implement comprehensive audit logging
Infrastructure:
- Replace in-memory stores with Redis or Postgres
- Set up monitoring and alerting
- Configure CDN for circuit artifact delivery
- Set up database backups
- Implement graceful shutdown for nonce store cleanup
- Configure CORS properly for cross-origin verification
Performance:
- Enable circuit artifact caching (browser + CDN)
- Use Web Workers for client-side proof generation
- Implement batch verification for high-throughput scenarios
- Monitor verification latency and set SLOs
6.2 Redis Setup (Recommended)
import Redis from 'ioredis';
import {
RedisNonceStore,
RedisIssuerRegistry,
RedisRevocationStore,
RedisRateLimiter,
} from '@zk-id/redis';
const redis = new Redis(process.env.REDIS_URL);
const server = new ZkIdServer({
verificationKeyPath: './verification_key.json',
nonceStore: new RedisNonceStore(redis, { ttlSeconds: 300 }),
issuerRegistry: new RedisIssuerRegistry(redis),
revocationStore: new RedisRevocationStore(redis),
rateLimiter: new RedisRateLimiter(redis, {
limit: 100,
windowMs: 60000,
}),
});
6.3 Postgres Setup
-- Create schema
CREATE SCHEMA IF NOT EXISTS zkid;
-- Valid credential tree (managed by PostgresValidCredentialTree)
-- Tables are auto-created by the SDK
import { PostgresValidCredentialTree } from '@zk-id/sdk';
import { Client } from 'pg';
const pg = new Client({
host: process.env.PG_HOST,
port: 5432,
database: process.env.PG_DATABASE,
user: process.env.PG_USER,
password: process.env.PG_PASSWORD,
ssl: { rejectUnauthorized: false }, // Configure properly for production
});
await pg.connect();
const validCredentialTree = new PostgresValidCredentialTree(pg, {
schema: 'zkid',
depth: 10,
});
// Use in ZkIdServer
const server = new ZkIdServer({
validCredentialTree: validCredentialTree,
// ... other config
});
6.4 Monitoring and Observability
import { ConsoleAuditLogger } from '@zk-id/core';
// Implement custom audit logger
class ProductionAuditLogger extends ConsoleAuditLogger {
async log(event: AuditEvent): Promise<void> {
// Send to your logging service (DataDog, Splunk, etc.)
await yourLoggingService.log(event);
}
}
const server = new ZkIdServer({
auditLogger: new ProductionAuditLogger(),
// ... other config
});
// Listen for verification events
server.onVerification((event) => {
// Send metrics to Prometheus/CloudWatch/etc.
metrics.recordVerification({
claimType: event.claimType,
verified: event.verified,
duration: event.verificationTimeMs,
});
});
6.5 Environment Variables
Example .env file:
# Server
NODE_ENV=production
PORT=5000
LOG_LEVEL=info
# Redis
REDIS_URL=redis://localhost:6379
# Postgres
DATABASE_URL=postgresql://user:password@localhost:5432/zkid
# Keys
ISSUER_PRIVATE_KEY_PATH=/secrets/issuer-private-key.pem
ISSUER_PUBLIC_KEY_PATH=/config/issuer-public-key.pem
# Verification
VERIFICATION_KEY_PATH=/config/age-verify-verification-key.json
NATIONALITY_VERIFICATION_KEY_PATH=/config/nationality-verify-verification-key.json
# Security
REQUIRE_SIGNED_CREDENTIALS=true
MAX_PROOF_AGE_MS=60000
NONCE_TTL_SECONDS=300
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
# CORS
ALLOWED_ORIGINS=https://yoursite.com,https://www.yoursite.com
6.6 Docker Deployment
Example Dockerfile:
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY packages/ ./packages/
# Install dependencies
RUN npm ci --production
# Build packages
RUN npm run build
# Copy configuration
COPY config/ ./config/
# Expose port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD node -e "require('http').get('http://localhost:5000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Start server
CMD ["node", "examples/web-app/dist/server.js"]
6.7 Kubernetes Deployment
See Deployment Guide for Kubernetes manifests and Helm charts.
Troubleshooting
Circuit Compilation Fails
Error: circom: command not found
Solution: Install circom following the official guide.
Proof Generation Fails in Browser
Error: Cannot find module '@zk-id/circuits/build/age-verify.wasm'
Solution: Download circuit artifacts from GitHub Releases:
bash scripts/download-artifacts.sh
Verification Fails with “Invalid proof”
Causes:
- Circuit artifacts mismatch (recompile circuits on all environments)
- Wrong verification key (ensure same version)
- Clock skew (timestamp validation failed)
- Nonce expired or reused
Debug:
const server = new ZkIdServer({
verboseErrors: true, // Enable detailed errors
// ... other config
});
Performance Issues
Slow proof generation:
- Use Web Workers for non-blocking proof generation
- Serve circuit artifacts from CDN with long cache headers
- Consider native mobile apps (faster than browser)
Slow verification:
- Enable batch verification for multiple proofs
- Check database query performance (add indexes)
- Monitor circuit artifact download times
Rate Limiting
Error: Rate limit exceeded
Solution:
- Check
RedisRateLimiterconfiguration - Implement proper user authentication (don’t rely on IP)
- Adjust limits based on your traffic patterns
Next Steps
- Read the Architecture Documentation
- Explore the Protocol Specification
- Review the Threat Model
- Check the Roadmap for upcoming features
- Join the community and contribute!
Package Documentation
- @zk-id/core — Core cryptographic library
- @zk-id/circuits — Zero-knowledge circuits
- @zk-id/sdk — Client and server SDK
- @zk-id/issuer — Credential issuance
- @zk-id/redis — Redis storage backends
- @zk-id/contracts — Solidity verifiers
Getting Help
- Issues: GitHub Issues
- Documentation: docs/
- Example App: examples/web-app/
License
Apache-2.0 - see LICENSE for details.