Inspiration

Indonesia has over 277 million citizens, each holding a physical KTP (Kartu Tanda Penduduk) as their sole proof of identity. Yet every year, millions of people face bureaucratic delays, counterfeit ID fraud, and inaccessibility — particularly those in remote areas or abroad. The NIK (Nomor Induk Kependudukan) system is entirely centralized: one database breach can compromise an entire nation's identity records.

We were inspired by the question: what if your identity was something no one could take away, forge, or manipulate — not even the government? Decentralized Identifiers (DIDs) on a public blockchain offer exactly that. Pair it with IPFS for tamper-proof credential storage and you have the foundation for a truly sovereign digital identity.


What it does

SatuIdentitas is a decentralized identity platform that replaces physical KTP/NIK documents with blockchain-anchored DIDs on the Ethereum Sepolia testnet.

For Indonesian Citizens (WNI):

  • Submit NIK or KK number to generate a unique DID (did:elpeef:citizen:<hash>)
  • DID is anchored on-chain via MetaMask — only SHA-256(NIK) is stored, never the raw ID
  • A W3C Verifiable Credential is uploaded to IPFS via Pinata as a permanent certificate
  • A Soulbound Token (SBT) card is generated as a non-transferable on-chain identity badge

For Tourists & Visa Holders (WNA):

  • Register a temporary DID tied to passport and visa expiry date
  • Real-time Overstay Detection monitors all active visas every 30 seconds and flags expired ones automatically

For Everyone:

  • QR code generation in two modes: on-chain (Etherscan link) and offline (HMAC-SHA256 signed JWT)
  • Batch identity verification API for institutions (banks, hospitals, employers)
  • Fraud detection engine that flags duplicate NIK, suspicious patterns, and manual reports
  • Offline JWT verification — works without internet by scanning a QR and verifying the signature locally
  • SatuBot: an AI assistant powered by Google Gemini that answers questions about the platform in Bahasa Indonesia or English

How we built it

Frontend: React 18 + Vite, TanStack Query v5, Wouter, shadcn/ui, Framer Motion

Backend: Node.js + Express 5 + TypeScript, PostgreSQL via Drizzle ORM

Blockchain: Solidity smart contract (DIDRegistry.sol) deployed on Sepolia, integrated via ethers.js v6 and MetaMask

Storage: Pinata IPFS for W3C Verifiable Credential certificates (JSON-LD format)

AI: Google Gemini 2.0 Flash via @google/generative-ai for the SatuBot chatbot

Security: HMAC-SHA256 for offline JWT signing, SHA-256 hashing for NIK privacy on-chain

Monitoring: Background interval process running every 30 seconds to detect overstaying visa holders and emit structured event logs to the database

The entire stack runs as a single monorepo — shared TypeScript types between frontend and backend ensure end-to-end type safety from the database schema to the UI.


Challenges we ran into

Privacy vs. Transparency tradeoff on a public blockchain. Every transaction on Sepolia is publicly readable. We had to ensure the NIK never touches the chain — solving this by hashing on the client side before the MetaMask popup even appears, so the raw ID never leaves the user's device.

Immutability as a double-edged sword. The smart contract correctly prevents re-registration of an existing DID. But during development, recovering state after a failed registration (e.g., user rejected the MetaMask transaction mid-flow) required building an event-log recovery mechanism that scans DIDRegistered events on-chain to reconstruct the txHash without requiring re-registration.

Offline verification without a server. Making JWT QR codes verifiable without internet access meant encoding enough context into the token payload and providing a self-contained verification page — essentially a mini cryptographic verifier in the browser.

Real-time overstay detection at scale. Running a background monitor across all active visa holders required careful database indexing and ensuring the monitor wouldn't re-flag already-flagged identities on every cycle.

Routing conflicts with wildcard DID parameters. The /api/visa-identities/flagged route had to be registered before /api/did/:did in Express to avoid the wildcard consuming the static path — a subtle but critical ordering issue.


Accomplishments that we're proud of

  • End-to-end on-chain identity flow — from form submission to MetaMask signature to IPFS certificate, entirely working on a live testnet
  • Zero raw PII on the blockchain — SHA-256 hashing ensures citizen data cannot be scraped from the public ledger
  • True offline verification — a QR code that can be verified cryptographically without any server or internet connection
  • Real-time overstay monitoring — automatic detection and flagging of expired visa holders, with a live event feed UI
  • Unified DID format — citizens and visitors share the same did:elpeef:* namespace with consistent API behavior
  • Production-ready architecture — shared types, typed API routes, Zod validation on every endpoint, and a clean separation of concerns throughout

What we learned

  • W3C DID and Verifiable Credential specifications are more accessible than they look — the core concepts map cleanly onto existing web infrastructure when you don't over-engineer the cryptography layer
  • Blockchain is the right tool for immutability, not for storage — keeping heavy data on IPFS while anchoring only the minimal hash on-chain is the right pattern for identity systems at scale
  • SHA-256 is not Zero Knowledge — it's one-way and computationally infeasible to reverse, but a true ZK proof (e.g., zk-SNARK) would allow proving identity attributes without revealing the hash at all; that's the natural next step
  • UX around wallets is still the biggest barrier to Web3 adoption — MetaMask prompts, network switching, and gas fees are friction points that need more abstraction for mainstream users
  • Real-time monitoring on a relational DB requires thoughtful state management — idempotency checks (flaggedAt IS NULL) are essential to avoid duplicate events

What's next for SatuIdentitas

  • Zero-Knowledge Proofs — upgrade the on-chain hash to a zk-SNARK proof so identity can be verified without revealing even the hash, achieving true cryptographic privacy
  • Mainnet deployment — migrate from Sepolia testnet to Ethereum mainnet or a lower-cost L2 (Polygon, Optimism) for production viability
  • DID-Auth login — allow third-party apps to authenticate users via their SatuIdentitas DID, replacing username/password with a cryptographic signature
  • Mobile app with NFC — embed the offline JWT into an NFC-enabled card that can be tapped against a reader for instant physical verification
  • Government integration API — a formal API layer for Dukcapil (civil registration office) and Imigrasi (immigration) to query and update DID status
  • Selective disclosure — allow citizens to prove specific attributes (e.g., "I am over 18") without revealing their full identity, using Verifiable Presentations
Share this project:

Updates