Skip to content

masaun/ZK-ID-policy-layer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

117 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZK ID Policy Layer

Table of Contents


Overview

ZK ID Policy Layer is a privacy-preserving modular on-chain policy layer.

In this repo, this ZK ID Policy Layer would be applied for RWA (Real World Asset) product's token issuance as one of use case.

Since the ZK ID Policy Layer is the modular on-chain policy layer, custom ZK Proof Verifier contracts (i.e. Accredition Proof Verifier, Age Proof Verifier, Residency Proof Verifier, etc) can be registered to the ZK Proof Verifier Registry contract. Those registered-custom ZK Proof Verifier contracts can be reused by RWA issuers to configure the eligibility and compliance rules as a policy for RWA buyers (RWA investors) - when the RWA issueres would create/issue a new RWA Product in the form of RWA Product token.

And then, it allow RWA issures to check whether RWA buyers is eligible for buying a RWA prodct and they are compliant or not without touching any sensitive data of RWA buyers by using Zero-Knowledge Proof.

  • RWA buyer (RWA investor) prove their identity attributes with Zero-Knowledge Proofs - without revealing the sensitive personal (raw) data of RWA buyer on-chain.
  • RWA issuer can mint only to the eligible and compliant buyers β€” without touching the underlying personal data of RWA buyer on-chain. For RWA issuer, they do not need to store any sensitive personal (raw) data of RWA buyer, which enable to bring the benefit of data minimization as well.

Key capabilities

Capability Description
Privacy-preserving proofs RWA buyers (Investors) generate ZK proofs locally; The ZK proof can be verified via the ZK Proof Verifier on-chain while only nullifier hash will be stored on-chain
Pluggable ZK Proof Verifier Registry Any Noir-compiled proof verifier can be registered without contract upgrades
Per-RWA product policy config Each RWA product independently declares which proof types and threshold values it requires
Replay-attack prevention Poseidon-based nullifier hashs are stored per ZK Proof Verifier ID to prevent from double-submission
Role-based UI Separate dashboards for Admin, RWA Issuer, and RWA Buyer

Technical Stack

Layer Technology
ZK Circuit Language Noir / Nargo v1.0.0-beta.19 (Powered by Aztec)
Proof Backend @aztec/bb.js v4.2.0-aztecnr-rc.2
Proof Runtime (browser / scripts) @noir-lang/noir_js v1.0.0-beta.19
Smart Contracts Solidity ^0.8.28
Contract Dev & Testing Foundry
Blockchain HashKey Chain Testnet (Chain ID 133)
Frontend Framework Next.js 15 (App Router) + React 19
Wallet / Chain Abstraction Reown AppKit + wagmi v2 + viem v2
Script Runtime Bun

Architecture

System Overview

                 +------------------------------+
                 |      Data Sources            |
                 | KYC / Bank / Workday / CEX   |
                 +-------------+----------------+
                               | zkTLS / API / Attestors (⚠️Limitation: This part has not been implemented yet)
                               v
                 +------------------------------+
                 |     ZK Circuit Library       |
                 |  accreditation / age /       |
                 |  credit-score / kyc-tier /   |
                 |  nationality / residency     |
                 +-------------+----------------+
                               | Noir -> UltraHonk proof
                               v
                 +------------------------------+
                 |        ZKIDRegistry          |
                 |  - nullifier storage         |
                 |  - per-user proof records    |
                 |  - on-chain verification     |
                 +-------------+----------------+
                               |
        +----------------------+----------------------+
        v                      v                     v
 +-------------+   +--------------------+   +--------------+
 |ZKProofVerif-|   |  RWAProductFactory |   |  RWAProduct  |
 |ierRegistry  |   |  - issuer approval |   |  - policy    |
 |(pluggable)  |   |  - product creation|   |    config    |
 +-------------+   +--------------------+   |  - mint()    |
                                            +--------------+

NOTE:

  • ⚠️Limitation: The "Data Sources" part above has not been implemented yet. In the e2e script and frontend, the mock source data has been used instead of the actual source data. This part will be implemented using zkTLS protocols (i.e. Primus, NexaID, etc) in the future to retrieve the actual source data.

Smart Contracts

Contract Role
ZKIDRegistry.sol Stores each user's ZK proof records and nullifiers; verifies proofs on submission via the verifier registry
ZKProofVerifierRegistry.sol Registry of deployed Noir-generated verifier contracts, identified by sequential uint8 IDs
RWAProductFactory.sol Manages issuer approval and deploys new RWAProduct instances with their policy configuration
RWAProduct.sol ERC-20-like RWA token that enforces proof policy at mint() time by re-verifying buyer credentials
circuits/ Auto-generated HonkVerifier.sol contracts, one per Noir circuit (e.g. AccreditationVerifier.sol)

ZK Circuits

Each circuit follows the same pattern:

  • private inputs hold the real credential values (Source data, which will be retrieved via zkTLS protocol, etc in the future. Currently, the mock source data is assigned to here)
  • public inputs carry thresholds and the Poseidon nullifier hash.
    The on-chain verifier checks the proof and the registry prevents the same nullifier from being reused.
Circuit Private Inputs Public Inputs
accreditation investor_type, income, net_worth, aum, salts income_threshold, net_worth_threshold, aum_threshold, nullifier_hash
age age, salts age_threshold, nullifier_hash
credit-score credit_score, salts credit_score_threshold, nullifier_hash
kyc-tier kyc_tier, salts kyc_tier_threshold, nullifier_hash
nationality country_code, iso_code, salts excluded_country_codes, excluded_iso_codes, nullifier_hash
residency country_code, iso_code, salts excluded_country_codes, excluded_iso_codes, nullifier_hash

ZK Proof Layer for RWA

In this repo, the ZK Proof Layer is applied to the RWA product's token issurance.

  • RWA issuer can choose the preferrable ZK Proof Verifier contracts (i.e. AccreditationVerifier.sol), which are registered by the Admin user.
  • By choosing the 6 ZK circuits based ZK Proof Verifier contracts above, RWA issuer can check the following eligibility and compliance for RWA buyer (Investor).
        +------------------------------+
        |        Identity Layer        |
        |  - KYC verified              |
        |  - Human (not Sybil)         |
        +-------------+----------------+
                      |
        +-------------v----------------+
        |     Compliance Layer         |
        |  - Jurisdiction              |
        |  - Sanctions / AML           |
        +-------------+----------------+
                      |
        +-------------v----------------+
        |   Financial Eligibility      |
        |  - Balance / inflow          |
        |  - Portfolio size            |
        +-------------+----------------+
                      |
        +-------------v----------------+
        |   Investor Qualification     |
        |  - Accredited investor       |
        |  - Risk profile              |
        +------------------------------+

User Flow

Three roles interact with the system: Admin, RWA Issuer, and RWA Buyer.

[ Admin ]                  [ RWA Buyer ]                [ RWA Issuer ]
     |                             |                          |
     | 1. Register ZKProofVerifier |                          |
     |    contracts to             |                          |
     |    ZKProofVerifierRegistry  |                          |
     |                             |                          |
     | 2. Approve issuer in        |                          |
     |    RWAProductFactory        |                          |
     |                             |                          |
     |                             | 3. Generate ZK proofs    |
     |                             |    (locally in browser / |
     |                             |     script)              |
     |                             |                          |
     |                             |           4. Create new RWAProduct
     |                             |              with required verifier
     |                             |              IDs + thresholds
     |                             |                          |
     |                             | 5. Submit proof batch    |
     |                             |    to ZKIDRegistry       |
     |                             |                          |
     |                             |<---------------------- 6. mint()
     |                             |    RWA token received    |
     |                             |    (only if all proofs   |
     |                             |     pass policy check)   |

Step details

  1. Admin registers verifiers β€” Calls ZKProofVerifierRegistry.registerNewZkVerifier() for each auto-generated Verifier.sol, associating it with a sequential uint8 ID and human-readable threshold labels. (NOTE: Admin can add more new ZKProofVerifer contracts besides the 6 ZK circuits based ZK Proof Verifier contracts above by calling the ZKProofVerifierRegistry.registerNewZkVerifier().)

  2. Admin approves issuer β€” Calls RWAProductFactory.approveIssuer() to whitelist the issuer address.

  3. Buyer generates proofs β€” For each required credential (age, KYC tier, accreditation, etc.), the buyer runs the corresponding Noir circuit locally using @aztec/bb.js. Private inputs never leave the device.

  4. Issuer creates product β€” Calls RWAProductFactory.createNewRWAProduct(), specifying required zkProofVerifierIds and threshold values. A new RWAProduct contract is deployed.

  5. Buyer submits proofs β€” Calls ZKIDRegistry.submitProof() (or submitProofBatch()) for each proof. Each proof is verified on-chain; nullifier hashes are stored to prevent replay.

  6. Issuer mints tokens β€” Calls RWAProduct.mint(buyerAddress, amount). The contract re-reads the buyer's stored proofs from ZKIDRegistry contract and checks every configured verifier's threshold before minting.


DEMO Video

DEMO Video link: https://www.loom.com/share/d325a34398aa4ac48b917988f6b198b0

NOTE:

  • ⚠️Limitation: The "Data Sources" part above has not been implemented yet.
    • Therefore, in the RWA buyer page (localhost:3000/buyer), the randomly generated mock source data has been used by clicking the "Generate Random Input Values" button in the ZK Proof Status panel - instead of the actual source data.
    • This part will be implemented using zkTLS protocols (i.e. Primus, NexaID, etc) in the future to retrieve the actual source data.

Installation

Prerequisites

1. Clone the repository

git clone <repo-url>
cd ZK-ID-policy-layer

2. Compile and build the ZK Circuits

Compile each circuit and generate the on-chain verifier. Run the convenience script inside each circuit folder:

cd circuits

# Compile and build all circuits
sh compile-and-build-all-circuits.sh

Compiled artefacts are written to circuits/<name>/target/.
The auto-generated Verifier.sol files are written to circuits/<name>/target/Verifier.sol.

3. Compile & Deploy Smart Contracts on HashKey Chain testnet

NOTE: Fancet for the HashKey Chain Testnet: https://faucet.hashkeychain.net/faucet

cd contracts

# Install Foundry dependencies
forge install

# Compile
sh compile-contracts.sh

# Deploy to HashKey Chain Testnet
sh scripts/deployments/hashkey-chain-testnet/deploy.sh

Copy the deployed contract addresses into contracts/.env:

cd contracts
cp .env.example .env

Then, adding your own private keys to the following 4 variables:

DEPLOYER_PRIVATE_KEY=<0x...>
ADMIN_PRIVATE_KEY=<0x...>
RWA_ISSUER_PRIVATE_KEY=<0x...>
RWA_BUYER_PRIVATE_KEY=<0x...>

NOTE: ADMIN_PRIVATE_KEY must be same private key with the DEPLOYER_PRIVATE_KEY in the ./contracts/.env file.

4. Install Script Dependencies

cd scripts
bun install

5. Run the Frontend (locally)

cd app
cp .env.example .env

Then fill in the required values in app/.env:

# Reown / WalletConnect Project ID (https://dashboard.reown.com/)
NEXT_PUBLIC_PROJECT_ID=""

# Salt values for ZK proof generation (required)
NEXT_PUBLIC_COMMITMENT_SALT="Your commitment salt here"
NEXT_PUBLIC_NULLIFIER_SALT="Your nullifier salt here"

The contract addresses and network configuration are pre-filled from .env.example. Then run:

npm install
npm run dev

Open http://localhost:3000.


Unit Script

Each script generates a ZK proof for a single circuit.
Script location: scripts/unit/circuits/<circuit-name>/generate-proof.ts

cd scripts

# Accreditation
bun run generate-proof:accreditation

# Age
bun run generate-proof:age

# Credit Score
bun run generate-proof:credit-score

# KYC Tier
bun run generate-proof:kyc-tier

# Nationality
bun run generate-proof:nationality

# Residency
bun run generate-proof:residency

E2E Script - Only ZK Proof generation and On-chain verification (e2e_zkp-generation-and-onchain-verification.ts)

Script location: scripts/e2e/e2e_zkp-generation-and-onchain-verification.ts
Run command:

cd scripts
bun run e2e:zkp-generation-and-onchain-verification

E2E Script - Full scenario (e2e_rwa-workflow.ts)

Script location: scripts/e2e/e2e_rwa-workflow.ts
Run command:

cd scripts
bun run e2e:rwa-workflow

The script exercises the full primary issuance lifecycle across three actor accounts (ADMIN, RWA_ISSUER, RWA_BUYER) read from contracts/.env.

Step 1 β€” RWA Buyer generates and submits all ZK proofs

The buyer account generates six proofs locally (using @aztec/bb.js UltraHonk backend) and submits each to ZKIDRegistry via submitProof():

# Proof Key private inputs Key thresholds
1 AccreditationProof income=250,000, net_worth=1,500,000, aum=0 income >= 200,000 OR net_worth >= 1,000,000
2 AgeProof age (actual value) configurable age_threshold
3 CreditScoreProof credit_score (actual value) configurable credit_score_threshold
4 KycTierProof kyc_tier (actual value) configurable kyc_tier_threshold
5 NationalityProof country_code, iso_code excluded country/ISO lists
6 ResidencyProof country_code, iso_code excluded country/ISO lists

For every proof, a Poseidon-based nullifier hash is computed from the credential values and a random salt, then included as the last public input. ZKIDRegistry stores the nullifier and rejects any duplicate submission.

Step 2 β€” Admin registers ZK Proof Verifier contracts

The admin account calls ZKProofVerifierRegistry.registerNewZkVerifier() for each of the six auto-generated Verifier.sol contracts, associating them with sequential IDs (0-5) and threshold label metadata:

ID Verifier contract Threshold labels
0 AccreditationProofVerifier income_threshold, net_worth_threshold, aum_threshold
1 AgeProofVerifier age_threshold
2 CreditScoreProofVerifier credit_score_threshold
3 KycTierProofVerifier kyc_tier_threshold
4 NationalityProofVerifier excluded_country_codes, excluded_iso_codes
5 ResidencyProofVerifier excluded_country_codes, excluded_iso_codes

Step 3 β€” Admin approves the RWA Issuer

RWAProductFactory.approveIssuer(RWA_ISSUER_ADDRESS)

Step 4 β€” RWA Issuer creates a new RWA Product

The issuer calls RWAProductFactory.createNewRWAProduct() with:

  • name / symbol β€” token metadata
  • zkProofVerifierIds β€” the subset of verifier IDs the product requires
    (e.g. [0, 2, 3, 4, 5] = Accreditation, Credit Score, KYC Tier, Nationality, Residency)
  • zkProofVerifierThresholds β€” parallel array of threshold values per verifier

A new RWAProduct contract is deployed and its address is emitted in the event log.

Step 5 β€” RWA Issuer mints the RWA token to the Buyer

RWAProduct.mint(RWA_BUYER_ADDRESS, amount)

mint() internally calls ZKIDRegistry contract to retrieve the buyer's stored proof for each configured verifier ID, re-runs the policy threshold check, and reverts if any check fails. On success, the buyer's balance is increased.

Actor key mapping

Role Env variable
Admin ADMIN_PRIVATE_KEY
RWA Issuer RWA_ISSUER_PRIVATE_KEY
RWA Buyer RWA_BUYER_PRIVATE_KEY

NOTE: ADMIN_PRIVATE_KEY must be same private key with the DEPLOYER_PRIVATE_KEY in the ./contracts/.env file.


Deployed Contracts

All contracts are deployed on HashKey Chain Testnet (Chain ID 133).

Core Contracts

Contract Address
ZKProofVerifierRegistry 0xc5c0b98b5d9a29da72b28026a2984958b1a3d97f
ZKIDRegistry 0xe06d40a01a583dbbc3eece1463113356271aa3a0
RWAProductFactory 0x858aa18878e08cd942387c5397efeb57ab19af25

ZK Proof Verifier Wrapper Contracts (The wrapper contract of each HonkVerifier contracs below)

Circuit Address
AccreditationProofVerifier 0x677fb51e3c949a9b365e336582d3a87ec551b1e9
AgeProofVerifier 0xc1e640b2c0a49b2bfea59a82e982e3d596d0a407
CreditScoreProofVerifier 0x934fee07952a865cc233a0de404d8c72b9055028
KycTierProofVerifier 0x3b395f1920fddeb3ab0bcd5a1eab9f4b393c4bbc
NationalityProofVerifier 0x896483b8ecb671011c6f58e5ec0b0cf1b71d107d
ResidencyProofVerifier 0x6d92e73faf9b427cb9bf124acb3f15aca22154cf

HonkVerifier Contracts (auto-generated from Noir)

Circuit Address
HonkVerifier for Accreditation circuit 0x306406a640207a05ae454dd5eaaa3805082e90b3
HonkVerifier for Age circuit 0x4dfdbb4761529a6c34da9a92b0582c5484ea8bb2
HonkVerifier for Credit Score circuit 0x9d369e1225fa0a9d252c2b513bd59b8d4ae92905
HonkVerifier for KYC Tier circuit 0x8c208cd8e37ad734c488f20214907222b22677a8
HonkVerifier for Nationality circuit 0x0e5c52239f257bba27ab306a7fcde2668dc989b2
HonkVerifier for Residency circuit 0x389d349389d79ccc4e5be77fbc33aef1be483741

References

ZK circuit:


Smart Contract:


HashKey Chain:


Frontend:

About

ZK ID Policy Layer that manage the custom ZK Proof Verifiers, which can check both eligibility and compliance without leaking investor's sensitive raw data

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors