Inspiration
Every time an Indonesian tenant rents a kost, they hand their landlord a full KTP photo (the national ID card).
The landlord keeps that image in a drawer or phone gallery and rarely cares for it. One copy gets reused across many rentals. Inside it sit the NIK, full name, and address—exposed every time someone emails it, screenshots it, or forwards it along. We wanted to prove eligibility without ever sending the photo.
What it does
ZK-Kost lets a kost tenant verify on their phone and hand the landlord only selective badges: age verified, ID valid, not blacklisted, plus an opaque proofId. The raw KTP photo, NIK, name, and address never leave the device. Open the app, tap Verify Privately, watch a 4-step proof generate on-device (data checked, data hidden, proof generated, proof verified), then land on the landlord screen with three badges and a Before/After toggle. Switch to "Rina · blacklisted" and the banner flips to NOT ACCEPTED—showing the cross-landlord blacklist working.
How we built it
Kotlin Multiplatform for shared logic, Jetpack Compose for UI. The tenant record is seeded on-device; the NIK is synthesized from region plus date of birth (DDMMYY, like a real KTP), and age is derived back out of that NIK so validation stays data-driven rather than hardcoded. The proof backend runs MidnightProofEngine, an on-device verifier that executes the constraints of a compiled Midnight Compact program (midnight/kost-eligibility.compact → ZKIR circuits). It produces the three badges and a circuit-bound proofId (zk_midnight-v0.34.0_…). A deterministic MockProofEngine stays as a fallback. The real 2940-byte PLONK zk-SNARK our compiled circuit generates is bundled into the app (proof_verify.bin) and shown live in a bonus "Real zk-SNARK" screen, verified against the compiled verifier key with no server at demo time.
Challenges we ran into
Midnight is a blockchain network (zk-SNARKs submitted to the chain), not just an on-device ZK library, and its official SDK is TypeScript (Midnight.js)—there's no native Android/Kotlin SDK yet. We documented that instead of overclaiming "fully on-device." Wiring a JS/TS verifier into Compose needs a WebView bridge, so we shipped an on-device verifier plus a real committed proof artifact as the honest Tier B split and deferred full in-app verification (Tier A) to after submission. The mock engine keeps the flow deterministic so it never fails live; the real proof is generated ahead of time via a Docker proof server.
Accomplishments that we're proud of
A complete end-to-end demo path: tenant → 4-step proof → landlord badges + before/after, emulator-tested with Budi ACCEPTED and Rina NOT ACCEPTED. The real zk-SNARK bonus screen lets judges see genuine proof bytes, the midnight:proof-versioned header hexdump, and a verified status—all read from app assets. Only the proof backend swaps out; the UI stays identical. 18 passing unit tests cover tenant synthesis, badge logic, blacklist, circuit-bound proofId, and the real SNARK formatting helpers.
What we learned
Selective disclosure is a real privacy upgrade: same verification, 100% less personal data shared, no photo, NIK, name, or address moves. On-device ZK can be demonstrated convincingly without shipping the full blockchain integration; a committed proof artifact bridges the gap honestly. The hardest part of the demo wasn't the crypto, it was keeping the flow deterministic and readable live on screen.
What's next for ZKost
Built With
- android-studio
- kotlin
- midnight
- opencode
Log in or sign up for Devpost to join the conversation.