Skip to main content
For the complete documentation index, see llms.txt

ZK Loan DApp

The ZK Loan example shows how a loan application can be evaluated against private credit data without that data ever leaving the user's machine. A bank-style attestation provider signs the credit profile off-chain; the smart contract verifies the signature inside a zero-knowledge circuit and writes only the loan status and authorized amount to the ledger.

The credit score, monthly income, employment tenure, attestation signature, and user PIN all stay private. The blockchain learns whether the user qualifies for which tier, never why.

What this DApp demonstrates​

This example is intentionally larger than a minimal DApp. It stitches together several Midnight building blocks in one place:

  • Witness-provided private inputs: TypeScript code feeds credit data into the zero-knowledge prover at proving time without exposing it on-chain.
  • Schnorr-on-Jubjub signature verification inside a circuit: a hand-rolled Compact module (to be replaced by a standard-library verifier once one ships) proves the credit data came from a registered attestation provider.
  • PIN-derived on-chain identity: a witness-supplied user secret is hashed with a secret PIN to produce a per-user public key. The key reveals neither the secret nor the PIN, but it is a pseudonym, not an unlinkable identity. All of a user's loans sit under that one key in loans, the attestation provider signs a hash of it with the credit data, and changePin names the old and new keys in the same transaction.
  • Two-phase approval flow: requests above the user's tier cap are stored as Proposed and require a follow-up respondToLoan call to accept or decline.
  • Witness-derived admin and identity: caller identity (both admin and per-user) is derived from a 32-byte witness secret. ownPublicKey() is not used by the contract. Only the holder of the admin's secret can register providers, blacklist users, or rotate the admin role; only the holder of a user's secret can request, accept, or decline that user's loans.

Project structure​

The example lives in examples/zk-loan in the midnight-examples monorepo:

examples/zk-loan/
├── contract/
│ ├── zk-loan.compact # Loan logic, eligibility tiers, ledger
│ ├── schnorr.compact # In-circuit signature verification
│ ├── witnesses.ts # Private state and witnesses (TypeScript)
│ └── index.ts # The compiled contract, with witnesses attached
├── attestation-api/ # REST server signing credit data
│ └── {signing,server,index,types}.ts
├── src/
│ ├── test/zk-loan.test.ts # End-to-end suite against a Midnight network
│ ├── test/zk-loan.sim.test.ts # Contract unit tests, in-memory simulator
│ └── {config,providers,wallet}.ts # Shared test harness
├── ui/ # React + Vite UI (Lace wallet, Preprod by default)
└── compose.yml # Local node + indexer + proof server

Prerequisites​

  • Node.js 22: the SDK uses Iterator helpers, so Node 20 crashes at the first sync
  • Corepack, for Yarn 4: corepack enable
  • Docker and Docker Compose, for the local network
  • Compact toolchain: install with the compact devtool, then run compact update 0.31.1 and check with compact compile --version
  • Midnight Lace wallet, for the UI only. See the installation guide

The example is verified against Compact language 0.23, compiler 0.31.1, @midnight-ntwrk/midnight-js-* 4.1.1 and wallet SDK 1.2.0. The local network in compose.yml runs midnightntwrk/midnight-node:1.0.0, midnightntwrk/indexer-standalone:4.3.3 and midnightntwrk/proof-server:8.1.0.

Set it up​

1. Install and compile​

git clone https://github.com/midnightntwrk/midnight-examples.git
cd midnight-examples
yarn install && yarn compile
cd examples/zk-loan

yarn compile generates contract/managed/zk-loan/: the JavaScript bindings, the prover and verifier keys, and the ZK IR.

2. Run the full flow on a local network​

yarn env:up # node, indexer and proof server from compose.yml
yarn wait:dust # wait until the test wallet can pay fees
yarn test:local

The suite starts the attestation API in-process, then deploys the contract, registers the API's key as admin, requests loans for two borrowers across the tiers, and checks that tampered credit data, non-admin calls, blacklisted borrowers and an ex-admin are rejected. Every successful call generates a real proof, so it takes several minutes. Stop the network with yarn env:down.

The contract's simulator tests need no network:

yarn test:sim

3. Run the attestation API​

The attestation API signs credit data with a Schnorr signature on Jubjub; the contract verifies the signature inside the ZK circuit.

PROVIDER_SECRET_KEY="$(node -e 'console.log(require("crypto").randomBytes(32).toString("hex"))')" \
yarn attestation:start

It listens on port 4000 (set PORT to change it) and prints the Provider ID (default 1) and the public key's x and y: the values an admin registers on-chain with registerProvider. Keep the PROVIDER_SECRET_KEY. Without it, every restart generates a new key and invalidates the on-chain registration.

4. Run the UI​

The UI in ui/ connects through the Midnight Lace wallet, on Preprod by default. Lace can't balance or sign transactions for the local undeployed chain, so use the test suite for local iteration.

cd ui
yarn dev # http://localhost:5173

The UI can deploy or join a contract, request loans (fetching the signature from the attestation API in step 3) and accept or decline proposals. It can't register the attestation provider. On a contract you deploy from the UI, nothing else can either: the admin role comes from a secret that lives only in that browser tab's memory, and the UI can't export it. So every loan request on a UI-deployed contract fails with Attestation provider not registered. To see a loan go through end to end, use the test suite in step 2. A page refresh drops the secret too, and with it your loans and the admin role. See the UI README for the settings and the browser-specific details.

Learn how it works​

The three-part tutorial walks through this example's code: the witness/disclose split, the Schnorr verification inside the circuit, the PIN-derived identity, and the on-chain test suite.