Skip to content

taikoxyz/shadow

Repository files navigation

Shadow Protocol

Privacy-preserving ETH and ERC20 token claims using zero-knowledge proofs.

Disclaimer: This repository is an experimental research prototype and is not production-ready. The code lacks compliance modules and has not undergone the review necessary for any real-world deployment. Its presence in the Taiko GitHub organization is for open-source research purposes only and does not indicate that any product or protocol built on this codebase will be launched on Taiko or endorsed by Taiko. No proposal to deploy any privacy-related protocol — including this one — has been made or approved through Taiko DAO governance. Use this code strictly for educational and research purposes at your own risk.

Quick Start

./start.sh

Or run without cloning:

curl -fsSL https://raw.githubusercontent.com/taikoxyz/shadow/main/start.sh | sh

This will pull the Shadow Docker image (or build from source if unavailable), create a ~/.taikoshadow directory, start the server, and open http://localhost:3000 in your browser.

Options:

Flag Description
--pull Force pull the latest image from registry
--build Force build the image from source
--clean Delete all local shadow images and containers, then exit
--verbose [level] Set verbosity level: info (default), debug, or trace
[port] Pin to a specific port (default: auto-select from 3000-3099)
./start.sh --pull              # force latest from registry
./start.sh --build             # build from source
./start.sh --build 3001        # build from source, use port 3001
./start.sh --clean             # remove all shadow images and containers
./start.sh --verbose           # info-level server logs + launcher details
./start.sh --verbose debug     # debug-level server logs
./start.sh --verbose trace     # trace-level server logs (full RPC payloads)

Verbose & debugging

--verbose [level] enables detailed output at two layers:

Launcher (always shown when --verbose is used):

  • Docker version, registry image, and expected circuit ID
  • Circuit ID comparison during image validation
  • Full docker build / docker pull output (normally suppressed)
  • Image name, port mapping, and workspace mount before container start

Server (controlled by the level — sets RUST_LOG inside the container):

Level What it shows
info (default) Pipeline start/complete, block fetched, account proof fetched, note proving, queue events
debug + RPC call timing, chain ID verification, ClaimInput details, proof sizes, queue progress
trace + Full RPC request params and response payloads

Browser console logging is separate and opt-in. Enable via localStorage.setItem('shadow-debug', '1') or add ?debug to the URL. This logs API calls with timing and all WebSocket events.

The server provides:

  • Web UI for managing deposits and proofs
  • REST API + WebSocket for real-time updates
  • In-process ZK proof generation (RISC Zero Groth16)

Place deposit files in ~/.taikoshadow/ or create new deposits from the UI.

Building the Docker Image

pnpm docker:build

This builds the image for linux/amd64 (required by RISC Zero) and prints the circuit ID on success. On Apple Silicon, the build runs under emulation.

To run the built image:

pnpm docker:run

Publishing Docker Image (Manual)

Docker publish is local-only. pnpm docker:publish does not use GitHub Actions. It reuses a local image if present; otherwise it builds locally, then pushes to GHCR.

# publish using default tag "latest"
pnpm docker:publish

Optional args:

# publish with an explicit tag (pnpm forwards args after `--`)
pnpm docker:publish -- latest

# publish with a custom tag
pnpm docker:publish -- dev

# publish to a custom registry (default: ghcr.io)
REGISTRY=ghcr.io pnpm docker:publish -- dev

# force rebuilding by removing the cached local image
docker image rm shadow-local:latest

Prerequisites:

  • gh auth status is authenticated for the target org/repo
  • token has read:packages and write:packages scopes

Local Development (without Docker)

Prerequisites

  • Rust toolchain (1.80+)
  • Node.js 20+ with pnpm
  • Foundry (for contract tests)
  • RISC Zero toolchain (only for proving): cargo install rzup --locked && rzup install

Option A: Without proving (UI/API dev only)

# Terminal 1 — server on :3000
cargo run --manifest-path packages/server/Cargo.toml -- \
  --port 3000

# Terminal 2 — UI dev server on :5173 (proxies API/WS to :3000)
pnpm ui:dev

Open http://localhost:5173.

Option B: With ZK proving (full flow)

# Terminal 1 — server on :3000 with proving enabled
cargo run --release --manifest-path packages/server/Cargo.toml --features prove -- \
  --port 3000 \
  --rpc-url https://rpc.hoodi.taiko.xyz \
  --shadow-address 0x77cdA0575e66A5FC95404fdA856615AD507d8A07

# Terminal 2 — UI dev server on :5173 (proxies API/WS to :3000)
pnpm ui:dev

Open http://localhost:5173.

The server will warn on startup if the local circuit ID doesn't match the on-chain verifier — this is expected when building locally. You can still prove; just redeploy the verifier before submitting on-chain.

To check your local circuit ID:

cargo run --manifest-path packages/risc0-prover/Cargo.toml -p shadow-risc0-host -- circuit-id

2. Create a deposit

From the UI, click + New Deposit and fill in:

  • Recipient: the Ethereum address that will claim the ETH or tokens
  • Amount: amount in the token's smallest units (e.g. 1000000000000000 = 0.001 ETH, or 1000000 = 1 USDC with 6 decimals)
  • Token (optional): ERC20 token contract address. Leave empty for ETH deposits.

Or via the API:

# ETH deposit
curl -X POST http://localhost:3000/api/deposits \
  -H 'Content-Type: application/json' \
  -d '{"chainId":"167013","notes":[{"recipient":"0xYourAddress","amount":"1000000000000000","label":"my note"}]}'

# ERC20 deposit
curl -X POST http://localhost:3000/api/deposits \
  -H 'Content-Type: application/json' \
  -d '{"chainId":"167013","token":"0xTokenAddress","notes":[{"recipient":"0xYourAddress","amount":"1000000","label":"my note"}]}'

Deposit creation is instant (generates a random secret and derives a target address).

3. Fund the target address

ETH deposit: Send ETH to the targetAddress. The total amount must cover all notes plus a 0.1% claim fee.

cast send <targetAddress> \
  --value <totalAmount> \
  --rpc-url https://rpc.hoodi.taiko.xyz \
  --private-key 0x...

ERC20 deposit: Send tokens via a plain ERC20 transfer to the targetAddress.

cast send <tokenAddress> "transfer(address,uint256)" <targetAddress> <totalAmount> \
  --rpc-url https://rpc.hoodi.taiko.xyz \
  --private-key 0x...

4. Generate proof

From the UI, click Generate Proof on the deposit detail page. Or via API: POST /api/deposits/{id}/prove

5. Claim on-chain

From the UI, click Claim next to each note (requires MetaMask connected to Taiko Hoodi).

Server API Reference

Method Endpoint Description
GET /api/health Health check
GET /api/config Server configuration
GET /api/deposits List all deposits
GET /api/deposits/:id Get deposit details
POST /api/deposits Create a new deposit
DELETE /api/deposits/:id Delete deposit file
POST /api/deposits/:id/prove Start proof generation
DELETE /api/deposits/:id/proof Delete proof file
GET /api/deposits/:id/notes/:idx/claim-tx Get claim tx calldata for MetaMask
POST /api/deposits/:id/notes/:idx/refresh Refresh on-chain claim status
GET /api/queue Proof generation queue status
DELETE /api/queue/current Cancel current proof job
WS /ws Real-time events (workspace changes, proof progress)

Deployed Contracts (Taiko Hoodi)

Contract Address
Shadow (proxy) 0x77cdA0575e66A5FC95404fdA856615AD507d8A07
ShadowVerifier 0xf2bbb2ca267227422c8b4975489606d969cbc8b1
Risc0CircuitVerifier 0xb7b85498cd316348d2ad64d59033cd8d6254e356
RiscZeroGroth16Verifier 0xd1934807041B168f383870A0d8F565aDe2DF9D7D
DummyEtherMinter 0x6DC226aA43E86fE77735443fB50a0A90e5666AA4
TestShadowToken (TST) 0x25a8012b7A97a00Bed854B960D9335d010fAc6a3

Circuit ID (imageId): 0xa7dd3b3e61fe665ea86945431797de7f6fa7953f99f725f6e17ad56f8d104728 Chain ID: 167013 (Taiko Hoodi testnet)

Architecture

                     +-----------+
                     |  Web UI   |
                     |  (Vite)   |
                     +-----+-----+
                           |
                     REST + WebSocket
                           |
                     +-----v-----+
                     |  Server   |  shadow-server (Axum)
                     |  (Rust)   |  - deposit creation (ETH + ERC20)
                     +-----+-----+  - proof generation (RISC Zero)
                           |        - workspace file management
                     +-----v-----+  - on-chain queries (RPC)
                     |  Shadow   |
                     | Contract  |  UUPS proxy on Taiko L2
                     +-----------+  - ZK proof verification
                                    - nullifier tracking
                                    - ETH claiming via IEthMinter (0.1% fee)
                                    - ERC20 claiming via IShadowCompatibleToken.shadowMint

Project Structure

packages/
  contracts/     Solidity contracts (Foundry)
  server/        Rust backend (Axum)
  ui/            Web frontend (Vite, vanilla JS)
  risc0-prover/  ZK prover (RISC Zero)
    crates/
      shadow-proof-core/   Cryptographic primitives
      shadow-prover-lib/   Prover pipeline library
    guest/                 ZK circuit (runs inside zkVM)
    methods/               Compiled guest methods
docker/
  Dockerfile     Multi-stage build (UI + server + prover)

Documentation

Testing

# Contract tests (54 tests including Groth16 verification)
cd packages/contracts && forge test -vvv

# Server tests (15 tests)
cd packages/server && cargo test

# Prover core tests
cd packages/risc0-prover && cargo test -p shadow-proof-core

Security

For security concerns, contact security@taiko.xyz

About

Research on ZK privacy-preserving ETH & ERC20 transfers on Taiko L2, in collaboration with NTU

Resources

Code of conduct

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors