Runbook

Kohl over Tor

On-chain privacy hides amounts and parties. Dandelion++ blurs which peer originated a gossip. Tor hides your IP from the peer graph. Use both — none of them alone is enough.

No --proxy-server on current builds · prefer torsocks + onion services · full markdown in repo

Why Tor still matters

LayerHidesDoes not hide
On-chain privacy Sender set, receiver, amount on chain Your IP when you peer or submit
Dandelion++ First-hop origin on the peer graph Clearnet listeners, long-lived peer ID
Tor Node / wallet IP from peers & observers Bad opsec, hostile remote RPC, malware

Honest limits: timing correlation, mixing clearnet and Tor for the same wallet, and open RPC on 0.0.0.0 all leak. This is hygiene, not a guarantee.

Pick a level

L1

Outbound only

Dial peers via Tor. No inbound onion. Lowest effort.

Tool: torsocks

L2

Full onion node

Hidden service for P2P (and optional RPC). Peers dial you without learning your IP.

Tool: Tor HS + --public-addr

L3

Wallet over Tor

Wallet reaches only your node via SOCKS → onion RPC (or local RPC if co-located).

Prefer a node you control

L1 — Outbound only

Install Tor so SOCKS is on 127.0.0.1:9050, then:

shell
# Confirm Tor works
curl -sS --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip

# Node: all TCP via Tor; P2P bound to localhost (no clearnet inbound)
torsocks ./target/release/kohl \
  --chain kohl-ash \
  --validator \
  --listen-addr /ip4/127.0.0.1/tcp/30333 \
  --rpc-port 9944 \
  --network-backend libp2p \
  --mining-seed <64-hex>
  • There is no --proxy-server flag on current Kohl / Substrate builds.
  • Prefer --network-backend libp2p when experimenting with onion multiaddrs.
  • You still need bootnodes reachable from Tor (clearnet via exit, or onion).

L2 — Onion service for P2P

1. Tor hidden service

torrc
SocksPort 9050

## P2P onion → local Kohl
HiddenServiceDir /var/lib/tor/kohl-p2p/
HiddenServicePort 30333 127.0.0.1:30333

## Optional separate RPC onion (wallet only)
# HiddenServiceDir /var/lib/tor/kohl-rpc/
# HiddenServicePort 9944 127.0.0.1:9944

Restart Tor, then:

shell
sudo cat /var/lib/tor/kohl-p2p/hostname
# e.g. abcdef…xyz.onion

2. Stable PeerId + node

shell
mkdir -p ~/.kohl
./target/release/kohl key generate-node-key --file ~/.kohl/node-key

ONION=$(sudo cat /var/lib/tor/kohl-p2p/hostname | tr -d '\n' | sed 's/\.onion$//')

./target/release/kohl \
  --chain kohl-ash \
  --validator \
  --base-path ~/.kohl/chain \
  --node-key-file ~/.kohl/node-key \
  --listen-addr /ip4/127.0.0.1/tcp/30333 \
  --public-addr "/onion3/${ONION}:30333" \
  --rpc-port 9944 \
  --rpc-cors localhost \
  --network-backend libp2p \
  --mining-seed <64-hex>

Multiaddr uses the 56-character v3 name without the .onion suffix. Publish to peers:

multiaddr
/onion3/<v3>:30333/p2p/<PeerId>

L3 — Wallet / RPC over Tor

  • Same machine: keep RPC on 127.0.0.1:9944; wallet uses localhost. Node handles Tor for p2p.
  • Remote wallet: enable a dedicated RPC onion; client uses SOCKS:
shell
curl --socks5-hostname 127.0.0.1:9050 \
  -H 'content-type: application/json' \
  -d '{"id":1,"jsonrpc":"2.0","method":"system_health","params":[]}' \
  http://<rpc-onion>.onion:9944

Treat an onion RPC like a secret URL. Do not expose author_* broadly. Prefer a full node you control over third-party public RPC.

Checklist

  • check.torproject.org via SOCKS reports IsTor: true
  • --listen-addr is localhost when using a hidden service
  • Tor HiddenServicePort target matches Kohl’s listen port
  • Onion multiaddr omits .onion; PeerId stable via --node-key-file
  • RPC not on 0.0.0.0 unless intentional and filtered
  • Logs show Dandelion++ enabled (kohl::dandelion)

Go deeper

← Back home