Verify Umbra bridge accountability proofs (signed receipts, checkpoints, Merkle inclusion).
Umbra is a non-custodial cross-chain bridge built around Monero — the only cryptocurrency with mandatory privacy. It enables trustless swaps between privacy coins and public blockchains using FROST threshold signatures (2-of-3 MPC), meaning no single party ever holds your funds.
What makes Umbra different from every other bridge is the Proof Layer: the bridge does not ask to be trusted — it produces cryptographic evidence for everything it does, and ships the tools to check that evidence offline.
Bridges are the most-hacked primitive in crypto (Ronin $624M, Multichain $126M, Nomad $190M, Harmony $100M — drained over minutes-to-hours while nothing watched). Umbra's answer is a layer of verifiable accountability that no major bridge ships today:
┌─────────────────────────────────────────────────────────┐
│ PROOF LAYER │
│ │
every │ ┌────────────┐ sealed ┌──────────────────┐ │
order ────►│ │ Signed │ every 5m │ Transparency │ │
event │ │ Receipts │────────────►│ Log (Merkle) │ │
│ │ Ed25519 + │ │ RFC 6962 proofs │ │
│ │ ML-DSA-65 │ └────────┬─────────┘ │
│ └────────────┘ │ anchors │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────┐ │
│ │ Sentinel │ │ Signed │ │
│ │ 5 guards, 30s │ │ Canary │ │
│ │ + Isolation │ │ (freshness- │ │
│ │ Forest (ML) │ │ bound) │ │
│ └──────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
▲ verify offline: browser page, single-file
HTML, or pure-stdlib Python CLI — zero trust
Every lifecycle event of every order (order_created, status_confirming, status_completed, …) produces a receipt signed with the bridge's Ed25519 key over canonical JSON. Receipts for one order are hash-chained (each embeds the SHA-256 of the previous receipt), so a user holding their final receipt can detect any retroactive edit to their order's history. Receipts carry a hash of the destination address, not the address — the proof layer never becomes a metadata leak.
curl -s https://your-bridge/v1/proof/receipt/br_ab12cd34ef56 | \
python3 tools/verify_receipt.py receipts - --pin <PUBLIC_KEY>
Receipts are archival evidence with a multi-decade shelf life. Alongside Ed25519, every receipt and checkpoint is signed with ML-DSA-65 (FIPS 204, CRYSTALS-Dilithium) over the same canonical bytes. Ed25519 stays the cheap, universally verifiable online layer; the ML-DSA signature keeps the archive forgery-proof against a future quantum adversary. Forging history requires breaking both schemes.
The audit hash-chain is sealed every few minutes into a signed Merkle checkpoint (the RFC 6962 construction used by Certificate Transparency, validated against the CT known-answer vectors). Anyone can then demand:
GET /v1/proof/inclusion/{audit_id} proves a specific audit entry is committed to by a checkpoint (log₂ n hashes, verifiable offline).GET /v1/proof/consistency?old_size=&new_size= proves a newer checkpoint is a pure append-only extension of an older one. History cannot be rewritten without detection.Mirror GET /v1/proof/checkpoint/latest on a cron job and you become an external witness the operator cannot silently contradict.
Five guards run every 30 seconds:
| Guard | Trips when | Would have caught |
|---|---|---|
| Outflow velocity | per-chain outbound volume exceeds hourly caps | Ronin, Harmony (key-compromise drains) |
| Order velocity | order creation rate spikes | bot floods, probing attacks |
| Failure spike | failed orders cluster | subsystem exploitation |
| Rate divergence | independent price sources disagree > 5% | oracle poisoning |
| ML anomaly (optional) | Isolation Forest flags order-flow combinations vs the 7-day baseline | drain signatures no fixed threshold expresses |
A trip pauses new intake only — in-flight swaps keep settling, so a false positive costs minutes of intake, not user funds. The sentinel never auto-resumes: a human must investigate and resume with a note. Every trip, pause, and resume lands in the tamper-evident audit chain and the public GET /v1/proof/status endpoint — the bridge cannot be paused or unpaused in secret. The ML guard fails open: a risk-engine outage never blocks the bridge.
GET /v1/proof/canary returns a signed statement embedding the current time and the latest checkpoint root — it cannot be replayed against a rewound log. A canary that stops updating is itself a signal.
Three independent verifiers ship in this repo, all validated against RFC 8032 test vectors and against each other:
| Verifier | Trust required |
|---|---|
website /verify page | verification runs client-side in your browser |
verifier/umbra-verify.html | none — save the file, open from file://, zero network requests, pure-BigInt Ed25519 with an on-load self-test |
tools/verify_receipt.py | none — pure Python stdlib, no dependencies; receipts/checkpoint/inclusion subcommands |
Pin the bridge's public key (GET /v1/proof/key) out-of-band once; from then on, every receipt either verifies or it doesn't. Telegram users get the same via /receipt <order_id> and /trust.
The full wire-format specification — exact canonical forms, algorithms, threat model, and test vectors for building your own verifier — lives in docs/PROOF_LAYER.md.
| Chain | Ticker | Type |
|---|---|---|
| Monero | XMR | Privacy L1 |
| Bitcoin | BTC | L1 |
| Ethereum | ETH | L1 |
| TON | TON | L1 |
| Solana | SOL | L1 |
| Arbitrum | ARB | L2 (EVM) |
| Base | BASE | L2 (EVM) |
| USDT / USDC | Stablecoins | Multi-chain |
+------------------+
| Nginx Proxy |
| :80 / :443 |
+--------+---------+
|
+------------------+------------------+
| | |
/app | / | /api |
v v v
+--------+------+ +------+-------+ +-------+-------+
| Telegram Mini | | Next.js | | Rust Backend |
| App (Vite) | | Website | | (axum) |
| :3000 | | :3001 | | :8000 |
+----------------+ +--------------+ +---+---+-------+
| |
+------------------+ +--------+
| |
+-------v------+ +--------v-------+
| PostgreSQL | | Redis |
| :5432 | | :6379 |
+--------------+ +----------------+
|
+-------------------------------------------+
| |
+--------v-------+ +--------v--------+
| Telegram Bot | | Prometheus + |
| (aiogram) | | Grafana |
+----------------+ +-----------------+
Single Rust binary replaces what would typically be 3+ Python processes. The backend handles HTTP API, WebSocket streaming, background task scheduling, and blockchain RPC — all in one process using tokio.
Mobile-optimized bridge UI that runs inside Telegram. Tap, swap, done.
Full conversational bridge — /bridge, /rate, /history, /status. Works without ever leaving the chat.
Desktop dashboard with portfolio tracking, analytics charts, order history, public transaction explorer, admin panel — plus /verify (client-side receipt verification) and /transparency (live sentinel status, checkpoints, canary).
All three share the same Rust API and real-time WebSocket feed.
| Layer | Technology |
|---|---|
| Backend | Rust, axum 0.7, sqlx, tokio, tower |
| Database | PostgreSQL 17, Redis 7 |
| MPC | FROST threshold signatures (frost-secp256k1) |
| Blockchain | monero-wallet-rpc, toncenter, EVM JSON-RPC, Solana RPC, Bitcoin Core |
| Website | Next.js 15, React 19, Tailwind CSS 4, Zustand, Recharts, TON Connect |
| Mini App | Vite 6, vanilla JS, Telegram WebApp SDK |
| Bot | Python, aiogram 3, httpx |
| Monitoring | Prometheus, Grafana |
| Proxy | Nginx with rate limiting |
| Rates | CoinGecko -> Binance -> CoinCap (triple fallback) |
# Clone
git clone https://github.com/beepboop2025/umbra.git
cd umbra
# Configure
cp backend/.env.example .env
# Edit .env with your keys (Telegram bot token, RPC endpoints, etc.)
# Launch everything
docker compose up --build
| Service | URL |
|---|---|
| Website | http://localhost |
| Mini App | http://localhost/app |
| API | http://localhost/api/health |
| Grafana | http://localhost:3002 |
| Prometheus | http://localhost:9090 |
umbra/
├── backend/ # Rust API (axum + sqlx + tokio)
│ ├── src/
│ │ ├── routes/ # HTTP + WebSocket endpoints (incl. /v1/proof/*)
│ │ ├── services/ # Rates, orders, attestations, transparency, sentinel
│ │ ├── blockchain/ # Monero, Bitcoin, EVM, TON, Solana RPC clients
│ │ ├── mpc/ # FROST threshold signatures, coordinator, signers
│ │ ├── middleware/ # Security headers, rate limiting, Telegram auth
│ │ ├── tasks/ # Deposit monitor, confirmations, expiry, sentinel, sealing
│ │ ├── models/ # SQLx models (orders, rates, audit, MPC)
│ │ └── utils/ # Crypto helpers, address validation, RFC 6962 Merkle
│ └── migrations/ # PostgreSQL schema
├── bot/ # Telegram bot (aiogram 3)
│ └── bot/
│ ├── handlers/ # /start, /bridge, /rate, /history, /admin
│ ├── keyboards/ # Inline keyboards for bridge flow
│ └── services/ # Backend API client
├── website/ # Enterprise dashboard (Next.js 15)
│ └── src/
│ ├── app/ # Pages: bridge, dashboard, explorer, admin
│ ├── components/ # UI, bridge, charts, wallet, layout
│ ├── hooks/ # useRate, useOrders, useWebSocket, useWallet
│ ├── stores/ # Zustand state management
│ └── lib/ # API client, validators, utilities
├── src/ # Telegram Mini App (Vite)
├── risk-engine/ # Python risk service (FastAPI)
│ └── services/ # Isolation Forest, MAD z-score, drain scoring
├── verifier/ # Single-file offline verifier (umbra-verify.html)
├── tools/ # verify_receipt.py — pure-stdlib CLI verifier
├── docker-compose.yml # Full stack orchestration (10 services)
├── nginx.conf # Reverse proxy + rate limiting
└── monitoring/ # Prometheus + Grafana config
1. User selects pair XMR -> TON, enters amount
2. Rate lock Cross-rate from CoinGecko (30s cache)
3. Sentinel gate Intake refused instantly if the circuit breaker is tripped
4. Order created Deposit address generated, 30min expiry, signed receipt #0
5. Deposit detected Monero wallet-rpc monitors subaddress
6. Confirmations 10 confirms for XMR, 1 for TON
7. MPC signing 2-of-3 FROST threshold signature
8. Withdrawal sent TON transfer broadcast
9. Complete Signed receipt binds the withdrawal tx hash;
user notified via WebSocket + Telegram
Every step from 4 onward emits a hash-chained, dual-signed receipt the user can verify offline — the order's history is provable end to end.
initDataThe attestation identity is derived deterministically from SECRET_KEY by default (fine for development). In production set independent seeds — ATTESTATION_SECRET_KEY (Ed25519) and ATTESTATION_PQ_SEED (ML-DSA-65) — so receipt-signing custody can be separated from JWT custody, and publish the public keys from GET /v1/proof/key somewhere you don't control (a tweet, a git tag, another bridge's transparency page) so users can pin them.
GET /v1/rate?direction=XMR_TO_TON Current rate + 24h change + sparkline
GET /v1/rate/history?direction=&period=1h Rate history (1h/4h/24h/7d/30d)
POST /v1/order Create bridge order
GET /v1/order/:id Order details
GET /v1/orders?telegram_user_id=&limit=50 Order history
POST /v1/order/:id/cancel Cancel pending order
WS /v1/ws/order/:id Real-time order status updates
WS /v1/ws/rates Live rate feed
GET /v1/proof/key Bridge public keys (Ed25519 + ML-DSA-65) — pin these
GET /v1/proof/receipt/:order_id Signed receipts for an order (full lifecycle)
GET /v1/proof/checkpoint/latest Latest signed Merkle tree head
GET /v1/proof/checkpoints?limit=20 Checkpoint history
GET /v1/proof/inclusion/:audit_id RFC 6962 inclusion proof for an audit entry
GET /v1/proof/consistency?old_size=&new_size= Append-only proof between two checkpoints
GET /v1/proof/audit/verify Server-side full hash-chain walk
GET /v1/proof/canary Signed, freshness-bound warrant canary
GET /v1/proof/status Sentinel state: accepting orders? why not?
POST /v1/risk/anomaly Statistical drain scores (MAD z-score, burst, round amounts)
POST /v1/risk/anomaly/ml Isolation Forest anomaly scores over order-flow features
POST /v1/admin/login JWT authentication
GET /v1/admin/stats System statistics
POST /v1/admin/order/:id/refund Manual refund
GET /v1/admin/sentinel Sentinel state + last 50 events
POST /v1/admin/sentinel/pause Manual pause (reason required)
POST /v1/admin/sentinel/resume Resume (investigation note required)
GET /health Liveness probe
GET /ready Readiness probe (DB + Redis)
GET /metrics Prometheus metrics
Prometheus scrapes the /metrics endpoint for:
Grafana dashboards available at :3002 (default password: admin).
# Backend only (requires local Postgres + Redis)
cd backend
cargo run
# Website only
cd website
npm install && npm run dev
# Mini App only
npm run dev
# Run backend tests
cd backend
cargo test
See backend/.env.example for the full list. Key ones:
| Variable | Description |
|---|---|
SECRET_KEY | JWT signing key (64+ chars) |
TELEGRAM_BOT_TOKEN | From @BotFather |
MONERO_RPC_URL | monero-wallet-rpc endpoint |
TON_API_URL | toncenter API |
ETH_RPC_URL | Ethereum JSON-RPC |
BRIDGE_FEE_PERCENT | Fee per swap (default: 0.3%) |
MPC_THRESHOLD | Signatures required (default: 2) |
ATTESTATION_SECRET_KEY | Ed25519 receipt-signing seed, 64 hex chars (derived from SECRET_KEY if unset) |
ATTESTATION_PQ_SEED | ML-DSA-65 seed, 64 hex chars (derived if unset) |
PQ_SIGNATURES_ENABLED | Post-quantum hybrid signatures (default: true) |
TRANSPARENCY_SEAL_INTERVAL_SECS | Checkpoint sealing cadence (default: 300) |
CANARY_STATEMENT | Warrant-canary text served at /v1/proof/canary |
SENTINEL_ENABLED | Circuit breaker (default: true) |
SENTINEL_OUTFLOW_CAPS | Per-chain hourly caps, e.g. XMR:1000,BTC:20 |
RISK_ENGINE_URL | Enables the Isolation Forest sentinel guard (optional) |
Source-available under the Source-Available License v1.0. You may view, study, fork, and run it locally for non-commercial evaluation and research. Commercial use, operating it as a service, or redistribution require a separate written license.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
docker run -i --rm ghcr.io/beepboop2025/umbra-proof-mcp:v0.1.2Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-beepboop2025-umbra-proof": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/beepboop2025/umbra-proof-mcp:v0.1.2"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceghcr.io/beepboop2025/umbra-proof-mcp:v0.1.2dockerio.github.beepboop2025/umbra-proof works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.