dumbtv
dumbtv가 ADB로 Fire TV에서 광고·추적·업데이트 알림을 제거, 루트·앱 변경 없이, 무료 오픈소스
BlindHop 믹스넷 프라이버시 레이어, Substrate 라이트 클라이언트용, 직접·빠른·전체 모드 전환, 설정 없이 브라우저 실행
BlindHop mixnet privacy layer for Substrate light clients. Toggle between direct, fast, and full metadata privacy modes. No setup required — runs in your browser.
보안Rust · ShellGitHub @BlindHop온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Mixnet Privacy Layer for Smoldot Light Clients
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
BlindHop
Mixnet Privacy Layer for Smoldot Light Clients
BlindHop is a network-layer privacy system for Substrate-based light clients. It routes smoldot traffic through the Nym mixnet, hiding who is submitting transactions, what they're querying, and when they're active — without any trusted setup, trusted hardware, or chain fork.
Problem: Substrate light clients connect directly to full nodes over libp2p, exposing the user's IP address. An ISP, network observer, or malicious full node can trivially correlate transactions to real-world identities.README 전문 보기 ↗BlindHop is a network-layer privacy system for Substrate-based light clients. It routes smoldot traffic through the Nym mixnet, hiding who is submitting transactions, what they're querying, and when they're active — without any trusted setup, trusted hardware, or chain fork.
Problem: Substrate light clients connect directly to full nodes over libp2p, exposing the user's IP address. An ISP, network observer, or malicious full node can trivially correlate transactions to real-world identities.
Solution: BlindHop wraps smoldot's WebSocket traffic, routing it through the Nym mixnet:
┌───────────────────────────────────────────────────────────────────┐
│ User Layer (Browser / CLI) │
│ smoldot light client → WS to blindhop-proxy │
│ Privacy slider: None (0-hop) | Fast (2-hop) | Full (5-hop) │
└──────────────────────────┬────────────────────────────────────────┘
│ WebSocket (JSON-RPC)
┌──────────────────────────▼────────────────────────────────────────┐
│ blindhop-proxy (Local) │
│ Accepts smoldot WS connections │
│ None: passthrough │ Fast/Full: routes through Nym SDK │
│ Runtime mode switching via blindhop_setPrivacyMode │
└──────────────────────────┬────────────────────────────────────────┘
│ Nym Mixnet (Sphinx packets)
┌──────────────────────────▼────────────────────────────────────────┐
│ Nym Mixnet (500+ nodes) │
│ Gateway → Layer 1 → Layer 2 → Layer 3 → Gateway │
│ Sphinx format, Poisson mixing, cover traffic │
└──────────────────────────┬────────────────────────────────────────┘
│ Nym SURB reply
┌──────────────────────────▼────────────────────────────────────────┐
│ blindhop-exit (Service Provider) │
│ Nym SP: receives mixnet traffic │
│ Forwards JSON-RPC → Substrate full node via WS │
│ Returns response through Nym reply channel (SURBs) │
└──────────────────────────┬────────────────────────────────────────┘
│ WebSocket
┌──────────────────────────▼────────────────────────────────────────┐
│ Substrate Full Node │
│ Sees exit node IP, NOT user IP │
│ Standard JSON-RPC interface, no modifications needed │
└───────────────────────────────────────────────────────────────────┘
blindhop/
├── common/ # blindhop-common — shared types, transport traits, config, wire frame
│ └── tests/ # Unit + (ignored) live integration tests
├── proxy/ # blindhop-proxy — local WS proxy + Nym client
├── exit/ # blindhop-exit — Nym service provider + Substrate forwarder
├── demo/ # Browser demo with privacy slider (Vite + Nym Wasm SDK)
├── deploy/ # Exit server deployment: build/install scripts, hardened systemd unit
├── scripts/ # run_exit.sh, run_proxy.sh, run_demo.sh, benchmark.sh
├── docs/ # Docusaurus site (wiki.blindhop.wtf)
└── archive/ # Legacy Sphinx relay code (pre-Nym)
├── lib/ # Original blindhop-lib (Sphinx cryptography)
└── relay/ # Original blindhop-relay (self-hosted nodes)
Open the hosted demo — no Rust, no CLI, no local setup:
https://demo.blindhop.wtfThe browser demo uses the Nym Wasm SDK (pinned to 1.4.1) to run the mixnet client directly in your browser. By default it never contacts localhost and never sends queries from your own IP: the "Compare with direct" latency comparison is opt-in, and if the Nym client fails to start the demo stops instead of falling back to a direct connection.
For production use or when you need Fast (2-hop) mode:
cargo build --workspace
Start the exit service on a server with good connectivity:
./scripts/run_exit.sh --release
# Or directly:
cargo run -p blindhop-exit -- --target-rpc wss://sys.turboflakes.io/asset-hub-paseo
This prints the exit's Nym address and saves it to .exit_nym_address.
The exit's Nym keys live in .blindhop-exit/ (override with --data-dir). Keep that directory across restarts and redeploys, and back it up: it is what keeps the exit's address stable. Deleting it gives the exit a new address, and every proxy and demo user must be reconfigured.
Use --gateway <IDENTITY_KEY> (or BLINDHOP_EXIT_GATEWAY) to pin the exit to a specific Nym gateway. This changes only the part of the address after @.
For a production server (systemd, hardening, key backups, upgrades), see deploy/README.md.
On the user's machine:
./scripts/run_proxy.sh --exit-address <EXIT_NYM_ADDRESS>
# Or directly:
cargo run -p blindhop-proxy -- \
--privacy-mode full \
--exit-address <EXIT_NYM_ADDRESS>
If .exit_nym_address exists (from the exit service), the proxy reads it automatically:
./scripts/run_proxy.sh # auto-reads exit address
The proxy refuses connections from web pages unless their origin is allowed, so a malicious site can't take control of it (for example, switching it to direct mode). Clients that aren't browsers, such as smoldot, are unaffected. To let the hosted demo use your local proxy:
cargo run -p blindhop-proxy -- --exit-address <EXIT_NYM_ADDRESS> \
--allowed-origin https://demo.blindhop.wtf
Then tick Use my local BlindHop proxy in the demo before pressing Start. The demo only contacts 127.0.0.1 when that box is ticked, and your browser will ask for permission to access this device.
./scripts/run_demo.sh --mode full --exit-address <EXIT_NYM_ADDRESS>
Or use the Vite dev server after starting the proxy: cd demo && npm run dev
Integrate BlindHop into your smoldot-based dApp:
ws://127.0.0.1:9500 while running blindhop-proxy--allowed-origin <your-site>), otherwise use the browser Nym clientSee the Guides for detailed setup.
| Mode | Path | Cover Traffic | Round-trip (p50, measured) | IP Hidden | Metadata Private |
|---|---|---|---|---|---|
| None | direct | ✗ | RPC node latency | ✗ | ✗ |
| Fast | entry gateway → exit gateway (no mix nodes) | ✗ | ~1.4–1.8 s | ✓ | ✗ |
| Full | gateway → 3 mix layers → gateway | ✓ | ~2 s (p90 ~3 s) | ✓ | ✓ |
Latencies were measured on Nym mainnet for small requests. Large replies take longer: state_getMetadata (~1.2 MB) takes ~9–11 s in Full mode.
Users can switch modes at runtime via the demo UI's privacy slider, or by sending a blindhop_setPrivacyMode JSON-RPC message to the proxy. Switching between Nym modes reconnects with a new Nym client. The reply reports the mode actually in effect (on failure, a JSON-RPC error whose data.mode_id is the current mode).
MixnetTransport (in blindhop-common)#[async_trait]
pub trait MixnetTransport: Send + Sync {
/// Send a request and wait for *its* reply (safe to call concurrently;
/// replies are matched by correlation ID).
async fn request(&self, data: &[u8]) -> Result<Vec<u8>>;
fn privacy_info(&self) -> PrivacyInfo;
fn metrics(&self) -> TransportMetrics;
fn is_connected(&self) -> bool;
async fn disconnect(&self) -> Result<()>;
}
A transport's privacy mode is fixed for its lifetime; switching modes creates a new transport.
ExitBackend (in blindhop-exit)#[async_trait]
pub trait ExitBackend: Send + Sync {
/// `id` is the request's validated JSON-RPC id, used to match the
/// response on a pooled upstream connection.
async fn forward_rpc(&self, request: &[u8], id: &serde_json::Value) -> Result<Vec<u8>>;
fn backend_type(&self) -> &str;
}
# Unit tests (all crates, no network required)
cargo test --workspace
# Lint (including tests and benches) and format check
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
# Dependency audit (accepted advisories are listed in .cargo/audit.toml)
cargo audit
(cd demo && npm audit --package-lock-only --audit-level=moderate)
| Crate | Version | Purpose |
|---|---|---|
nym-sdk | 1.21.6 | Nym mixnet client SDK |
tokio | 1.x | Async runtime |
tokio-tungstenite | 0.26 | WebSocket client/server |
async-trait | 0.1 | Async trait support |
clap | 4.x | CLI argument parsing |
serde | 1.x | Serialization |
flate2 | 1.1 | Deflate compression of large mixnet replies |
@nymproject/sdk-full-fat (npm) | 1.4.1 | Nym Wasm client for the browser demo |
Apache 2.0 + MIT dual license — same as smoldot and the Kusama/Polkadot ecosystem.