brainstate

0xciph3r/ARFL · source

Snapshot 0 · 2026-11-04T12:47:28Z · Go ·

Content fingerprint0dbbd53124d1b801a65a6125d969a13d11ca72ad51e60a3f09db0de720174349

GitHub description at capture

Privacy-respecting bandwidth marketplace — A decentralised VPN protocol powered by Bitcoin.

README at capture

<p align="center">
  <img src="assets/brand/arfl-banner.png" alt="ARFL" width="720">
</p>

**Privacy-respecting bandwidth marketplace** — A decentralised VPN protocol powered by Bitcoin.

ARFL is a decentralised VPN protocol that combines WireGuard, Nostr, the Bitcoin Lightning Network, and Cashu ecash into a self-sustaining privacy network. No accounts. No subscriptions. No logs. No token.

Users pay per-gigabyte via Lightning. Node operators offer bandwidth they already own; automated payouts are not yet reliable (see [operator quick start](#operator-quick-start-ubuntu)). The hub coordinates sessions but **mathematically cannot link buyers to their browsing activity** thanks to Cashu blind signatures (BDHKE).

[![CI](https://github.com/0xciph3r/ARFL/actions/workflows/ci.yml/badge.svg)](https://github.com/0xciph3r/ARFL/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/0xciph3r/ARFL)](https://github.com/0xciph3r/ARFL/releases/latest)

## How It Works

```
┌──────────┐     Lightning      ┌──────────┐     Nostr relays     ┌──────────┐
│  Client   │ ──── pay ────────► │   Hub    │ ◄── announcements ── │  Nodes   │
│           │ ◄── Cashu tokens ─ │  (mint)  │                      │          │
└─────┬─────┘                    └──────────┘                      └────┬─────┘
      │                                                                 │
      │  token delivery                                                 │
      └────────────────── WireGuard tunnel ────────────────────────────►│
                          (entry → exit → internet)
```

1. **Client** pays a Lightning invoice for bandwidth (e.g., 500 sats for 1 GB)
2. **Hub** mints Cashu ecash tokens — blind-signed via BDHKE (Blind Diffie-Hellman Key Exchange)
3. **Client** unblinds tokens locally — Hub mathematically cannot link tokens to the buyer
4. **Client** delivers tokens to the **Node** — either direct `/cashu-connect` HTTP (`token_delivery: "http"`) or NIP-44 encrypted Nostr request/reply (`token_delivery: "nip44"`)
5. **Node** verifies proofs with the Hub's `/v1/redeem`, grants WireGuard access
6. Traffic flows through a **nested two-hop WireGuard tunnel** (entry → exit → internet)

### Privacy Properties

| Property | How |
|---|---|
| **Buyer-session unlinkability** | Cashu BDHKE — Hub signs blinded messages, cannot link tokens to buyers |
| **Token delivery privacy** | NIP-44 encrypted request/reply events — Hub and relays cannot read token contents in transit (`internal/client/token_sender.go`, `internal/node/token_receiver.go`) |
| **Entry node can't see destinations** | Inner WireGuard tunnel encrypts traffic end-to-end to the exit |
| **Exit node can't see client IP** | Only sees the entry node's IP (NAT'd) |
| **No accounts or identity** | Lightning payments require no personal information |
| **No logs by design** | Nodes track byte counters only; no destination logging |

### Honest Threat Model

ARFL is a **privacy-respecting bandwidth marketplace** — not an untraceable VPN. Key caveats:

- The Hub is the real-time arbiter for double-spend detection. Nodes must contact it for `/v1/redeem` checks.
- The residential node economics model works for flat-rate fiber operators. Commercial cloud hosting is explicitly not viable at current pricing.
- Two-hop routing means every GB costs 2x bandwidth. At $5/250GB, nodes clear ~$0.008/GB each — viable for unmetered pipes, not cloud servers.
- Hub observability is reduced (not eliminated): with `discovery_source: "nostr"` the client builds its node list from relay announcements and pairs hops locally without a hub `/nodes` fetch timing anchor. The hub still sees `/v1/redeem` timing and values.
- Token delivery mode is explicit in `client.json`: `http` (default) or `nip44`. In `http` mode, the entry node sees the client source IP by design, while the exit provisioning request is routed through the outer tunnel. In `nip44` mode, proof delivery and connect replies happen over encrypted Nostr events, so clients do not call node `/cashu-connect` endpoints directly.
- The client now uses distinct WireGuard keys per hop and rotates them after failed connect attempts; this removes key-reuse linkage between entry and exit for retries and active sessions.
- Tunnels route IPv4 only (`0.0.0.0/1` and `128.0.0.0/1`). Before payment, preflight temporarily installs, verifies and removes the outbound IPv6 block; on connection, the privileged tunnel helper installs it again *before* creating the outer hop. Failure or incomplete preflight cleanup aborts the connection. Linux uses `ip6tables` (`OUTPUT`), macOS uses a `pf` filter anchor (requires the standard `com.apple/*` anchor hook), and Windows uses a Windows Firewall outbound rule (all profiles must be enabled and accept local rules). Teardown removes only ARFL's rule/token. This is **not** a general kill switch: it does not stop unprotected IPv4 traffic during tunnel outages or cover forwarded traffic, and an administrator/firewall-policy change during the session may invalidate protection. Force termination can leave rules behind until explicitly removed or the OS resets them; verify on real dual-stack hardware before relying on the feature.

## Installation

### Prerequisites

- **Go 1.26.3+** — [install](https://go.dev/dl/)
- **WireGuard** — `apt install wireguard wireguard-tools` (Linux) or `brew install wireguard-tools` (macOS)
- **nftables** (Linux nodes only) — `apt install nftables` (for kernel-level quota enforcement)
- **ip6tables** (Linux clients, including the desktop app) — `apt install iptables` (required for the outbound IPv6 block; without it, connection preflight fails before payment)
- **LND** (hub) — Lightning node with REST API enabled (`mainnet` for live sats, `testnet/signet` for E2E dry runs; [Polar](https://lightningpolar.com) for local dev)

### Build from Source

```bash
git clone https://github.com/0xciph3r/ARFL.git
cd ARFL
mkdir -p bin
go build -o bin/arfl-hub ./cmd/arfl-hub
go build -o bin/arfl-node ./cmd/arfl-node
go build -o bin/arfl-client ./cmd/arfl-client
go build -o bin/arfl ./cmd/arfl
```

This produces four binaries:

| Binary | Purpose |
|---|---|
| `bin/arfl-hub` | Coordination hub — discovery, payments, blind signing |
| `bin/arfl-node` | Node daemon — WireGuard tunnel endpoint (entry or exit) |
| `bin/arfl-client` | Client CLI — purchase bandwidth, connect to nodes |
| `bin/arfl` | Setup helper — guided config generation (`init`) and diagnostics (`doctor`) |

> **Important (production deploy):** build the hub with CGO enabled (default).
> `CGO_ENABLED=0` will compile, but SQLite will be a stub and `arfl-hub` exits
> at startup with `go-sqlite3 requires cgo to work`.

### Install from GitHub Release Assets (Linux amd64)

If you just want to run the latest released hub/node binaries:

```bash
VERSION=v0.1.0
BASE_URL="https://github.com/0xciph3r/ARFL/releases/download/${VERSION}"

mkdir -p /usr/local/bin /tmp/arfl-release && cd /tmp/arfl-release

# curl (or use wget commands below)
curl -fL -o arfl-hub-linux-amd64  "${BASE_URL}/arfl-hub-linux-amd64"
curl -fL -o arfl-node-linux-amd64 "${BASE_URL}/arfl-node-linux-amd64"
curl -fL -o checksums.txt         "${BASE_URL}/checksums.txt"

# verify + install
sha256sum -c checksums.txt
install -m 755 arfl-hub-linux-amd64  /usr/local/bin/arfl-hub
install -m 755 arfl-node-linux-amd64 /usr/local/bin/arfl-node
```

`wget` equivalent:

```bash
wget "${BASE_URL}/arfl-hub-linux-amd64"
wget "${BASE_URL}/arfl-node-linux-amd64"
wget "${BASE_URL}/checksums.txt"
```

### Automated Node Setup (Ubuntu)

For production node servers, the setup script installs WireGuard, nftables, enables IP forwarding, and opens the UDP port:

```bash
sudo bash deployments/setup-node.sh
```

### Automated Hub Setup (Ubuntu)

For production hub servers, the setup script installs dependencies, builds binaries, and
generates `/opt/arfl/data/hub.json` with secure defaults:

```bash
sudo bash deployments/setup-hub.sh
```

The script is idempotent for hub identity: if `/opt/arfl/data/hub.json` already exists,
it does not overwrite it by default. Set `ARFL_REGENERATE_HUB_CONFIG=1` only when you
intentionally want fresh hub secrets and identity.

### Guided Setup (No Manual JSON Editing)

Use the setup helper to generate configs and validate them.
`arfl init` uses Charm interactive forms (Bubble Tea + Huh), while `arfl doctor`
uses Lip Gloss styling for readable pass/warn/fail diagnostics.

```bash
# Generate hub config + secrets
./bin/arfl init hub --output hub.json

# Generate node config + keys
./bin/arfl init node --role entry --endpoint <public-ip>:51820 --output node-entry.json

# Validate configuration and runtime health
./bin/arfl doctor hub --config hub.json --url http://127.0.0.1:8080
./bin/arfl doctor node --config node-entry.json --hub-url http://<hub-ip>:8080
```

`arfl init hub` / `arfl init node` refuse to overwrite existing output files unless
you pass `--force` (which creates a timestamped backup first).

For CI/automation, disable the interactive TUI with `--non-interactive` and pass flags directly.

## Operator quick start (Ubuntu)

These steps use separate hub, entry-node, and exit-node hosts. Run commands on the
named host, replace example addresses with your own, and use the **same Nostr relays**
on hub and nodes. See [the deployment guide](docs/deployment-guide.md) for LND
credentials, firewalls, HTTPS, and systemd services. This is experimental software;
do not advertise paid node earnings as guaranteed (see step 4).

1. **Hub operator: prepare and start the hub.** Provide an LND REST endpoint with
   a TLS certificate and macaroon; use mainnet for real payments or signet for
   testing. On the hub host, from a checkout of this repository:

   ```bash
   sudo bash deployments/setup-hub.sh
   # Supply the LND host when prompted, then set the correct LND credential
   # paths in /opt/arfl/data/hub.json (see the deployment guide).
   sudo arfl doctor hub --config /opt/arfl/data/hub.json
   sudo arfl-hub --config /opt/arfl/data/hub.json
   # In another terminal:
   curl -fsS http://127.0.0.1:8080/health
   ```

   Set a recognizable `name` in `hub.json` and expose the API through HTTPS
   for remote nodes and clients. Keep `hub.json`, the SQLite database, and
   `/opt/arfl/data/keys/` private and backed up; regenerating the hub keys can
   invalidate existing tokens. The setup script builds the hub and `arfl`
   helper but does **not** install or start a systemd service.

2. **Node operator: prepare one host per role.** On each node host, install
   WireGuard and nftables, then build `arfl` and `arfl-node` with the Go version
   in [Prerequisites](#prerequisites). `setup-node.sh` prepares the OS, but its
   bundled Go installer may be older than the required version; install the
   required Go version separately if needed.

   ```bash
   sudo bash deployments/setup-node.sh
   go build -o arfl ./cmd/arfl
   go build -o arfl-node ./cmd/arfl-node
   sudo install -d -m 700 /opt/arfl/data /opt/arfl/data/keys
   sudo ./arfl init node --role entry --endpoint <entry-public-ip>:51820 \
     --out-interface <internet-interface> --hub-url https://<hub-domain> \
     --hub-pubkey-file /opt/arfl/data/keys/key-100mb.pub.json \
     --output /opt/arfl/data/node.json
   ```

   On the **exit** host, substitute `--role exit` and
   `--endpoint <exit-public-ip>:51821`; the helper selects the corresponding
   WireGuard port, interface, and tunnel address. Record each host's **Nostr**
   and **WireGuard** public keys printed by `arfl init node`.
   Keep `node.json` private. Open the role's WireGuard UDP port and TCP 9091
   for the connect API, but keep the admin API (default `127.0.0.1:9090`)
   private. Both nodes need to reach the hub and the configured Nostr relays.

3. **Hub and node operators: approve and connect each node.** Give the hub
   operator the two public keys and desired role, **not** `node.json`. On the
   hub host, repeat for each node (using the matching keys and role):

   ```bash
   sudo arfl-hub attest --config /opt/arfl/data/hub.json \
     --node-pubkey <node-nostr-pubkey> --node-wg-key <node-wireguard-pubkey> \
     --operator <operator-id> --role entry --lease 90d \
     --out /tmp/entry-attestation.json
   ```

   Transfer the resulting attestation and the hub's
   `/opt/arfl/data/keys/key-100mb.pub.json` to that node securely (see
   [deployment steps 7-9](docs/deployment-guide.md#7-configure-the-entry-node)
   for file-copy examples). On the node host, save the public key at the
   `hub_pubkey_file` path and put the attestation **as a JSON string** in the
   `attestation` field of `node.json` (not as a nested JSON object). Check
   `hub_url` and `relays`, then run:

   ```bash
   sudo ./arfl doctor node --config /opt/arfl/data/node.json \
     --hub-url https://<hub-domain>
   sudo ./arfl-node --config /opt/arfl/data/node.json
   # Check the hub from another terminal:
   curl -fsS https://<hub-domain>/nodes
   ```

   A node announces itself to relays; the hub discovers it and verifies its
   attestation. Sharing a hub URL alone does not register a node. Renew
   attestations last six hours; the node refreshes them automatically near
   expiry while the hub-issued lease remains active (90 days in this example).
   Check node logs if refresh fails, and arrange lease renewal with the hub
   operator before the lease expires. A usable two-hop route needs two distinct
   nodes serving entry and exit.

4. **How node payment is intended to work (not yet reliable for live payouts).**
   Clients buy bandwidth from a hub and use it through entry and exit nodes.
   The hub aggregates eligible two-sided usage reports from redeemed,
   paid sessions, subtracts its margin (default 20%), and allocates the
   remainder equally to the two nodes. It runs settlement every six hours
   by default; entries below `min_payout_sats` (default 1,000) are not sent
   and currently do not carry forward into later settlement periods.
   Check `GET /node/<node-nostr-pubkey>/earnings` and the hub's settlement
   logs for accounting and failed or in-flight payments.

   **Current payout limitation:** the settlement code sends LND Keysend to
   the usage-report node key, not a separately configured Lightning node
   public key. Registering a Lightning address via `/node/wallet` does not
   change that destination; `/node/withdraw` also sends to the supplied node
   key. A Nostr or WireGuard key is not a Lightning payment destination, so
   these endpoints are **not a working payout setup** for ordinary nodes.
   Hub operators must reconcile usage and arrange payouts separately until
   authenticated Lightning destinations and end-to-end payout verification
   are implemented; never retry an in-flight payment without reconciling it.

## Configuration

### Hub (`hub.json`)

```json
{
  "nostr_privkey": "<64-char hex Nostr private key>",
  "listen_addr": "0.0.0.0:8080",
  "relays": ["wss://relay.damus.io", "wss://nos.lol"],
  "db_path": "arfl.db",
  "credential_key": "<64-char hex HMAC secret>",
  "blind_key_dir": "keys/",
  "settlement_hours": 6,
  "min_payout_sats": 1000,
  "lnd_host": "localhost",
  "lnd_port": 8080,
  "lnd_tls_cert_path": "~/.lnd/tls.cert",
  "lnd_macaroon_path": "~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon",
  "lnd_fee_limit_sat": 100
}
```

#### Environment Variable Overrides

Sensitive config can be set via environment variables (recommended for production). These take priority over `hub.json`:

| Env Var | Overrides | Purpose |
|---|---|---|
| `ARFL_LND_HOST` | `lnd_host` | LND REST hostname |
| `ARFL_LND_PORT` | `lnd_port` | LND REST port |
| `ARFL_LND_TLS_CERT_PATH` | `lnd_tls_cert_path` | Path to LND's tls.cert |
| `ARFL_LND_MACAROON_PATH` | `lnd_macaroon_path` | Path to admin.macaroon |
| `ARFL_LND_FEE_LIMIT_SAT` | `lnd_fee_limit_sat` | Max routing fee per payment |
| `ARFL_CREDENTIAL_KEY` | `credential_key` | HMAC secret for tickets |
| `ARFL_NOSTR_PRIVKEY` | `nostr_privkey` | Hub's Nostr private key |
| `ARFL_DB_PATH` | `db_path` | SQLite database path |

```bash
# Development mode (auto-generates credential key — NOT for production):
./arfl-hub --config hub.json --dev

# Production:
./arfl-hub --config hub.json
```

On first run, the Hub generates an RSA denomination key in `keys/key-100mb.json` and exports the public key to `keys/key-100mb.pub.json`. **Distribute the `.pub.json` file to all nodes.**

### Listing your hub in the apps

Hubs on [`hubs/registry.json`](hubs/registry.json) appear in every ARFL app as **Trusted by ARFL**. Set a `"name"` in your `hub.json`, serve the hub over HTTPS on a domain, then open a pull request adding it to the registry. Merging the pull request publishes it; apps pick it up within a day. See [`hubs/README.md`](hubs/README.md).

### Node (`node.json`)

```bash
# Generate a WireGuard keypair:
./arfl-node --genkey
```

**Entry node** (`node-entry.json`):
```json
{
  "role": "entry",
  "listen_port": 51820,
  "private_key": "<base64 WireGuard private key>",
  "tunnel_ip": "10.100.0.1/24",
  "interface": "wg-entry",
  "out_interface": "eth0",
  "admin_addr": "127.0.0.1:9090",
  "endpoint": "<public-ip>:51820",
  "upload_mbps": 100,
  "download_mbps": 100,
  "capacity": 50,
  "nostr_privkey": "<64-char hex>",
  "relays": ["wss://relay.damus.io"],
  "attestation": "<hub-issued attestation JSON>",
  "hub_url": "http://<hub-ip>:8080",
  "hub_pubkey_file": "keys/key-100mb.pub.json",
  "connect_addr": "0.0.0.0:9091",
  "enabled_transports": ["wireguard"],
  "transport_endpoints": {
    "wireguard": "<public-ip>:51820",
    "hysteria2": "<public-ip>:443",
    "amneziawg": "<public-ip>:51830"
  },
  "transport_connect_addrs": {
    "wireguard": "0.0.0.0:9091",
    "hysteria2": "0.0.0.0:9092",
    "amneziawg": "0.0.0.0:9093"
  }
}
```

**Exit node** (`node-exit.json`):
```json
{
  "role": "exit",
  "listen_port": 51821,
  "private_key": "<base64 WireGuard private key>",
  "tunnel_ip": "10.200.0.1/24",
  "interface": "wg-exit",
  "out_interface": "eth0",
  "admin_addr": "127.0.0.1:9090",
  "connect_addr": "0.0.0.0:9091",
  "endpoint": "<public-ip>:51821",
  "upload_mbps": 100,
  "download_mbps": 100,
  "capacity": 50,
  "nostr_privkey": "<64-char hex>",
  "relays": ["wss://relay.damus.io"],
  "attestation": "<hub-issued attestation JSON>",
  "hub_url": "http://<hub-ip>:8080",
  "hub_pubkey_file": "keys/key-100mb.pub.json",
  "connect_addr": "0.0.0.0:9091",
  "enabled_transports": ["wireguard"],
  "transport_endpoints": {
    "wireguard": "<public-ip>:51821",
    "hysteria2": "<public-ip>:443",
    "amneziawg": "<public-ip>:51831"
  },
  "transport_connect_addrs": {
    "wireguard": "0.0.0.0:9091",
    "hysteria2": "0.0.0.0:9092",
    "amneziawg": "0.0.0.0:9093"
  }
}
```

`enabled_transports`, `transport_endpoints`, and `transport_connect_addrs` are negotiation metadata: they are advertised in node discovery, but current active runtime transport remains WireGuard unless transport adapters are enabled in code.
Today, node `/connect` and `/cashu-connect` are served on one shared connect API listener (`connect_addr`), so advertised `connect_url` currently resolves to that shared endpoint.

### Client

**Purchase bandwidth:**
```bash
./arfl-client --purchase 1gb \
  --hub-url http://<hub-ip>:8080 \
  --hub-key keys/key-100mb.pub.json
```

This will:
1. Create a Lightning invoice (displayed as BOLT11)
2. Wait for you to pay it
3. Prompt for the payment preimage
4. Redeem blind tokens and save to `tokens.json`

**Connect to the network (Phase 1 static mode):**
```bash
sudo ./arfl-client --session session.json --key client.key
```

**Discover nodes dynamically (Phase 2):**
```bash
sudo ./arfl-client --discover http://<hub-ip>:8080 \
  --hub-pubkeys <hub-nostr-pubkey> \
  --key client.key
```

**Optional client transport policy (`client.json`):**
```json
{
  "hub_url": "http://<hub-ip>:8080",
  "hub_pubkeys": ["<trusted-hub-nostr-pubkey>"],
  "discovery_source": "nostr",
  "token_delivery": "nip44",
  "relays": ["wss://relay.damus.io", "wss://nos.lol"],
  "preferred_transports": ["hysteria2", "wireguard"],
  "allowed_transports": ["wireguard", "hysteria2"],
  "require_common_hop_transport": true
}
```

Desktop loads this policy from `client.json` in the current working directory by default, or from
`ARFL_CLIENT_CONFIG=/path/to/client.json` when set.
Unknown transport names in `preferred_transports` / `allowed_transports` are treated as configuration errors.
If `require_common_hop_transport` is explicitly set to `false`, `arfl-desktop` rejects the policy until mixed-hop adapters are implemented.
Unknown `token_delivery` values are treated as configuration errors.
Unknown `discovery_source` values are treated as configuration errors. `discovery_source: "nostr"` requires both `relays` and `hub_pubkeys`. NIP-44 delivery also requires Nostr discovery with trusted hub keys: hub `/nodes` responses do not authenticate node encryption keys. HTTP delivery still supports hub discovery.

The desktop automatically selects and displays its entry and exit before connecting (the sole eligible pair when there is one, otherwise a random eligible pair); manual selection is optional. It prefers separately operated nodes, but if none are available it can use two distinct nodes of one operator. The Privacy panel explains that different node keys do not guarantee different operators. Connect rechecks the displayed nodes against the current node index and refuses to spend proofs if either is unavailable; **Pick another pair** selects a new route. Manually chosen nodes remain pinned until the user switches back to automatic selection.

From the menu bar popover, **Choose nodes to connect** opens the main Nodes view. Review the displayed pair there, then click **Connect** in the main window to start a session. The popover can still disconnect an active session.

The desktop checks the hub's `/info` endpoint separately from tunnel status. Repeated failed checks produce a non-blocking warning, not proof that the hub is down or that the tunnel disconnected; **Retry check** tests reachability from this device. Reconnecting can spend another pair of proofs, so it is never automatic. On macOS, the privileged helper records tunnel bring-up and teardown method results (without token data) in the root-only `/var/log/io.arfl.helper.log` for diagnosing unexpected disconnects.

### Bandwidth Tiers

| Tier | Data | Price | Tokens |
|---|---|---|---|
| 1 GB | 1,000 MB | 500 sats | 10 × 100 MB |
| 10 GB | 10,000 MB | 4,500 sats | 100 × 100 MB |
| 50 GB | 50,000 MB | 20,000 sats | 500 × 100 MB |

```bash
sudo ./arfl-node --config node-entry.json
sudo ./arfl-node --config node-exit.json
```

## Docker Testnet

One-command local testnet with hub, 2 nodes, and a Nostr relay:

```bash
cd deploy
./init-testnet.sh          # generate WG keys, Nostr keys, configs
docker compose up --build  # start all services
./smoke-test.sh            # verify everything is healthy
```

Uses distroless base images for minimal attack surface. The hub image has zero shell access.

**With Polar (real Lightning):** Install [Polar](https://lightningpolar.com), create a network with 2 LND nodes, then update `data/hub/hub.json` with the LND connection details. See `deploy/docker-compose.yml` for the volume mount pattern.

## Architecture

```
cmd/
  arfl-hub/          Hub binary — discovery, payments, Cashu mint
  arfl-node/         Node binary — WireGuard tunnels, Cashu-gated /cashu-connect
  arfl-client/       Client binary — purchase flow, tunnel management
internal/
  client/            Bandwidth SDK + CashuConnector (node-side verifier)
  config/            JSON config loader for all components
  control/           Node admin API (POST /cashu-connect, /peers, /quota)
  credentials/       RSA blind signatures, HMAC tickets, key persistence
  discovery/         Nostr-based node discovery + attestation + Cashu API
  ecash/             Cashu ecash mint (BDHKE, NUT-01→07, worker pool)
  lightning/         Lightning client interface (LND REST adapter + circuit breaker)
  nostr/             Nostr relay pool, NIP-44 encryption, keypairs, events
  payments/          Purchase API, settlement engine, node payouts
  quota/             Kernel-level bandwidth enforcement (nftables)
  routing/           IP forwarding + NAT setup
  store/             SQLite storage (invoices, proofs, keysets, quotes, settlements)
  wg/                WireGuard interface management (wgctrl)
pkg/
  protocol/          Protocol constants (MTU, ports, intervals)
  types/             Shared types (NodeInfo, NodeRole)
test/
  integration/       E2E tests (full Cashu privacy flow)
  loadtest/          Performance load tests (/v1/redeem throughput)
deployments/         systemd units, nftables rules, setup scripts
deploy/              Docker Compose testnet (hub + nodes + relay)
docs/                Architecture, API spec, deployment guide
```

See [docs/architecture.md](./docs/architecture.md) for the nested WireGuard two-hop design, encryption layers, and routing rules.

## Testing

408 tests across 10 packages, all passing with `-race`:

```bash
go test -race ./...
```

### Security Testing (STRIDE)

ARFL includes dedicated threat model tests across the STRIDE categories:

| Category | Server-side | Client-side | Total |
|---|---|---|---|
| Spoofing | 2 | 3 | 5 |
| Tampering | 2 | 5 | 7 |
| Repudiation | 2 | 1 | 3 |
| Information Disclosure | 1 | 4 | 5 |
| Denial of Service | 2 | 3 | 5 |
| Elevation of Privilege | 3 | 2 | 5 |
| Concurrent / Race | 2 | 2 | 4 |
| **Total** | **14** | **20** | **34** |

Run STRIDE tests specifically:
```bash
go test -race -v ./... -run STRIDE
```

### Integration Tests

End-to-end tests that exercise the full protocol in-process (no infrastructure required):

```bash
go test -race -v ./test/integration/
```

Covers: purchase → pay → redeem → verify → spend → double-spend detection → unlinkability proof.

## Protocol Flow (Detailed)

```
 Client                          Hub                           Node
   │                              │                              │
   │  POST /v1/mint/quote/bolt11  │                              │
   │  {amount: 500, unit: "sat"}  │                              │
   │ ────────────────────────────►│                              │
   │  ◄─── Lightning invoice ─── │                              │
   │                              │                              │
   │  [pays invoice externally]   │                              │
   │                              │  settlement listener         │
   │                              │  marks quote as PAID         │
   │                              │                              │
   │  POST /v1/mint/bolt11        │                              │
   │  {quote, blinded_messages}   │                              │
   │ ────────────────────────────►│                              │
   │  ◄─── blind_signatures ──── │                              │
   │                              │                              │
   │  [unblinds signatures        │                              │
   │   locally — Hub never        │                              │
   │   sees token secrets]        │                              │
   │                              │                              │
   │  NIP-44 encrypted event      │                              │
   │  {proofs, wg_pubkey, role}   │                              │
   │ ─────────────────── via Nostr relay ──────────────────────►│
   │                              │                              │
   │                              │  POST /v1/redeem             │
   │                              │  {proofs, node_pubkey}       │
   │                              │ ◄──────────────────────────  │
   │                              │  ── {ok, bytes_allowed} ──► │
   │                              │                              │
   │  ◄──── {tunnel_ip, pubkey, endpoint} ───────────────────── │
   │                              │                              │
   │  [WireGuard tunnel established]                             │
   │ ═══════════════ encrypted traffic ════════════════════════► │
```

## Development

```bash
# Build all binaries
go build ./...

# Run all tests with race detection
go test -race ./...

# Check formatting
gofmt -l .

# Vet
go vet ./...
```

### Decision Log

87 architecture decisions are documented in [`.notes/decisions.md`](./.notes/decisions.md), covering:
- Phase 1: WireGuard transport design
- Phase 2: Nostr discovery protocol
- Phase 3: Lightning payment flow
- Phase 4: Blind signature architecture
- Phase 5: Client integration wiring

## Roadmap

- [x] **Phase 1** — WireGuard transport (nested two-hop tunnels)
- [x] **Phase 2** — Nostr discovery (node announcements, attestation)
- [x] **Phase 3** — Lightning payments (invoices, settlement, tickets)
- [x] **Phase 4** — Blind signatures (RSA mint, Chaumian tokens, STRIDE)
- [x] **Phase 5** — Client integration (SDK, TokenGate, binary wiring)
- [x] **Phase 6** — Token→connect flow, LND adapter, Docker testnet
- [x] **Phase 9** — Client-side node selection + Cashu token redemption
- [x] **Phase 10** — Node-side Cashu gate + hub redeemer
- [x] **Phase 11** — NIP-44 token envelope, sender and receiver, wired request/reply delivery path
- [x] **Phase 12** — E2E integration test (full privacy chain)
- [x] **Phase 13** — VPS deployment (3 servers, systemd)
- [x] **Phase 14** — Performance engineering (benchmarks, pprof, load test, optimization)
- [ ] **Phase 15** — LNbits extension (wallet integration)
- [ ] **Phase 16** — Mobile app (gomobile bindings)
- [ ] **Phase 17** — Multi-hop routing (>2 hops)
- [ ] **Paid-bandwidth lifetime** — redeemed bytes are currently forfeited after 30 minutes idle or a node restart; define, persist and surface the rule ([#51](https://github.com/0xciph3r/ARFL/issues/51))

### Protocol v2: privacy hardening

[Whitepaper v0.4](ARFL_Whitepaper_v0.4.pdf) documents these privacy hardening items; current implementation status:

- [x] **Setup-time IP exposure** — HTTP-mode exit provisioning is routed through the outer tunnel so the exit never sees the client's IP ([#46](https://github.com/0xciph3r/ARFL/issues/46))
- [x] **NIP-44 delivery end to end** — sender and receiver are wired with encrypted node-to-client replies (`token_delivery: "nip44"`) ([#47](https://github.com/0xciph3r/ARFL/issues/47))
- [x] **Per-hop WireGuard keys** — a separate key per hop, reset after failed connects ([#48](https://github.com/0xciph3r/ARFL/issues/48))
- [x] **Hub timing correlation mitigation** — optional client-side node pairing from the Nostr relay index (`discovery_source: "nostr"`), without hub `/nodes` mediation ([#49](https://github.com/0xciph3r/ARFL/issues/49))

## Performance

Benchmarked on Apple M1 Pro (single core):

| Operation | Throughput | Latency |
|-----------|-----------|---------|
| Token redeem (hub) | 15,000/sec | p50: 862µs |
| VerifyProofs (single) | 6,000/sec | 167µs |
| NIP-44 Encrypt | 360,000/sec | 2.8µs |
| ECDH (per session) | 7,300/sec | 137µs |

## Links

- [Whitepaper (PDF)](./ARFL_Whitepaper_v0.4.pdf)
- [Architecture](./docs/architecture.md)
- [API Specification](./docs/api-spec.md)
- [Deployment Guide](./docs/deployment-guide.md)
- [Releases](https://github.com/0xciph3r/ARFL/releases)

## Responsible Use

ARFL is experimental networking software intended for legitimate
privacy, networking, and bandwidth-sharing use cases.

Users and operators are responsible
for complying with the laws and regulations applicable to their use of the
software and infrastructure.

## License

No license is currently declared in this repository.