Contributing to zk-id
Thank you for your interest in contributing to zk-id! This guide will help you understand the project structure, development workflow, and how to make meaningful contributions.
Table of Contents
- Prerequisites
- Getting Started
- Monorepo Structure
- Development Workflow
- Adding New Features
- Testing Strategy
- Code Style Guidelines
- Git Workflow
- Pull Request Process
- Release Process
Prerequisites
Required
- Node.js 20+ β Check with
node --version - npm 8+ β Check with
npm --version - Git β For version control
Optional (for circuit development)
- circom 0.5.46+ β Circuit compiler (installation)
- Rust toolchain β Required by circom (rustup.rs)
- snarkjs β Installed automatically via npm
Getting Started
1. Fork and Clone
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/zk-id.git
cd zk-id
# Add upstream remote
git remote add upstream https://github.com/star7js/zk-id.git
2. Install Dependencies
# Install all workspace dependencies
npm install
This installs dependencies for all packages in the monorepo using npm workspaces.
3. Build All Packages
# Build in dependency order
npm run build
Build order matters! The build script compiles packages in the correct order:
@zk-id/circuits(no TypeScript build, just circuits)@zk-id/core(depended on by all other packages)@zk-id/sdk(depends on core)@zk-id/issuer(depends on core)@zk-id/redis(depends on core)@zk-id/contracts(depends on core, circuits)@zk-id/example-web-app(depends on core, sdk, issuer)
4. Run Tests
# Run all tests
npm test
# Run tests for a specific package
npm test --workspace=@zk-id/core
npm test --workspace=@zk-id/sdk
5. Start the Demo
# From repository root
npm start --workspace=@zk-id/example-web-app
# Or from examples/web-app/
cd examples/web-app
npm start
Monorepo Structure
zk-id uses npm workspaces for monorepo management. All packages share a single node_modules and package-lock.json.
zk-id/
βββ packages/ # Published packages
β βββ circuits/ # @zk-id/circuits (Circom ZK circuits)
β βββ core/ # @zk-id/core (core cryptographic library)
β βββ sdk/ # @zk-id/sdk (client & server SDK)
β βββ issuer/ # @zk-id/issuer (credential issuance)
β βββ redis/ # @zk-id/redis (Redis storage backends)
β βββ contracts/ # @zk-id/contracts (Solidity verifiers)
βββ examples/ # Example applications (not published)
β βββ web-app/ # @zk-id/example-web-app (demo)
βββ docs/ # Documentation
βββ .github/ # GitHub Actions, Dependabot config
βββ package.json # Root package.json with workspaces config
Understanding npm Workspaces
Workspaces are defined in the root package.json:
{
"workspaces": ["packages/*", "examples/*"]
}
Benefits:
- Shared dependencies (single
node_modules) - Cross-package linking (no
npm linkneeded) - Unified commands (
npm testruns tests for all packages)
Working with workspaces:
# Install a dependency in a specific workspace
npm install <package> --workspace=@zk-id/core
# Run a script in a specific workspace
npm run build --workspace=@zk-id/core
# Run a script in all workspaces
npm run test --workspaces
Package Dependencies
@zk-id/circuits (standalone, no deps)
β
@zk-id/core (depends on circuits for artifacts)
β
βββ @zk-id/sdk (depends on core)
βββ @zk-id/issuer (depends on core)
βββ @zk-id/redis (depends on core)
βββ @zk-id/contracts (depends on core, circuits)
β
@zk-id/example-web-app (depends on core, sdk, issuer)
Development Workflow
Daily Development
-
Pull latest changes
git checkout main git pull upstream main -
Create a feature branch
git checkout -b feature/your-feature-name -
Make changes
- Edit code in relevant package(s)
- Add tests for new functionality
- Update documentation
-
Build affected packages
# Build specific package npm run build --workspace=@zk-id/core # Or rebuild everything npm run build -
Run tests
# Test specific package npm test --workspace=@zk-id/core # Test everything npm test -
Commit and push
git add . git commit -m "Add feature: description" git push origin feature/your-feature-name
Rebuilding After Changes
When to rebuild:
- Changed TypeScript code in
packages/coreβ rebuild core + dependent packages - Changed circuits in
packages/circuitsβ recompile circuits + rebuild packages using them - Changed SDK β rebuild SDK only (unless API changed)
Quick rebuild commands:
# Rebuild just one package
npm run build --workspace=@zk-id/core
# Rebuild core and all dependents
npm run build --workspace=@zk-id/core && \
npm run build --workspace=@zk-id/sdk && \
npm run build --workspace=@zk-id/issuer && \
npm run build --workspace=@zk-id/redis
# Nuclear option: rebuild everything
npm run build
Adding New Features
Adding a New Circuit
-
Create the circuit file
# In packages/circuits/src/ touch packages/circuits/src/my-new-circuit.circom -
Write the circuit
pragma circom 2.1.6; template MyNewCircuit() { signal input privateInput; signal output publicOutput; // Circuit logic here } component main = MyNewCircuit(); -
Add to compilation script Edit
packages/circuits/scripts/compile.shto include your circuit. -
Compile and setup
npm run compile --workspace=@zk-id/circuits npm run setup --workspace=@zk-id/circuits -
Add tests Create
packages/circuits/test/my-new-circuit.test.js -
Update documentation
- Add circuit to
packages/circuits/README.md - Update
docs/CIRCUIT-COMPLEXITY.mdwith constraint counts - Update
docs/CIRCUIT-DIAGRAMS.mdif applicable
- Add circuit to
Adding a New Package
-
Create package directory
mkdir -p packages/my-new-package/src cd packages/my-new-package -
Create package.json
{ "name": "@zk-id/my-new-package", "version": "0.6.0", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "test": "mocha" }, "dependencies": { "@zk-id/core": "^0.7.0" } } -
Create tsconfig.json Copy from another package and adjust paths.
-
Add to build script Update root
package.jsonbuild script to include your package. -
Create README.md Follow the format from existing packages.
Adding Core Functionality
When adding features to @zk-id/core:
- Add type definitions in
src/types.ts - Implement functionality in appropriate module (
src/prover.ts,src/verifier.ts, etc.) - Export from index in
src/index.ts - Add tests in
test/ - Update README with new API
- Rebuild dependent packages
Testing Strategy
Unit Tests
Each package has its own test suite:
# Core library tests
npm test --workspace=@zk-id/core
# Circuit tests
npm test --workspace=@zk-id/circuits
# SDK tests
npm test --workspace=@zk-id/sdk
Test Structure
packages/core/
βββ src/
β βββ prover.ts
β βββ verifier.ts
βββ test/
βββ prover.test.ts
βββ verifier.test.ts
Writing Tests
Use Mocha + Chai for TypeScript tests:
import { expect } from 'chai';
import { generateAgeProof } from '../src/prover';
describe('generateAgeProof', () => {
it('should generate a valid proof', async () => {
const proof = await generateAgeProof(/* ... */);
expect(proof).to.have.property('proof');
expect(proof.proof).to.be.a('string');
});
});
Use Mocha + circom_tester for circuit tests:
const { expect } = require('chai');
const wasm_tester = require('circom_tester').wasm;
describe('MyCircuit', () => {
it('should compute correctly', async () => {
const circuit = await wasm_tester('src/my-circuit.circom');
const witness = await circuit.calculateWitness({ input: 42 });
await circuit.checkConstraints(witness);
});
});
Test Coverage
While we donβt enforce strict coverage targets, aim for:
- Core functionality: 80%+ coverage
- Critical paths: 100% coverage
- Edge cases: Well documented tests
Code Style Guidelines
TypeScript
- Use strict mode:
"strict": truein tsconfig.json - Explicit types: Avoid
any, prefer explicit types - Async/await: Prefer over raw promises
- Error handling: Use try/catch, validate inputs
Example:
// Good
export async function generateProof(credential: Credential, minAge: number): Promise<AgeProof> {
validateCredential(credential);
validateMinAge(minAge);
try {
const proof = await snarkjs.groth16.fullProve(/* ... */);
return formatProof(proof);
} catch (error) {
throw new Error(`Proof generation failed: ${error.message}`);
}
}
// Bad
export async function generateProof(cred: any, age: any): Promise<any> {
return await snarkjs.groth16.fullProve(/* ... */);
}
Circom
- Use latest Circom version: 2.1.6+
- Include pragma:
pragma circom 2.1.6; - Comment complex logic: Explain non-obvious constraints
- Minimize constraints: Optimize for proof size and speed
Example:
pragma circom 2.1.6;
include "circomlib/circuits/poseidon.circom";
include "circomlib/circuits/comparators.circom";
// Verifies age >= minAge without revealing exact age
template AgeVerify() {
signal input birthYear;
signal input currentYear;
signal input minAge;
// Calculate age
signal age;
age <== currentYear - birthYear;
// Check age >= minAge
component ageCheck = GreaterEqThan(8);
ageCheck.in[0] <== age;
ageCheck.in[1] <== minAge;
ageCheck.out === 1;
}
Formatting
We use ESLint and Prettier for automated code quality and formatting:
# Lint code
npm run lint
# Auto-fix linting issues
npm run lint:fix
# Format code
npm run format
# Check formatting without modifying
npm run format:check
Style rules:
- Indentation: 2 spaces (no tabs)
- Line length: Max 100 characters (soft limit)
- Trailing commas: Use in multiline arrays/objects
- Semicolons: Required
- Quotes: Single quotes for strings
- Arrow functions: Prefer over function expressions
Editor integration:
- Install ESLint and Prettier extensions for your editor
- Enable βFormat on Saveβ for automatic formatting
- ESLint will highlight issues in real-time
Pre-commit hooks:
- Prettier automatically formats staged files
- ESLint checks run before commit
- Hooks configured via
.husky/directory
Documentation
- JSDoc comments for public APIs
- README for each package
- Inline comments for complex logic only
Example:
/**
* Generates a zero-knowledge proof that the credential holder is at least minAge years old
*
* @param credential - The user's credential (private)
* @param minAge - The minimum age requirement (public)
* @param nonce - Nonce for replay protection (public)
* @param requestTimestampMs - Request timestamp in milliseconds (public)
* @param wasmPath - Path to the compiled circuit WASM file
* @param zkeyPath - Path to the proving key
* @returns An AgeProof that can be verified without revealing the birth year
*/
export async function generateAgeProof(/* ... */): Promise<AgeProof> {
// Implementation
}
Git Workflow
Branch Naming
feature/descriptionβ New featuresfix/descriptionβ Bug fixesdocs/descriptionβ Documentation changesrefactor/descriptionβ Code refactoringtest/descriptionβ Test additions/changes
Commit Messages
Follow the Conventional Commits style:
<type>: <description>
[optional body]
[optional footer]
Types:
feat:β New featurefix:β Bug fixdocs:β Documentation changesrefactor:β Code refactoringtest:β Adding/updating testschore:β Maintenance tasks
Examples:
feat: Add nullifier circuit for sybil resistance
Implement nullifier computation circuit that generates unique
nullifiers per credential and scope, preventing double-spending
and enabling sybil-resistant applications.
Closes #123
fix: Correct nonce validation in verifier
The verifier was incorrectly rejecting valid nonces due to
BigInt comparison issues. Fixed by using string comparison.
Fixes #456
Keeping Your Branch Updated
# Fetch upstream changes
git fetch upstream
# Rebase your branch on upstream/main
git checkout feature/your-feature
git rebase upstream/main
# Push to your fork (force push after rebase)
git push origin feature/your-feature --force-with-lease
Pull Request Process
Before Submitting
- All tests pass (
npm test) - All packages build (
npm run build) - Code passes linting (
npm run lint) - Code is formatted (
npm run format) - Documentation updated (README, inline comments)
- No console.logs or debug code (except in tests)
- Commit messages are clear and descriptive
- Type errors resolved (
npm run typecheckif available)
Submitting a PR
- Push your branch to your fork
- Open a PR on GitHub against
main - Fill out the PR template completely
- Link related issues (Closes #123)
PR Title Format
Use the same format as commit messages:
feat: Add BBS selective disclosure support
fix: Resolve circuit compilation on Linux
docs: Update GETTING-STARTED guide
PR Description
Include:
- What: What changes does this PR make?
- Why: Why are these changes needed?
- How: How do the changes work?
- Testing: How did you test this?
- Screenshots: For UI changes
Review Process
- Automated checks run (tests, builds)
- Maintainers review your code
- Address feedback by pushing new commits
- Squash and merge when approved
After Merge
- Delete your branch (GitHub does this automatically)
- Pull latest main
git checkout main git pull upstream main - Delete local branch
git branch -d feature/your-feature
Release Process
(For maintainers)
Version Numbering
We follow Semantic Versioning:
- Major (1.0.0): Breaking changes, post-audit
- Minor (0.1.0): New features, backwards compatible
- Patch (0.0.1): Bug fixes, backwards compatible
Pre-1.0 Status
Currently at version 0.6.0 (pre-release):
- APIs may change
- Not recommended for production use
- Development Powers of Tau (not production-ready)
- No npm publishing until 1.0.0
Release Checklist (Future)
- All tests pass
- Documentation updated
- CHANGELOG.md updated
- Version bumped in all package.json files
- Git tag created
- GitHub release created
- npm packages published (when ready)
Getting Help
Resources
- Documentation: docs/
- README files: Each package has detailed README
- GETTING-STARTED: Getting Started Guide
- Architecture: Architecture Documentation
- Protocol: Protocol Specification
Communication
- GitHub Issues: Bug reports, feature requests
- GitHub Discussions: Questions, ideas, community
- Pull Requests: Code contributions
Common Questions
Q: Why wonβt my circuits compile?
A: Ensure you have circom and Rust installed. Check npm run compile:circuits output for errors.
Q: Tests fail after updating dependencies?
A: Rebuild all packages: npm run build
Q: How do I test changes across multiple packages?
A: Use npm link isnβt needed with workspaces β changes are automatically reflected after rebuild.
Q: Should I update package-lock.json? A: Yes, if you add/update dependencies. Commit the updated lockfile.
Code of Conduct
Be respectful, inclusive, and constructive. Weβre all here to build great software together.
- Be patient with newcomers
- Provide constructive feedback
- Focus on the code, not the person
- Assume good intentions
License
By contributing to zk-id, you agree that your contributions will be licensed under the Apache-2.0 License.
Thank you for contributing to zk-id! Your efforts help make privacy-preserving identity verification accessible to everyone.