Bitcoin Light currently integrates Breez SDK Spark for Bitcoin Lightning Network. Breez operates on a hosted LSP model:
a cloud service runs the LN node (via Greenlight), the mobile device holds only keys and signs locally via
defaultExternalSigner. Channel management, liquidity, routing, and watchtower are all handled by Breez.
Fiber is a self-hosted Lightning Network implementation built on Nervos CKB. It can run as a native binary
(fnn), as a native library compiled into mobile apps (via FFI), in WASM via @nervosnetwork/fiber-js,
or as a standalone RPC service. Unlike Breez, Fiber has no hosted LSP infrastructure — no cloud node
management, no delegated signing, no liquidity-as-a-service.
We need to decide how to integrate Fiber into the Bitcoin Light mobile wallet. Three approaches are presented below.
Despite the API naming (LightningInvoice, LightningPayment), Breez SDK Spark uses the Spark
protocol (Statechains), not Lightning Network's HTLC/commitment-transaction model.
At its core, Spark is a shared signing protocol on Bitcoin with a distributed ledger model. There are no payment channels, no commitment transactions, and no HTLCs in the traditional LN sense.
Spark uses FROST threshold signatures with a Spark Entity (SE) — a set of operators holding threshold key shares:
Aggregated public key: PubKey_Agg = PubKey_User + PubKey_SE
Transferring ownership of a "leaf" (Spark's equivalent of a UTXO):
PubKey_SE' = PubKey_SE + (sk_Sender - sk_Receiver) * G
= PubKey_SE + PubKey_Sender - PubKey_Receiver
The key insight: the SE can update state using only public key arithmetic. No private keys are needed from the sender or receiver. The user's private key is required only for:
- Sending payments (authorizing a transfer out)
- Unilateral exit (broadcasting a pre-signed exit transaction to L1)
For receiving, the user does not need to sign anything. The SE adjusts its key share, and the new state
is valid. When the receiver's device comes online, the SDK syncs and emits a PaymentSucceeded event.
This is fundamentally impossible in HTLC-based Lightning Network.
In traditional Lightning Network (including Fiber's FNP protocol), every channel state update requires both parties to sign a new commitment transaction:
Sender ──→ add_htlc ──→ Peer ──→ Recipient
│
commit_signed ← MUST sign here
│
revoke_and_ack ← MUST sign here
The commitment_signed message carries the recipient's signature over the new commitment transaction.
Without this signature, the HTLC cannot be added to the channel. This is a protocol-level constraint, not
an implementation detail.
BOLT12 (offers + onion messages + blinded paths) solves a different problem:
| Problem | BOLT11 | BOLT12 |
|---|---|---|
| Invoice is single-use and expires | Per-payment bolt11 string | Offer: static, long-lived payment identifier |
| Must know recipient's node_id | Routing hints in invoice | Onion messages: async communication through the network |
| Privacy | Invoice exposes recipient identity | Blinded paths: recipient hidden behind blinded node IDs |
BOLT12 makes invoice distribution asynchronous — a sender can request an invoice via onion message, and
the recipient replies when online. But this does not change the commitment signing requirement.
The commitment_signed round must still happen with the recipient online.
BOLT12 + an always-online LSP node = a complete solution: BOLT12 handles the "who to pay" problem, the LSP handles the "stay online to sign" problem. These two are complementary, not competing.
| Model | Protocol | Mechanism | User Offline OK? |
|---|---|---|---|
| Statechains (Breez Spark) | Spark / FROST | SE tweaks key share via public key arithmetic | Yes |
| LSP runs your node (Greenlight, Voltage, Zeus) | Traditional LN | LSP server is always online, signs on your behalf | Yes (node is online; user is offline) |
| Self-hosted (Umbrel, Phoenix, Approach 1) | Traditional LN | Your node must be online to sign commitments | No |
Compile fiber-lib as a native Rust library for the mobile app (via FFI), using SQLite for local storage. Only the watchtower is outsourced to a trusted third party.
┌────────────────────────────────────┐ ┌─────────────────────────────┐
│ Mobile Device │ │ Third-Party Watchtower │
│ │ │ │
│ ┌────────────────────────────────┐ │ │ Monitors CKB chain for │
│ │ fiber-lib (Rust native, cross- │ │ RPC │ channel cheating │
│ │ compiled for ARM64/x86_64) │ │──────→│ Signs settlement tx when │
│ │ │ │ │ needed (holds per-channel │
│ │ ┌────────────────────────────┐ │ │ │ settlement private key) │
│ │ │ Tokio runtime │ │ │ │ │
│ │ │ - P2P networking (TCP/WS) │ │ │ └─────────────────────────────┘
│ │ │ - Channel management │ │ │
│ │ │ - Gossip + routing │ │ │
│ │ │ - Payment engine │ │ │
│ │ │ - Invoice management │ │ │
│ │ │ - Signing (keys on-device) │ │ │
│ │ └────────────────────────────┘ │ │
│ │ ↕ SQLite │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ App sandbox storage │ │ │
│ │ │ (data.sqlite) │ │ │
│ │ └────────────────────────────┘ │ │
│ └──────┬─────────────────────────┘ │
│ │ FFI (JSI / C++ bridge) │
│ ┌──────▼─────────────────────────┐ │
│ │ React Native / TypeScript │ │
│ │ @shared/blockchain (WCI) │ │
│ └────────────────────────────────┘ │
└────────────────────────────────────┘
- Build: Compile
fiber-libwith--features sqlitefor Android (aarch64, armv7, x86_64) and iOS (aarch64) - FFI: Expose a C-compatible API from fiber-lib, bridge into React Native via JSI or a Turbo Native Module
- Storage: SQLite via
rusqlite(bundled), stored in the app's sandboxed document directory - WCI Integration: Wire the native fiber node as a
NervosProvidersentry withProviderType.LIGHTNING - Config:
standalone_watchtower_rpc_url→ trusted third-party watchtower;disable_built_in_watchtower: true - All signing happens locally — the wallet mnemonic derives keys on-device
| fiber-js (WASM) | fiber-lib (Native + SQLite) | |
|---|---|---|
| Runtime | Web Worker + SharedArrayBuffer | Native Tokio runtime on OS threads |
| Storage | IndexedDB (browser API overhead) | SQLite via rusqlite (direct file I/O) |
| Networking | Only WebSocket outbound (browser limits) | Full TCP/WS, could use native socket APIs |
| Performance | WASM interpreter overhead | Native code (ARM64/x86_64) |
| Concurrency | Single-threaded WASM | Multi-threaded Tokio (real async) |
| Build complexity | Simple (npm package) | Cross-compilation toolchain needed |
| OS integration | Limited (Web Worker lifecycle) | Can integrate with OS background tasks |
- SQLite backend (
fiber-storewithrusqlite+bundledfeature) —Store::open_db(path)createsdata.sqlite standalone_watchtower_rpc_url— first-class config; node forwards channel events to remote watchtower automaticallyReestablishChannelprotocol — channel state recovery after disconnection (sub-second in the happy path)- Gossip pruning — 4-week hard limit, daily cleanup
- FFI boundary layer (C API exports from fiber-lib for init, RPC, events)
- React Native native module wrapping the FFI
- Build pipeline for cross-compiling fiber-lib to Android (NDK) and iOS (Xcode)
- Lifecycle management — start/stop the Tokio runtime with app foreground/background
- No Fiber core protocol changes needed — runs the same node code
- Channel signing keys stay on device (non-custodial)
- Native performance — real threads, direct file I/O, no WASM overhead
- SQLite is battle-tested on mobile (used by Signal, WhatsApp, etc.)
- Zero operational cost beyond the watchtower service
- Decentralized — the user runs their own node
- Watchtower holds
local_settlement_key(a per-channel private key). This is a trust requirement — the watchtower can sign settlement transactions and could collude with a channel counterparty. - Cross-compilation complexity — needs Rust NDK toolchains for Android,
cargo-lipo/cargo-xcodefor iOS - FFI maintenance — the C API surface must be kept stable across fiber version updates
- Full node on mobile — P2P networking, gossip subscription, and routing graph run on device
- Not 24/7 — when the app is backgrounded or offline:
- Cannot send or receive payments
- HTLCs may expire; the watchtower must act (funds at risk if watchtower fails)
- Resource usage — TCP/WS connections, SQLite WAL growth, Tokio thread pool memory
- No push notifications — the node must be running to receive payments
| Risk | Mitigation |
|---|---|
| Watchtower misbehavior (holds settlement key) | Use a reputable third-party; settlement key is per-channel, not the wallet master key |
| HTLC expiry while offline | Watchtower must monitor and act reliably |
| App killed by OS | Channel partners may force-close; watchtower must handle |
| SQLite WAL growth | Built-in gossip pruning (4 weeks); periodic VACUUM |
| Cross-compilation breakage | Pin Rust toolchain; CI builds for all targets |
| FFI API drift across fiber versions | Versioned FFI contract; integration tests |
A server runs the Fiber node. The mobile device only holds keys and signs operations remotely.
┌──────────────────────────────────┐ ┌──────────────────────────────┐
│ Mobile Device │ │ LSP Server │
│ │ │ │
│ ┌──────────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ Light Client SDK │ │ │ │ Fiber Node (fnn) │ │
│ │ - Key derivation (mnemonic) │ │ Sign │ │ - Channel management │ │
│ │ - Remote signing callbacks │ │──────→│ │ - P2P networking │ │
│ │ - Balance & payment queries │ │ │ │ - Payment routing │ │
│ │ - No P2P, no gossip │ │ │ │ - Gossip │ │
│ └──────────────────────────────┘ │ │ │ - Watchtower │ │
│ │ ←─────│ │ - CKB RPC │ │
│ Only signs; never runs a node │ State │ └──────────────────────────┘ │
│ │ │ │
└──────────────────────────────────┘ │ ┌──────────────────────────┐ │
│ │ Remote Signer Module │ │
│ │ (calls back to client │ │
│ │ for every signature) │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
The critical missing piece is a signer delegation mechanism. Today, Fiber signs everything internally
via InMemorySigner, a concrete struct embedded directly in ChannelActorState:
// fiber-types/src/channel.rs — current state
pub struct InMemorySigner {
pub funding_key: Privkey,
pub tlc_base_key: Privkey,
pub musig2_base_nonce: Privkey,
pub commitment_seed: [u8; 32],
}No trait. No abstraction. No way to delegate signing.
Required Fiber changes:
-
ChannelSignertrait — extract a trait fromInMemorySigner:sign_commitment_tx(...)generate_musig2_nonce(...)derive_commitment_point(number)sign_revocable_output(...)
-
RemoteChannelSigner— implement the trait with a callback transport (REST, WebSocket, gRPC) -
Refactor
ChannelActorState— replacesigner: InMemorySignerwithsigner: Box<dyn ChannelSigner> -
Remote signing protocol — define wire format for sign requests/responses, timeout/reconnect logic
-
Mobile light client SDK — wraps the remote signing callback, handles key derivation from mnemonic
Required LSP infrastructure:
| Component | Status | Effort |
|---|---|---|
| Fiber node hosting (multi-tenant) | New | Medium |
| Client identity & API key management | New | Medium |
| Channel lifecycle management | New | Medium |
| Liquidity provisioning | New | High |
| Watchtower (built-in to fiber node) | Exists | Low |
| CKB RPC node | Needs operations | Medium |
| Monitoring, alerting, SLAs | New | High |
| Billing / usage metering | New | Medium |
- Best mobile UX — near-zero resource usage, instant on, no sync delay
- 24/7 availability — node runs on servers
- Push notifications possible — server can notify client of incoming payments
- True non-custodial — keys never touch the server
- No watchtower trust issue — the LSP is already trusted to operate the node
- Requires significant Fiber core changes (Signer trait + remote signing protocol)
- Requires building and operating LSP infrastructure (servers, CKB nodes, liquidity, monitoring)
- Channel commitment signing is on the critical path — latency matters; remote signing must be fast
- Musig2 nonce generation must happen client-side before each commitment round
- If the LSP goes down, all clients lose access — single point of failure for availability (not custody)
- Estimated timeline: 12–20 weeks of development
| Risk | Mitigation |
|---|---|
| Signer trait scope creep (many signing operations) | Start with sign_commitment_tx only; iterate |
| Remote signing latency | Use WebSocket persistent connection; batch where possible |
| LSP operational cost | Charge service fees per channel/payment |
| Multi-tenancy complexity | Start single-tenant; add multi-tenancy incrementally |
A server runs a full Fiber node. The mobile app connects to it via JSON-RPC (similar to CCH standalone mode).
┌──────────────────────────────────┐ ┌──────────────────────────────┐
│ Mobile Device │ │ Remote Fiber Node │
│ │ │ │
│ ┌──────────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ RPC Client (light) │ │ RPC │ │ Fiber Node (fnn) │ │
│ │ - open_channel (external │ │──────→│ │ - Full P2P stack │ │
│ │ funding: user's wallet │ │ │ │ - Channel management │ │
│ │ signs funding tx) │ │ │ │ - Payment engine │ │
│ │ - new_invoice / send_payment │ │ │ │ - Gossip + routing │ │
│ │ - list_channels / list_ │ │ │ │ - Watchtower │ │
│ │ payments │ │ │ └──────────────────────────┘ │
│ │ - No P2P, no gossip │ │ │ │
│ └──────────────────────────────┘ │ │ Holds all keys. │
│ │ │ Fully custodial. │
│ Holds CKB wallet for funding │ │ │
│ transactions only │ │ │
└──────────────────────────────────┘ └──────────────────────────────┘
This is essentially a custodial wallet over RPC with an important twist: open_channel_with_external_funding
allows the user to fund channels from their own CKB wallet without sending funds to the node. This provides
some non-custodial properties for channel funding, but channel signing keys are still on the node.
Required Fiber changes:
The CCH already has a "standalone" mode where it connects to a remote Fiber node via fiber_rpc_url
without running the Fiber P2P service locally. This pattern could be extended to general wallet operations.
// Existing pattern in fiber-bin/src/main.rs
if fiber_service.is_none() {
// CCH standalone: connect to remote fiber node via RPC
let fiber_rpc_client = RpcClient::new(config.cch.fiber_rpc_url);
}Currently this pattern is CCH-only — it doesn't extend to general channel/payment/invoice operations. To make it general-purpose:
- Extract the "standalone" pattern into a reusable RPC client mode
- Ensure all wallet-relevant RPC methods are accessible via the remote client
- Build a mobile RPC client SDK that wraps the JSON-RPC API
What exists without changes:
- The
open_channel_with_external_funding+submit_signed_funding_txflow — user signs funding tx with their own CKB wallet - All RPC methods for invoices, payments, channels, and graph queries
- Biscuit token auth for RPC
- Medium effort — most RPC infrastructure exists; mainly client SDK work
- Good mobile UX — no P2P, no gossip, no sync
- 24/7 availability — node runs on server
- Users can provide their own channel funding via external funding flow
- Leverages existing
fiber_rpc_urlpattern from CCH
- Custodial — the node holds channel signing keys. Users trust the node operator.
- No key sovereignty — if the remote node is compromised, channel funds can be stolen
- Single point of failure — node operator can censor payments
- Shared node resource contention — multiple users sharing one fiber node
- Not truly "integrating" Fiber — more like providing a hosted wallet interface
| Risk | Mitigation |
|---|---|
| Custodial trust | Use reputable operator; external funding reduces exposure |
| Censorship risk | Users can run their own node as fallback |
| Multi-user isolation | One fiber node per user (higher cost), or expect trust |
| External funding UX | Two-step flow adds complexity; needs careful mobile UX |
| Dimension | Approach 1: Mobile Full Node (Native) | Approach 2: Hosted LSP (Breez-like) | Approach 3: Hybrid RPC |
|---|---|---|---|
| Fiber changes needed | FFI layer only (no protocol changes) | Large (Signer trait, remote signing protocol) | Small (extend CCH standalone pattern) |
| Custodial risk | Non-custodial (keys on device) | Non-custodial (keys on device) | Custodial (keys on server) |
| Mobile resource usage | Medium (native perf, SQLite disk I/O) | Minimal (only signing) | Low (RPC calls only) |
| 24/7 availability | No (watchtower covers security only) | Yes | Yes |
| Offline payment risk | HTLC expiry → watchtower must act | Server handles everything | Server handles everything |
| Infrastructure cost | Watchtower service only | Full LSP operation | Fiber node(s) + CKB RPC |
| Development effort | ~6–10 weeks (FFI + build pipeline) | ~12–20 weeks + ongoing ops | ~4–8 weeks |
| Scalability (many users) | Each user runs own node (decentralized) | LSP scaling challenge (centralized) | One node per user or shared |
| Push notifications | Not possible without background service | Yes | Yes |
| Graph privacy | Full — all gossip visible locally | Opaque — user trusts LSP | Opaque — user trusts node |
| Fiber version upgrades | User-managed (app update) | Server-managed (transparent) | Server-managed (transparent) |
| Storage backend | SQLite (rusqlite, bundled) | RocksDB (server-side) | RocksDB (server-side) |
The three integration approaches above handle who runs the node. But at the protocol level, enabling offline receive in HTLC-based LN requires additional mechanisms. Three options exist, each with different trade-offs.
The server holds the user's channel signing keys. When the user is offline, the server signs
commitment_signed on their behalf.
Protocol changes needed:
ChannelSignertrait (extract fromInMemorySigner)RemoteChannelSigner/DelegatedChannelSignerimplementationChannelActorStaterefactored to holdBox<dyn ChannelSigner>
Security: The server has full signing authority for user↔LSP channels. It can both send and receive on the user's behalf. This requires trust in the LSP operator. The per-channel key model limits blast radius.
Maps to: Approach 2 (Hosted LSP). This is the closest equivalent to the Greenlight model.
Estimated effort: 6–8 weeks of Fiber core changes.
The user↔LSP channel operates normally (user signs when online). When the user is offline:
- LSP receives payment into its own balance (not the user's channel)
- LSP holds the funds until the user comes online
- User signs a new
commitment_signedto receive funds through the user↔LSP channel
Protocol changes needed: None. Pure application-layer logic.
Fiber's existing hold invoice (settle_invoice) can serve as the buffer mechanism:
LSP creates a hold invoice → receives payment → waits for user → user reveals preimage → LSP settles.
Security: Keys never leave the device. However, funds sit in the LSP's balance during the offline window. The UX shows "pending confirmation" until the user comes online.
Maps to: Approach 3 (Hybrid RPC). Can be layered on top without Fiber changes.
Estimated effort: 1–2 weeks of application code.
The user delegates a receive-only sub-key to the LSP. This key can sign commitment_signed
only for operations that add incoming HTLCs. It cannot sign outgoing HTLCs, channel closure,
or fund withdrawal.
Protocol changes needed (substantial):
- Split
InMemorySignerinto per-operation sub-signers - Define a FNP handshake extension (
receive_agentannouncement duringChannelReady) - Add verification logic: counterparty validates that incoming HTLC signatures come from the receive-only sub-key
- Adapt revocation logic for receive-only key rotation
- New mobile SDK API:
delegateReceive(agentPubkey, expiry)
Security: Cryptographically enforced — the LSP literally cannot steal funds or send payments. The receive-only key simply cannot produce valid signatures for those operations. This is the highest-security model.
Estimated effort: 20–30 weeks. Requires new FNP specification, protocol negotiation, cross-implementation compatibility testing.
| A: Shared Keys | B: LSP Buffer | C: Receive-Only Sub-Key | |
|---|---|---|---|
| Fiber changes | Signer trait (6–8 weeks) | Zero | Protocol extension (20–30 weeks) |
| True offline receive | Yes (server signs) | No (settles on reconnect) | Yes (agent signs) |
| Server can send? | Yes (full authority) | No | No (cryptographically prevented) |
| Server can steal? | Potentially (trust-based) | No (funds not in channel) | No (key limited) |
| UX on reconnect | Already settled | "Pending" → confirmed | Already settled |
| Best paired with | Approach 2 | Approach 3 | Approach 2 (replaces shared keys) |
Start with Approach 3 + Protocol B (Hybrid RPC + LSP Buffer) as the immediate path, then evolve toward Approach 2 + Protocol A (Hosted LSP + Shared Keys), with Protocol C (Receive-Only Sub-Key) as the long-term ideal.
- Deploy a managed Fiber node (one per user or shared)
- Build mobile RPC client SDK wrapping fiber JSON-RPC
- Support
open_channel_with_external_fundingfor non-custodial channel funding - Layer Protocol B on top: LSP buffers incoming payments, user settles on reconnect
- Accept custodial channel signing as a known trade-off
- Validate product-market fit for Fiber on mobile
- Zero Fiber code changes required
- Implement
ChannelSignertrait in Fiber core (Protocol A) - Build remote signing transport between mobile SDK and LSP
- Mobile device signs commitment transactions; LSP server does everything else
- Enables true offline receive — server signs when user is offline
- Transitions from Approach 3 → Approach 2 incrementally
- Extend FNP with
receive_agenthandshake and receive-only sub-key capabilities - Cryptographically constrain the LSP to receiving only
- Achieves true non-custodial offline receive without trust
- Also build the native mobile full node (Approach 1) for power users
- Three tiers: hosted buffer (Phase 1) → non-custodial hosted (Phase 2) → self-sovereign + protocol-secured (Phase 3)
- Who operates the infrastructure? (watchtower, fiber nodes, CKB RPC) — in-house or third-party?
- Target chain/asset scope? CKB only, or UDT/stablecoins from day one?
- Cross-chain with Bitcoin LN? Fiber's CCH enables BTC ↔ CKB swaps — should this be in scope?
- WCI interface changes? Current
ILightningProvideris single-token (BTC). Fiber needs multi-asset. - Regulatory considerations? Hosted/custodial models may have different compliance requirements.
- Funding liquidity? Who provides the CKB to open channels in Phase 1?