{"id":35210088,"url":"https://github.com/hazbase/zk","last_synced_at":"2026-04-06T07:03:06.577Z","repository":{"id":317129049,"uuid":"1066114579","full_name":"hazbase/zk","owner":"hazbase","description":"An SDK helper that integrates with the hazBase backend to ZK (Groth16) KYC/threshold proofs using Poseidon commitments and Merkle membership proofs.","archived":false,"fork":false,"pushed_at":"2025-12-29T15:17:39.000Z","size":20546,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-01T19:31:23.916Z","etag":null,"topics":["backend","blockchain","dapps","ethereum","evm","web3","zero-knowledge","zk"],"latest_commit_sha":null,"homepage":"https://lp.hazbase.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hazbase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-29T03:32:20.000Z","updated_at":"2025-12-29T15:17:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"c1628171-c29c-45e3-ba76-a62c7afc5251","html_url":"https://github.com/hazbase/zk","commit_stats":null,"previous_names":["hazbase/zk"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/hazbase/zk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazbase%2Fzk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazbase%2Fzk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazbase%2Fzk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazbase%2Fzk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hazbase","download_url":"https://codeload.github.com/hazbase/zk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hazbase%2Fzk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31463016,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","blockchain","dapps","ethereum","evm","web3","zero-knowledge","zk"],"created_at":"2025-12-29T17:32:32.288Z","updated_at":"2026-04-06T07:03:06.571Z","avatar_url":"https://github.com/hazbase.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @hazbase/zk\n[![npm version](https://badge.fury.io/js/@hazbase%2Fzk.svg)](https://badge.fury.io/js/@hazbase%2Fzk)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\n## Overview\n`@hazbase/zk` is a utility toolkit for **Poseidon hashing**, **Merkle trees**, and **Groth16 proofs**.\n\nIt 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**.\n\n### Key concept (important)\nIn the latest MTC design, the on-chain metric field `Metric.leafFull` stores the **anchor root**:\n\n- **`anchorRoot`**: Merkle root (anchor) stored on-chain (`Metric.leafFull`)\n- **`leafCommitment`**: commitment used inside the Merkle leaf (e.g., `Poseidon(score, rand)`), **not** stored on-chain\n- **`merklePath`**: `{ root, siblings, pathPos }` provided by the issuer to the holder/prover\n\nProof generation requires `anchorRoot` + `merklePath`. The prover should **not** try to recompute `root` locally from `currentRoot` only.\n\nCore capabilities:\n- Poseidon helpers (`init`, `toF`, `H1/H2/H3`, `genSalt`)\n- Deterministic allowlist Merkle utilities (normalize → deduplicate → sort ascending → pad)\n- Merkle path generation utilities for issuers\n- **Groth16** proof generation:\n  - `generateProof` (baseline / threshold comparisons)\n  - `generateProofAllowlist` (allowlist membership)\n  - `generateProofRange`, `generateProofDelta` (RANGE/DELTA predicates, if enabled in your build)\n- First-class integration with **MTC (@hazbase/kit)** for on-chain proof flows\n\n---\n\n## Requirements\n- **Node.js**: 18+ (ESM recommended)\n- **Deps**: `snarkjs`, `circomlibjs`, `ethers`\n- **MTC**: use with `@hazbase/kit` `MultiTrustCredentialHelper`\n\n---\n\n## Installation\n```bash\nnpm i @hazbase/zk\n```\n\n---\n\n## Configuration\nThis package does **not** read environment variables directly. Provide **paths to circuit assets** and **network/domain info** explicitly from your application.\n\nDomain separation uses:\n- `domain = keccak256(abi.encode(chainId, mtcAddress)) mod Fr`\n\nSo pass `chainId` and **MTC contract address** (`mtcAddress`) when generating proofs/paths.\n\n---\n\n## Quick start (Issuer → Holder → Verifier)\n\nThis section demonstrates:\n1) Issuer issues a metric (builds Merkle leaf + anchor root + Merkle path) and stores **anchorRoot** on-chain.\n2) Holder generates a proof using issuer-provided **merklePath** (and `rand`).\n3) Anyone verifies on-chain (no issuer involvement required at verification time).\n\n\u003e Assumption: issuer stores `rand` and can deliver it to the holder when needed (your current operational model).\n\n---\n\n### A) Proof of Threshold (baseline: `proveMetric`)\n\n```ts\nimport { ethers } from \"ethers\";\nimport { PoseidonHelper, genValuesWithAnchor, generateProof } from \"@hazbase/zk\";\nimport { MultiTrustCredentialHelper } from \"@hazbase/kit\";\n\nasync function run() {\n  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);\n  const admin = new ethers.Wallet(process.env.ADMIN_KEY!, provider);\n  const issuer = new ethers.Wallet(process.env.ISSUER_KEY!, provider);\n  const student = new ethers.Wallet(process.env.STUDENT_KEY!, provider);\n\n  // Deploy \u0026 attach MTC\n  const { address } = await MultiTrustCredentialHelper.deploy({ admin: admin.address }, admin);\n  const mtc = MultiTrustCredentialHelper.attach(address, admin);\n\n  // Register a commitment metric (score) and authorize issuer\n  const metricId = ethers.id(\"exam-score\");\n  const ROLE = ethers.id(\"EXAM_SCORE_ROLE\");\n  await mtc.registerMetric(metricId, \"ExamScore\", ROLE, true, MultiTrustCredentialHelper.CompareMask.GTE);\n  await mtc.contract.grantRole(ROLE, issuer.address);\n\n  // (Issuer) build commitment + insert into issuer Merkle tree to get anchorRoot + merklePath\n  const realScore = 80n;\n\n  // In production, issuer maintains a persistent Merkle tree state.\n  // Here we demonstrate with an empty tree and index 0 for simplicity.\n  const issued = await genValuesWithAnchor({\n    score: realScore,\n    walletAddress: student.address,\n    chainId: 11155111,\n    mtcAddress: mtc.address,\n    currentRoot: 0n,\n    nextIndex: 0\n  });\n\n  // Store anchorRoot on-chain (maps to Metric.leafFull)\n  await mtc.connect(issuer).mint(student.address, {\n    metricId,\n    value: 0,\n    anchorRoot: issued.anchorRoot, // IMPORTANT: anchorRoot (Merkle root), not leafCommitment\n    uri: \"\",\n    expiresAt: 0\n  });\n\n  // (Holder) generate proof using issuer-provided merklePath and issuer-provided rand\n  // Issuer must deliver:\n  //   - issued.merklePath (root/siblings/pathPos)\n  //   - issued.rand (for score commitment)\n  const proofBundle = await generateProof(\n    {\n      govId: \"X987654\",\n      name: \"Alice Chember\",\n      dobYMD: 12345678,\n      country: 392\n    },\n    student.address,\n    {\n      mode: \"GTE\",\n      threshold: 60n,\n      score: realScore,\n      rand: issued.rand,\n      idNull: PoseidonHelper.genSalt(),\n      chainId: 11155111,\n      mtcAddress: mtc.address,\n      merklePath: issued.merklePath\n    }\n  );\n\n  // (Holder) verify on-chain via baseline proveMetric\n  const tokenId = MultiTrustCredentialHelper.tokenIdFor(student.address);\n  const { a, b, c } = proofBundle.proof;\n\n  await mtc.connect(student).proveMetric(tokenId, metricId, a, b, c, proofBundle.publicSignals);\n}\n```\n\n---\n\n### B) Proof of Allowlist Membership (ZKEx: `provePredicate`)\n\nThis uses the ZKEx flow (`provePredicate`) and predicate profiles on the MTC contract. You must configure:\n- `setPredicateAllowed(metricId, predicateType, true)`\n- `setPredicateProfile(metricId, predicateType, verifier, signalsLen, anchorIndex, addrIndex, epochIndex, epochCheck, requireMaskZero)`\n\n```ts\nimport { ethers } from \"ethers\";\nimport { PoseidonHelper, generateProofAllowlist } from \"@hazbase/zk\";\nimport { MultiTrustCredentialHelper } from \"@hazbase/kit\";\n\nasync function run() {\n  const provider = new ethers.JsonRpcProvider(process.env.RPC_URL!);\n  const admin = new ethers.Wallet(process.env.ADMIN_KEY!, provider);\n  const issuer = new ethers.Wallet(process.env.ISSUER_KEY!, provider);\n  const alice  = new ethers.Wallet(process.env.ALICE_KEY!, provider);\n\n  const { address } = await MultiTrustCredentialHelper.deploy({ admin: admin.address }, admin);\n  const mtc = MultiTrustCredentialHelper.attach(address, admin);\n\n  // Register an allowlist metric (compareMask must be 0 if requireMaskZero=true in profile)\n  const metricId = ethers.id(\"country-code\");\n  const ROLE = ethers.id(\"COUNTRY_CODE_ROLE\");\n  await mtc.registerMetric(metricId, \"CountryCode\", ROLE, true, MultiTrustCredentialHelper.CompareMask.NONE);\n  await mtc.contract.grantRole(ROLE, issuer.address);\n\n  // Configure predicate (ALLOWLIST) on-chain (admin)\n  const pred = MultiTrustCredentialHelper.PredicateType.ALLOWLIST;\n\n  await mtc.setPredicateAllowed(metricId, pred, true);\n\n  // Legacy allowlist layout:\n  //   [0]=issuerRoot(anchor), [1]=allowRoot, [2]=nullifier, [3]=addr, [4]=statementHash, [5]=leaf\n  await mtc.setPredicateProfile(\n    metricId,\n    pred,\n    process.env.ALLOWLIST_VERIFIER_ADDRESS as `0x${string}`,\n    6,   // signalsLen\n    0,   // anchorIndex\n    3,   // addrIndex\n    0,   // epochIndex (unused)\n    false, // epochCheck\n    true   // requireMaskZero\n  );\n\n  const allowValues = [392n, 840n, 124n]; // JP/US/CA (example)\n  const idNull = PoseidonHelper.genSalt();\n\n  // Issuer must provide issuer-side Merkle path data to the holder/prover.\n  // This call demonstrates proof generation; issuer path wiring is application-specific.\n  const proofBundle = await generateProofAllowlist({\n    list: allowValues,\n    policyId: metricId,\n    policyVersion: 1,\n    addr: alice.address,\n    value: 392n,\n    salt: PoseidonHelper.genSalt(),\n    idNull,\n    chainId: 11155111,\n    mtcAddress: mtc.address\n  });\n\n  const tokenId = MultiTrustCredentialHelper.tokenIdFor(alice.address);\n  await mtc.connect(alice).provePredicate(\n    tokenId,\n    metricId,\n    pred,\n    proofBundle.proof,\n    proofBundle.publicSignals\n  );\n}\n```\n\n---\n\n## Function reference (Core API)\n\n### Poseidon / Field\n- `PoseidonHelper.init(): Promise\u003cvoid\u003e`\n- `PoseidonHelper.toF(x): bigint`\n- `PoseidonHelper.H1(x) / H2(a,b) / H3(a,b,c): bigint`\n- `PoseidonHelper.genSalt(): bigint`\n\n### Proof generators\n- `generateProof(subject, holderAddr, opts)` → `Promise\u003cProofBundle\u003e`\n  - Requires:\n    - `mtcAddress`, `chainId`\n    - `merklePath` (issuer-provided `{ root, siblings, pathPos }`)\n    - `rand` if `mode != 0` (issuer-provided in your operational model)\n\n- `generateProofAllowlist(args)` → `Promise\u003c{ proof, publicSignals, ... }\u003e`\n  - Uses `mtcAddress` for domain separation\n  - Produces publicSignals aligned to your allowlist circuit layout\n\n### Issuer utilities\n- `genScoreCommitment(score, rand?)`\n- `genValuesWithAnchor(opts)`\n  - Convenience helper for issuers:\n    - Generates a score commitment\n    - Builds `treeLeaf = Poseidon(leafCommitment, addr, domain)`\n    - Inserts into a local Merkle tree and returns `{ anchorRoot, merklePath, rand, ... }`\n\n---\n\n## Troubleshooting\n- **`anchor mism` (on-chain revert)**\n  - You stored the wrong value on-chain. `Metric.leafFull` must be `anchorRoot` (Merkle root), not leaf commitment.\n- **Proof verifies locally but fails on-chain**\n  - Check `mtcAddress`/`chainId` domain separation parity\n  - Ensure `merklePath.root` equals on-chain `Metric.leafFull`\n  - Ensure `addr` is uint160 bounded in circuit (should be in your latest circuits)\n\n---\n\n## License\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhazbase%2Fzk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhazbase%2Fzk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhazbase%2Fzk/lists"}