Integration Guide

Add ZK credential verification to your HashKey Chain dApp in minutes.

Quick Start

Gate any function in your smart contract behind a KYC credential check:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.23;

import {ISemaphore} from "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";

interface IHSKPassport {
    function verifyCredential(
        uint256 groupId,
        ISemaphore.SemaphoreProof calldata proof
    ) external view returns (bool);
}

contract MyDApp {
    IHSKPassport public passport;
    uint256 public constant KYC_GROUP = 25; // KYC_VERIFIED

    constructor(address _passport) {
        passport = IHSKPassport(_passport);
    }

    function kycGatedFunction(
        ISemaphore.SemaphoreProof calldata proof
    ) external {
        // REQUIRED: bind proof to caller to prevent front-running attacks
        require(
            proof.message == uint256(uint160(msg.sender)),
            "proof must be bound to caller"
        );
        require(
            passport.verifyCredential(KYC_GROUP, proof),
            "KYC proof required"
        );
        // Your logic here
    }
}

Frontend: Generate Proofs

import { Identity } from "@semaphore-protocol/identity";
import { Group } from "@semaphore-protocol/group";
import { generateProof } from "@semaphore-protocol/proof";

// 1. Create identity from wallet signature
const signature = await signer.signMessage(
  "HSK Passport: Generate my Semaphore identity"
);
const identity = new Identity(signature);

// 2. Reconstruct the group (from on-chain events or indexer)
const group = new Group();
for (const member of groupMembers) {
  group.addMember(member);
}

// 3. Generate ZK proof — MUST bind proof to caller (prevents front-running)
const callerAddress = await signer.getAddress();
const proof = await generateProof(
  identity,
  group,
  BigInt(callerAddress),  // message = caller — REQUIRED to prevent front-running
  "mint-silver-v1"        // scope — unique per action for sybil resistance
);

// 4. Submit to your contract (which must also verify proof.message == msg.sender)
const tx = await myDApp.kycGatedFunction(proof);

Contract Addresses (Testnet)

NetworkHashKey Chain Testnet (133)
CredentialRegistry0x2026...9De1
HSKPassport0x7d2E...D792
Semaphore0xd09e...CFE9
DemoIssuer0xBf7d...88C3
GatedRWA (hSILVER)0xb695...b9c9
v6 — Credential freshness ZK
FreshnessRegistry0xd251...3938
FreshnessVerifier0x59A0...1394
HSKPassportFreshness0xFF79...5fBb

Credential Groups

Group IDNameDescription
25KYC_VERIFIEDUser has passed standard KYC verification
26ACCREDITED_INVESTORUser is an accredited/professional investor
27HK_RESIDENTUser is a Hong Kong resident
28SG_RESIDENTUser is a Singapore resident
29AE_RESIDENTUser is a UAE resident

How It Works

Semaphore v4

HSK Passport is built on Semaphore v4 by the Privacy & Scaling Explorations team (Ethereum Foundation). Semaphore uses Groth16 zero-knowledge proofs to enable anonymous group membership verification.

Identity Commitments

Each user generates a Semaphore identity (EdDSA keypair) from their wallet signature. The identity commitment (a hash of the public key) is added to a credential group's on-chain Merkle tree by an issuer.

Zero-Knowledge Proofs

When a user needs to prove a credential, they generate a Groth16 proof in their browser (via WASM). The proof demonstrates: "I know a private key whose commitment is a leaf in this Merkle tree" — without revealing which leaf.

On-Chain Verification

The smart contract verifies the proof using the bn128/alt_bn128 elliptic curve precompiles (ecAdd, ecMul, ecPairing) available on HashKey Chain. Verification costs ~241,000 gas.