ZK-powered selective disclosure for Canton Network institutional holdings on Mina Protocol. A Canton validator signs an attestation about a party's CIP-56 token balance; an o1js ZkProgram verifies the signature in zero knowledge and proves "balance >= threshold."
- A Canton-registered validator key signed a specific balance claim
- The claimed balance exceeds a threshold
- The claim is bound to a specific instrument, timestamp, and Mina wallet
The exact balance, Canton party ID, and ECDSA signature remain private.
flowchart TD
A[Canton Validator\nLedger API] -->|GetActiveContracts| B[Attestation Service]
B -->|192-byte payload\nECDSA-secp256k1 signature| C[ZK Proof Generator]
C -->|proof.json ~1.2 KB| D[Verifier]
subgraph B[Attestation Service]
B1[Read holdings from Canton]
B2[Construct canonical payload]
B3[SHA-256 + ECDSA sign]
B1 --> B2 --> B3
end
subgraph C[ZK Proof Generator - o1js ZkProgram]
C1[SHA-256 hash payload in-circuit]
C2[Verify ECDSA signature in-circuit]
C3[Assert balance >= threshold]
C1 --> C2 --> C3
end
subgraph D[On-Chain Verifier - Mina SmartContract]
D1[Verify ZkProgram proof]
D2[Assert validator key matches on-chain state]
end
- 51,654 rows (within 65,536 Kimchi limit, no chunking needed)
- Compilation: ~45s
- Proof generation: ~20s
npm install
npm test # run all tests
npm run test:unit # payload + circuit testsRequires a running Canton LocalNet (see canton/ directory).
# Set env vars
export PRIVATE_KEY=<hex-encoded secp256k1 private key>
export CANTON_API_URL=http://localhost:2975
# Start service
npm run service# Request attestation
npx tsx src/cli/index.ts attest \
--party-id "participant1::abc123" \
--instrument-id "canton-coin::usd" \
--mina-key "aabbcc..." \
-o attestation.json
# Generate ZK proof
npx tsx src/cli/index.ts prove \
-a attestation.json \
-t 1000000000000 \
-o proof.json
# Verify proof (off-chain)
npx tsx src/cli/index.ts verify -p proof.jsonPrerequisites:
- A funded Mina devnet account (get testnet MINA from the faucet)
- The validator key hash (Poseidon hash of the validator's secp256k1 public key)
# Deploy the AttestationVerifier contract
npx tsx src/cli/index.ts deploy \
--sender-key <base58-private-key> \
--validator-key-hash <field-value> \
--mina-url https://api.minascan.io/node/devnet/v1/graphql
# The command prints the zkApp address on successOnce deployed, proofs can be submitted on-chain by calling the verifyAttestation method on the deployed contract. The contract verifies the ZkProgram proof and checks that the validator key hash matches its on-chain state.
Uses cn-quickstart:
git clone https://github.com/digital-asset/cn-quickstart
cd quickstart
make setup && make build && make startThen generate the attestation key:
cat canton/init-attestation-key.sh
# Run the printed commands in Canton console (make canton-console)src/
common/ # Provable types (Secp256k1, EcdsaSig, PayloadBytes)
attestation/ # Canton client, payload, signing, HTTP service
circuit/ # ZkProgram (ECDSA verify + SHA-256 + balance check)
contract/ # SmartContract on-chain verifier (AttestationVerifier)
cli/ # Commander-based CLI (attest, prove, verify, deploy)
test/ # Payload, circuit, and on-chain integration tests
canton/ # Canton setup scripts
This is not trustless proof of reserves. It is accountable, selectively-disclosed proof of reserves.
The ZK proof does not prove the signed balance matches Canton's ledger. What it provides:
- Canton consensus ensures real balances (holdings require validated transactions)
- Ledger API is the authoritative source (attestation service reads directly)
- Validator identity creates accountability (key registered in Canton topology)
- ZK proof adds privacy and verifiability (exact balance stays hidden)
The vulnerability: a malicious operator could modify the attestation service to sign a different balance. This is the oracle problem. Mitigations include legal liability, auditor verification, and future multi-validator attestation.
- o1js - ZK circuit framework
- @noble/curves - secp256k1 signing (off-chain)
- @noble/hashes - SHA-256 hashing (off-chain)
- commander - CLI framework
- express - HTTP service