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

@hazbase/zk

v0.1.0

Published

An SDK helper that integrates with the hazBase backend to ZK (Groth16) KYC/threshold proofs using Poseidon commitments and Merkle membership proofs.

Downloads

8

Readme

@hazbase/zk

npm version License

Overview

@hazbase/zk is a utility toolkit for Poseidon hashing, Merkle trees, and Groth16 proofs.

It is designed to be used together with MultiTrustCredential (MTC) and provides low-level building blocks to create commitment-based proofs (e.g., score ≥ threshold, allowlist membership) with minimal disclosure.

Key concept (important)

In the latest MTC design, the on-chain metric field Metric.leafFull stores the anchor root:

  • anchorRoot: Merkle root (anchor) stored on-chain (Metric.leafFull)
  • leafCommitment: commitment used inside the Merkle leaf (e.g., Poseidon(score, rand)), not stored on-chain
  • merklePath: { root, siblings, pathPos } provided by the issuer to the holder/prover

Proof generation requires anchorRoot + merklePath. The prover should not try to recompute root locally from currentRoot only.

Core capabilities:

  • Poseidon helpers (init, toF, H1/H2/H3, genSalt)
  • Deterministic allowlist Merkle utilities (normalize → deduplicate → sort ascending → pad)
  • Merkle path generation utilities for issuers
  • Groth16 proof generation:
    • generateProof (baseline / threshold comparisons)
    • generateProofAllowlist (allowlist membership)
    • generateProofRange, generateProofDelta (RANGE/DELTA predicates, if enabled in your build)
  • First-class integration with MTC (@hazbase/kit) for on-chain proof flows

Requirements

  • Node.js: 18+ (ESM recommended)
  • Deps: snarkjs, circomlibjs, ethers
  • MTC: use with @hazbase/kit MultiTrustCredentialHelper

Installation

npm i @hazbase/zk

Configuration

This package does not read environment variables directly. Provide paths to circuit assets and network/domain info explicitly from your application.

Domain separation uses:

  • domain = keccak256(abi.encode(chainId, mtcAddress)) mod Fr

So pass chainId and MTC contract address (mtcAddress) when generating proofs/paths.


Quick start (Issuer → Holder → Verifier)

This section demonstrates:

  1. Issuer issues a metric (builds Merkle leaf + anchor root + Merkle path) and stores anchorRoot on-chain.
  2. Holder generates a proof using issuer-provided merklePath (and rand).
  3. Anyone verifies on-chain (no issuer involvement required at verification time).

Assumption: issuer stores rand and can deliver it to the holder when needed (your current operational model).


A) Proof of Threshold (baseline: proveMetric)

import { ethers } from "ethers";
import { PoseidonHelper, genValuesWithAnchor, generateProof } from "@hazbase/zk";
import { MultiTrustCredentialHelper } from "@hazbase/kit";

async function run() {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
  const admin = new ethers.Wallet(process.env.ADMIN_KEY!, provider);
  const issuer = new ethers.Wallet(process.env.ISSUER_KEY!, provider);
  const student = new ethers.Wallet(process.env.STUDENT_KEY!, provider);

  // Deploy & attach MTC
  const { address } = await MultiTrustCredentialHelper.deploy({ admin: admin.address }, admin);
  const mtc = MultiTrustCredentialHelper.attach(address, admin);

  // Register a commitment metric (score) and authorize issuer
  const metricId = ethers.id("exam-score");
  const ROLE = ethers.id("EXAM_SCORE_ROLE");
  await mtc.registerMetric(metricId, "ExamScore", ROLE, true, MultiTrustCredentialHelper.CompareMask.GTE);
  await mtc.contract.grantRole(ROLE, issuer.address);

  // (Issuer) build commitment + insert into issuer Merkle tree to get anchorRoot + merklePath
  const realScore = 80n;

  // In production, issuer maintains a persistent Merkle tree state.
  // Here we demonstrate with an empty tree and index 0 for simplicity.
  const issued = await genValuesWithAnchor({
    score: realScore,
    walletAddress: student.address,
    chainId: 11155111,
    mtcAddress: mtc.address,
    currentRoot: 0n,
    nextIndex: 0
  });

  // Store anchorRoot on-chain (maps to Metric.leafFull)
  await mtc.connect(issuer).mint(student.address, {
    metricId,
    value: 0,
    anchorRoot: issued.anchorRoot, // IMPORTANT: anchorRoot (Merkle root), not leafCommitment
    uri: "",
    expiresAt: 0
  });

  // (Holder) generate proof using issuer-provided merklePath and issuer-provided rand
  // Issuer must deliver:
  //   - issued.merklePath (root/siblings/pathPos)
  //   - issued.rand (for score commitment)
  const proofBundle = await generateProof(
    {
      govId: "X987654",
      name: "Alice Chember",
      dobYMD: 12345678,
      country: 392
    },
    student.address,
    {
      mode: "GTE",
      threshold: 60n,
      score: realScore,
      rand: issued.rand,
      idNull: PoseidonHelper.genSalt(),
      chainId: 11155111,
      mtcAddress: mtc.address,
      merklePath: issued.merklePath
    }
  );

  // (Holder) verify on-chain via baseline proveMetric
  const tokenId = MultiTrustCredentialHelper.tokenIdFor(student.address);
  const { a, b, c } = proofBundle.proof;

  await mtc.connect(student).proveMetric(tokenId, metricId, a, b, c, proofBundle.publicSignals);
}

B) Proof of Allowlist Membership (ZKEx: provePredicate)

This uses the ZKEx flow (provePredicate) and predicate profiles on the MTC contract. You must configure:

  • setPredicateAllowed(metricId, predicateType, true)
  • setPredicateProfile(metricId, predicateType, verifier, signalsLen, anchorIndex, addrIndex, epochIndex, epochCheck, requireMaskZero)
import { ethers } from "ethers";
import { PoseidonHelper, generateProofAllowlist } from "@hazbase/zk";
import { MultiTrustCredentialHelper } from "@hazbase/kit";

async function run() {
  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);
  const admin = new ethers.Wallet(process.env.ADMIN_KEY!, provider);
  const issuer = new ethers.Wallet(process.env.ISSUER_KEY!, provider);
  const alice  = new ethers.Wallet(process.env.ALICE_KEY!, provider);

  const { address } = await MultiTrustCredentialHelper.deploy({ admin: admin.address }, admin);
  const mtc = MultiTrustCredentialHelper.attach(address, admin);

  // Register an allowlist metric (compareMask must be 0 if requireMaskZero=true in profile)
  const metricId = ethers.id("country-code");
  const ROLE = ethers.id("COUNTRY_CODE_ROLE");
  await mtc.registerMetric(metricId, "CountryCode", ROLE, true, MultiTrustCredentialHelper.CompareMask.NONE);
  await mtc.contract.grantRole(ROLE, issuer.address);

  // Configure predicate (ALLOWLIST) on-chain (admin)
  const pred = MultiTrustCredentialHelper.PredicateType.ALLOWLIST;

  await mtc.setPredicateAllowed(metricId, pred, true);

  // Legacy allowlist layout:
  //   [0]=issuerRoot(anchor), [1]=allowRoot, [2]=nullifier, [3]=addr, [4]=statementHash, [5]=leaf
  await mtc.setPredicateProfile(
    metricId,
    pred,
    process.env.ALLOWLIST_VERIFIER_ADDRESS as `0x${string}`,
    6,   // signalsLen
    0,   // anchorIndex
    3,   // addrIndex
    0,   // epochIndex (unused)
    false, // epochCheck
    true   // requireMaskZero
  );

  const allowValues = [392n, 840n, 124n]; // JP/US/CA (example)
  const idNull = PoseidonHelper.genSalt();

  // Issuer must provide issuer-side Merkle path data to the holder/prover.
  // This call demonstrates proof generation; issuer path wiring is application-specific.
  const proofBundle = await generateProofAllowlist({
    list: allowValues,
    policyId: metricId,
    policyVersion: 1,
    addr: alice.address,
    value: 392n,
    salt: PoseidonHelper.genSalt(),
    idNull,
    chainId: 11155111,
    mtcAddress: mtc.address
  });

  const tokenId = MultiTrustCredentialHelper.tokenIdFor(alice.address);
  await mtc.connect(alice).provePredicate(
    tokenId,
    metricId,
    pred,
    proofBundle.proof,
    proofBundle.publicSignals
  );
}

Function reference (Core API)

Poseidon / Field

  • PoseidonHelper.init(): Promise<void>
  • PoseidonHelper.toF(x): bigint
  • PoseidonHelper.H1(x) / H2(a,b) / H3(a,b,c): bigint
  • PoseidonHelper.genSalt(): bigint

Proof generators

  • generateProof(subject, holderAddr, opts)Promise<ProofBundle>

    • Requires:
      • mtcAddress, chainId
      • merklePath (issuer-provided { root, siblings, pathPos })
      • rand if mode != 0 (issuer-provided in your operational model)
  • generateProofAllowlist(args)Promise<{ proof, publicSignals, ... }>

    • Uses mtcAddress for domain separation
    • Produces publicSignals aligned to your allowlist circuit layout

Issuer utilities

  • genScoreCommitment(score, rand?)
  • genValuesWithAnchor(opts)
    • Convenience helper for issuers:
      • Generates a score commitment
      • Builds treeLeaf = Poseidon(leafCommitment, addr, domain)
      • Inserts into a local Merkle tree and returns { anchorRoot, merklePath, rand, ... }

Troubleshooting

  • anchor mism (on-chain revert)
    • You stored the wrong value on-chain. Metric.leafFull must be anchorRoot (Merkle root), not leaf commitment.
  • Proof verifies locally but fails on-chain
    • Check mtcAddress/chainId domain separation parity
    • Ensure merklePath.root equals on-chain Metric.leafFull
    • Ensure addr is uint160 bounded in circuit (should be in your latest circuits)

License

Apache-2.0