
ZK-Inherit
Private crypto inheritance for the ones you care
About
The problem it solves ## The problem it solves **ZK-InherIt** is a private, verifiable crypto inheritance system. It lets people decide who gets their crypto (ETH, USDC, NFTs) if they become inactive, **without revealing beneficiaries or amounts on-chain** and **without relying on a lawyer or custodian** to execute the will. ### What people can use it for - **Set a private digital will** Allocate assets to up to 8 beneficiaries. Only a commitment hash and Merkle root go on-chain; who gets what stays hidden until execution. - **Prove validity without leaking data** A Noir ZK circuit proves that allocations sum to declared totals and match the committed will, so the chain can trust execution without seeing beneficiary details. - **Automate execution on inactivity** A heartbeat contract tracks check-ins (e.g. yearly). If the user stops checking in, a configurable grace period starts; trusted veto members can pause. After the grace window (and if not vetoed), the will can be executed and assets distributed privately. - **Use existing legal documents** Upload a signed PDF will; a Rust backend extracts beneficiary data so users can bridge traditional wills into the same private, on-chain flow. - **Comply with identity and age rules** Integration with Self Protocol ensures only verified humans (e.g. passport/Aadhaar) can register wills and enforces age checks (e.g. 18+) without storing personal data on-chain. ### How it makes things easier or safer | Before | With ZK-InherIt | |--------|------------------| | Will details or beneficiary addresses on a public chain | Only commitments and Merkle roots on-chain; beneficiaries and amounts stay private | | Manual or custodian-driven execution | Automated execution after verifiable inactivity and grace period | | Trust in a single executor | Trust in math: ZK proof guarantees the will is valid; execution is permissionless once the proof verifies | | No link between legal PDFs and crypto wills | PDF upload → extracted beneficiaries → same ZK registration and execution pipeline | | Bot or underage registrations | Human verification + age checks via Self Protocol before registration | In short: **people use ZK-InherIt to pass crypto to chosen beneficiaries privately, on a schedule driven by inactivity, with verifiable correctness and optional PDF/identity integration.**
How it's made
Challenges we ran into ### 1. **Heavy ZK dependencies slowing the frontend** **What happened:** The Next.js frontend took several minutes to compile and often felt stuck. First load was very slow because of large crypto/ZK libraries (`circomlibjs`, `@noir-lang/noir_js`, `@aztec/bb.js`, Poseidon, etc.). Webpack was analyzing and bundling all of them on every cold start. **How I got over it:** I optimized the Next.js webpack config: added **chunk splitting** so heavy crypto libs are in a separate bundle, **ignored** noisy "Critical dependency" warnings from circomlibjs/ffjavascript, and enabled **SWC minification**. I also cleared the `.next` cache and ensured Node 18/20 (not Node 24) was used. First compile stayed heavy but became manageable; subsequent dev runs and page loads were much faster. --- ### 2. **Node 24 breaking Next.js with `ERR_INVALID_PACKAGE_CONFIG`** **What happened:** Running `npm run dev` failed with `Invalid package config ... next/dist/compiled/commander/package.json` and `Node.js v24.4.1`. Next.js 14 isn’t validated on Node 24, and Node 24’s stricter package.json handling caused the bundled `commander` inside Next to fail. **How I got over it:** Switched the project to **Node 20** (or 18) via nvm, added an `.nvmrc` with `20` so the right version is used automatically, and reinstalled dependencies. The frontend then started without that error. --- ### 3. **Stale Service Worker (Workbox) blocking the app from loading** **What happened:** The app at `http://localhost:3000` kept loading indefinitely. The browser console showed `no-response` errors for `/_next/static/chunks/main-app.js` and other Next chunks. A **previously registered PWA/Workbox Service Worker** (from another project or an old build) was intercepting requests and failing to fetch the new chunks, so the page never rendered. **How I got over it:** In Chrome DevTools → **Application** → **Service Workers**, I **unregistered** the service worker for localhost and enabled **“Bypass for network”**. Then under **Storage** I did **Clear site data** and performed an **Empty Cache and Hard Reload**. After that, Next’s chunks loaded normally and the app worked. --- ### 4. **Rust backend failing to build: `serde_core` build script timeout** **What happened:** Running `cargo run` in the backend failed with: `failed to run custom build command for serde_core v1.0.228` and `Os { code: 60, kind: TimedOut, message: "Operation timed out" }`. The build script of the newer `serde`/`serde_core` was hitting a timeout (likely a network or system call), so the backend never compiled. **How I got over it:** I **pinned** `serde` and `serde_json` in `Cargo.toml` to slightly older versions that don’t trigger that build script behavior: `serde = "=1.0.210"` and `serde_json = "1.0.114"`. Then I ran `cargo clean` and `cargo build`. The backend compiled successfully and runs on port 3002. --- ### 5. **Designing the Noir circuit so allocations are both private and verifiable** **What happened:** I needed the circuit to prove that beneficiary allocations sum to the declared totals and match a Merkle tree, without revealing individual addresses or amounts. Getting the **public vs private inputs** right and building a **Poseidon-based Merkle tree** that matches the frontend (and fits in the circuit) was tricky; wrong ordering or padding broke verification. **How I got over it:** I clearly separated **public inputs** (will_commitment, merkle_root, total_eth, total_usdc, total_nft_count) from **private inputs** (salt, will_data, beneficiary arrays). I implemented the same Poseidon hash (hash_2, hash_4, hash_5) and Merkle structure in both the Noir circuit and the frontend’s Poseidon lib so the computed commitment and root matched. I added a **test** in the circuit (`test_valid_will`) with fixed values to lock in the expected hashes and avoid regressions.