Privacy-preserving light wallet server for the DarkFi network. The default binary serves compact blocks for trial decryption. Experimental UnifOMR is a separate build (--features fhe-omr).
0.3.4. Same trial-decrypt default as 0.3.2; version bump aligned with Moonshine 3.1.7 / Nighthawk 3.01.08 light-wallet clients. Build pin 44ec78b6e (nighthawk-testnet, includes upstream tx-local fees and net hosts/refinery fixes).
0.3.2. Default build is trial decrypt: GetCompactOutputBodies, GetNullifiers, GetCompactDaoCalls, and auth-token history. GetLightInfo.omr_supported is false unless the binary is built with fhe-omr. DarkFi pin e47d3f5f2 (Arti 0.48). Proto 2.1.0.
0.3.1. Clue registration accepts monotonic key_version values, including the wallet constant 1. A lower version is rejected, and the same version is accepted only for the same clue key.
0.3.0. DarkFi pin 8af61b3f0 (nighthawk-testnet), the same checkout as the 3.00.026 wallets. That pin pays a fee from tx-local change when the change output covers it. Proto 2.0.0, with no 1.x compatibility path: historical tree snapshots for checkpoints, padded-range enforcement on GetNoteCommitments, GetNullifiers, and GetCompactOutputBodies (a power-of-two bucket of at least 1024, or one cut short within 64 blocks of the tip), a UnifOMR detection-key pool (RegisterDetectionKey, GetUnifOmrDigestFromPool; 24 h handles, 4096 handles and 8 GiB total), a SubscribeBlocks ChainTip stream, and GetCompactOutputBodies.
0.2.4. SendTransaction accepts extra_omr_clues, one UniFOMR clue per sender output (transfer change and fee change) in addition to the recipient clue. Each clue is stored against its output index. A repeat registration for an index that already has a clue is left as first-writer-wins.
- Architecture
- Prerequisites
- Build
- Run
- gRPC API
- Features
- Infrastructure sizing
- Related projects
- License
┌─────────────┐ gRPC (port 9067) ┌─────────────────────┐ JSON-RPC ┌──────────┐
│ Mobile / │ ◄────────────────────► │ darkfi-lightwalletd │ ◄──────────────► │ darkfid │
│ Moonshine │ Compact Blocks │ (cache + detector) │ Full Blocks │ (node) │
│ Wallet │ OMR Digests │ sled cache │ │ │
└─────────────┘ └─────────────────────┘ └──────────┘
The server:
- Subscribes to new blocks from
darkfidvia JSON-RPC - Strips ZK proofs, signatures, and PoW data into compact blocks
- Caches compact blocks in a local sled database
- Serves them to wallets over gRPC
- Serves those compact records for wallet trial decryption. Experimental UnifOMR detection runs only when the binary is built with
fhe-omr.
Production clients use the default binary (no fhe-omr). See docs/verification-checklist.md and docs/connectivity-triage.md. UnifOMR limits stay in docs/unifomr_mvp_limits.md.
| Requirement | Notes |
|---|---|
| Rust stable (edition 2021) | rustup |
protoc |
On PATH (tonic-build in build.rs) |
darkfi-nighthawk-testnet |
Sibling directory literally named ../darkfi-nighthawk-testnet at pin 44ec78b6e (nighthawk24 nighthawk-testnet). Upstream DarkFi is git.dark.fi/darkrenaissance/darkfi, not the GitHub mirror. That pin is not on git.dark.fi, so a master checkout is not enough. Reuse Android nighthawk-android-wallet/third_party/darkfi with ln -sfn. |
Running darkfid |
JSON-RPC endpoint for full blocks |
Recommended layout after ./scripts/build.sh:
~/GitHub/
darkfi-nighthawk-testnet/ # pin 44ec78b6e — or symlink to Android third_party/darkfi
darkfi-lightwalletd/ # this repo
moonshine/
nighthawk-android-wallet/
nighthawk-ios-wallet/
# Reuse the Android pin instead of a second clone:
ln -sfn nighthawk-android-wallet/third_party/darkfi darkfi-nighthawk-testnetbrew install protobuf # macOS
sudo apt install protobuf-compiler # Debian/UbuntuIndependent clone (pulls darkfi-nighthawk-testnet next to this repo, then builds):
./scripts/build.sh
# Binary: ./target/release/darkfi-lightwalletdscripts/fetch-darkfi.sh clones/updates the sibling checkout pinned by
scripts/darkfi.rev (44ec78b6efbf1d3e797f99bffe95195994b2488d). Overrides:
DARKFI_DIR=/path/to/darkfi-nighthawk-testnet DARKFI_GIT_REF=<commit> ./scripts/fetch-darkfi.sh
./scripts/build.sh --features fhe-omr # experimental UnifOMR / FHEIf ../darkfi-nighthawk-testnet already exists (symlink to Android third_party/darkfi is OK):
cargo build --release --all-features
# Unit tests do not need compiled contract *.zk.bin (no test-harness dep).
cargo test --release --libCopy lightwalletd.toml.example and set network
to testnet or mainnet explicitly. Invalid TOML, missing files, or a
chain_name that disagrees with network exits the process (no silent
fallback to defaults).
darkfid_endpoint = "tcp://127.0.0.1:18345" # testnet; mainnet often 8345
# SECURITY: cleartext ONLY on loopback. Public binds REQUIRE TLS.
grpc_listen = "127.0.0.1:9067"
cache_path = "~/.local/share/darkfi/lightwalletd_cache"
poll_interval = 10
chain_name = "darkfi-testnet"
network = "testnet" # or "mainnet"
# Public / non-loopback example:
# grpc_listen = "0.0.0.0:9067"
# tls_cert_path = "~/.config/darkfi/lightwalletd.crt"
# tls_key_path = "~/.config/darkfi/lightwalletd.key"Ops notes:
~incache_path/ TLS paths is expanded via$HOME- Default cache root is scoped by network:
…/lightwalletd_cache/testnetor…/lightwalletd_cache/mainnetso compact blocks for the two networks never share a sled DB - Chain poller stores compact blocks from
darkfidinto that cache and serves them over gRPC - UnifOMR detection keys must carry the matching network byte (
0x00mainnet /0x01testnet)
Cleartext on non-loopback hosts is refused at startup.
# Self-signed (LAN / Mac Studio) or Let's Encrypt (public DNS):
./scripts/generate_tls_cert.sh self-signed --domain studio.local
# ./scripts/generate_tls_cert.sh letsencrypt --domain lw.example.com --email ops@example.com
# Emits certs + LIGHTWALLET_TLS_PIN_SHA256 (leaf DER SHA-256) under scripts/certs/Full pin guide: docs/TLS_PINNING.md.
# Ensure darkfid is up on darkfid_endpoint (same network as config.network)
cargo run --release -- --config lightwalletd.toml
# or: ./target/release/darkfi-lightwalletd --config lightwalletd.toml| Client | How |
|---|---|
| Moonshine | server_url = "http://127.0.0.1:9067" (or https://… behind a trusted path) |
| Android | App settings / -PLIGHTWALLET_TLS_PIN_SHA256=<64hex> for remote HTTPS |
| iOS | LIGHTWALLET_TLS_PIN_SHA256 build setting / UserDefaults override |
Clients must use the same network / chain_name family as this server.
Defined in proto/lightwallet.proto.
| RPC | Purpose |
|---|---|
GetLightInfo |
Version, tip, omr_supported |
GetBlock / GetBlockRange |
Compact blocks |
GetCompactBlocksAtHeights |
Sparse compact blocks |
GetChainTip |
Tip height + hash |
SendTransaction |
Broadcast + optional omr_clue → clue_accepted |
RegisterOmrClue |
Transitional clue register (peer-bound) |
GetUnifOmrDigest |
UnifOMR Round 1 (scheme 0x05) |
FetchPirBatch |
UnifOMR Round 2 |
RegisterCluePublicKey / GetCluePublicKey |
UnifOMR send-path registry |
GetOmrCapabilities |
Disabled in the default build. unifomr only with --features fhe-omr |
GetNullifiers / GetNoteCommitments |
Spend / commitment sync |
OmrDigestRequest:
detection_key— legacy single keydetection_keys— multi-address (preferred when N > 1)- Multi-key response framing: repeated
[u32 LE len][digest bytes]
| Field | Values |
|---|---|
| Network | Mainnet 0x00 · Testnet 0x01 |
| Scheme | UnifOMR 0x05 (only) |
| Multi-address | Cap 16 keys; SIMD slots = block heights |
| Feature | Status | Notes |
|---|---|---|
| TLS off-loopback (S6) | ✅ | Cleartext only on 127.0.0.1 / ::1 / localhost |
| Optional PEM TLS | ✅ | tls_cert_path + tls_key_path |
| OMR rate limit | ✅ | omr_rate_limit_per_min |
| Clue rate limit | ✅ | Tighter on RegisterOmrClue |
| Detection key size cap | ✅ | Reject oversized UnifOMR keys (~48 MiB) |
| Max block range | ✅ | Caps range RPCs (DoS) |
| Feature | Status | Notes |
|---|---|---|
| Proof/signature stripping | ✅ | Compact blocks only |
| Empty-height FHE slots (S19) | ✅ | Every height in range occupies a SIMD slot |
| Skip note clone when clue present (P2) | ✅ | Detector uses omr_clue when set |
| Clue hint TTL (S21) | ✅ | Orphan hints expire (24h) |
| First-empty / indexed clue apply (S20) | ✅ | One clue → first empty output or explicit index |
| No per-hint sled.flush (P8) | ✅ | Flush with block insert / shutdown |
| Feature | Status | Notes |
|---|---|---|
| Trial decrypt compact blocks (default) | ✅ | GetCompactOutputBodies, nullifiers, DAO calls |
| FetchPirBatch | ✅ | Length-prefixed SealPIR-style limbs |
| RegisterCluePublicKey / GetCluePublicKey | ✅ | Decoy directory; 250 ms timing pad |
| FHE any-match (S1) | ✅ | Per-clue layers, client OR |
| Multi-key digest (S17) | ✅ | Cap 16 |
| spawn_blocking OMR (P1) | ✅ | Off the async gRPC worker |
| Timing pad | ✅ | Min response time on UnifOMR + clue-dir |
| UnifOMR clue validate | ✅ | Size + parse + noise bound |
| Feature | Status | Notes |
|---|---|---|
| SendTransaction + omr_clue | ✅ | Hint keyed by tx hash and output index (24h TTL). extra_omr_clues covers the sender's other outputs |
| clue_accepted (S22) | ✅ | Tx may succeed while clue is rejected |
| RegisterOmrClue peer bind (S12) | ✅ | Prior SendTransaction from same peer within 24h |
| Envelope parse | ✅ | Wire OMR envelope can supply FHE clue |
- Experimental. Build with
--features fhe-omr. The default binary does not serve it. - Cross-client crypto parity: RLWE
n=1024, signed AHE,CLUE_ERROR_BOUND=2, length-prefixed SealPIR limbs - Flow: RegisterCluePublicKey → GetClue → SendTransaction(omr_clue) → GetUnifOmrDigest → FetchPirBatch
- Empty OMR → clients may supplemental trial-decrypt (decoy directory ≠ failure)
- Clients default to
http://127.0.0.1:9067;network/chain_namemust match wallets - Clue hints from
SendTransactionlive 24h until the confirming block is indexed - Limits:
docs/unifomr_mvp_limits.md - Sync speed (snapshots, padded ranges, detection-key pool, tip subscribe):
docs/wallet-sync-speed-plan.md
| Item | Status |
|---|---|
Crypto parity LWD ↔ moonshine ↔ iOS ↔ Android (n=1024) |
✅ |
| Registration matrix + digest round-trip | ✅ |
| PIR assemble / CompactBlock reassembly | ✅ |
Clue noise bound vs R_PRIME |
✅ |
| Clue hint 24h TTL · GetClue 250ms pad | ✅ |
Decoy directory found channel |
✅ |
| Funded broadcast → confirm → PIR e2e | ⏳ needs live darkfid |
Paper Param2 structural params (n,q,h,r,D,ℓ) |
✅ active — see docs/unifomr_mvp_limits.md; archived MVP in docs/unifomr_mvp_archive.md |
| Paper FP/FN rates | ❌ not claimed until digest mod-switch + measured FP |
| Invariant | Status |
|---|---|
| No wallet accounts / session identity | ✅ |
| Do not log detection keys or match heights | ✅ |
| Sanitize darkfid errors to clients | ✅ |
| Resource | 1k users | 10k | 100k |
|---|---|---|---|
| CPU | 2 | 8 | 32+ (detector pool) |
| RAM | 4 GB | 16 GB | 64+ GB |
| Storage | 50 GB SSD | 200 GB | 1 TB NVMe |
| Bandwidth | ~50 Mbps | ~500 Mbps | ~5 Gbps |
| Project | Role |
|---|---|
| moonshine | CLI light wallet |
| nighthawk-android-wallet | Android |
| nighthawk-ios-wallet | iOS |
Copyright (C) 2026 Nighthawk Apps — AGPL-3.0-or-later (see LICENSE and COPYRIGHT).