Skip to content

Tracking: Implement FFOR offline receive in ldk-node for Bitkit Android and iOS #117

Description

@coreyphillips

Goal

Implement FFOR offline Lightning receive in Synonym's rust-lightning/ldk-node stack, interoperable with the current Beignet implementation, and ship it through the generated Kotlin and Swift bindings to Bitkit Android and Bitkit iOS.

The end-to-end outcome is:

  1. Bitkit prepares an eligible channel and a bounded set of receive vouchers while online.
  2. Bitkit exposes an invoice only after the voucher commitments, activation transcript, and required recovery service acknowledgements are durable.
  3. The user closes the app or the device loses connectivity. The wallet process can be completely stopped.
  4. An ordinary compatible Lightning payer pays the invoice. The payer receives the preimage and reaches a terminal successful payment without waiting for Bitkit to reconnect.
  5. Bitkit later reconnects, verifies the payment evidence, reconciles the vouchers, and records the correct payment and spendable balance without duplication.
  6. If the settlement peer disappears or refuses to cooperate, Bitkit can recover the required preimage from an available witness or payer and enforce its signed claim on-chain before the claim deadline.

A library demo against Beignet is an intermediate milestone. This tracker is complete only after the supported production settlement peer and both Bitkit apps pass the acceptance gates below.

All implementation and verification checkboxes below are initially outstanding. Source references and existing test definitions are design inputs, not evidence that this port has already passed those tests.

1. Baseline and source of truth

Snapshot inspected on 2026-09-17:

Repository Reference Relevance
FFOR specification 80cafcd79555835e227d968b62ff3f6f74cbbc30 Draft v0.9.3, including lifecycle, Variant D, D-R witnesses, issuer, and paging errata
Beignet 9ea018b6371c8b22366a133bc504679ac04b830e Current reference implementation of Variant D, receipt witnesses, and issuer
Synonym ldk-node main 26664614696e82dc10e10f5c2042cc9ac1bb8f5e Manifest version 0.7.0-rc.66; direct Lightning dependency resolves to registry lightning 0.2.5 in this snapshot's lockfile
Bitkit Android 521332d04e082e5ae2f22d9ec35621cfcf89ca69 Uses Synonym's ldk-node-android 0.7.0-rc.66
Bitkit iOS a84f00395bd05f6918ede4f6d169a462a8e4195b Resolves Synonym's ldk-node 0.7.0-rc.66 package

Reconfirm these references before starting implementation and pin the actual interop fixtures and dependency revisions used by CI. Do not assume a developer checkout, a released package, and repository main have identical dependency patches.

Important baseline corrections:

  • Target Variant D and the D-R receipt-witness profile. The older feat/ffor branch primarily implements the A/B fast-forward and tower design.
  • Some README and spec status paragraphs still describe Variant D as unprototyped. Current Beignet source and tests implement it. Resolve normative disagreements explicitly against the pinned spec and errata; do not silently copy whichever implementation is easier.
  • Variant D uses ordinary BOLT 2 voucher HTLC commitment rounds. It does not need A/B unilateral commitment advancement, escape transactions, or commitment-number exceptions.
  • Use ffor-variant-d-vectors.md for the Variant D port. The older Appendix A vectors describe a different construction and are not a substitute.
  • The receiver role belongs in this stack. Blocktank currently uses LND, so compatible settlement support is a required external dependency, with its own linked implementation work.

Primary references:

2. Product scope and honest guarantees

First supported release

  • Variant D receiver support with fixed-amount, single-part payments over eligible ECDSA anchor channels.
  • A fixed-amount BOLT 11 invoice can be prepared and shared before Bitkit goes offline.
  • D-R witness provisioning, receipt retrieval, and recovery are part of the intended Bitkit deployment. Plain Variant D without witnesses may be used in the initial harness, with its weaker recovery assumption made explicit.
  • Cooperative reconciliation, force-close recovery, persistence, backups, Kotlin/Swift bindings, and both mobile integrations are required.
  • The feature is explicitly enabled and requires negotiated peer support. Existing online receive behavior remains supported.

Constraints that must appear in the design and product behavior

  • An epoch reserves a finite voucher book. Amounts, slot count, budget, and deadlines are fixed before the receiver goes offline. This does not provide unbounded or arbitrary-amount offline receive.
  • Cooperative credit preserves d_k in millisatoshis; on-chain outputs use the specified satoshi rounding and recovery incurs chain fees. Prefer whole-satoshi slots in the initial product and show recovery proceeds honestly.
  • Each voucher must survive dust trimming and satisfy the actual negotiated HTLC count, in-flight value, reserve, and commitment-fee constraints. 483 is a protocol upper bound, not a promised usable capacity.
  • The channel is frozen for normal updates while the epoch is active. Startup and send flows must reconcile it before treating it as an ordinary usable channel again.
  • The receiver must retrieve evidence and reconcile or enforce before T_exp with sufficient claim margin. No indefinite offline-recovery guarantee is made.
  • Watchtower-free operation requires a sufficiently long to_self_delay on the settlement peer's outputs, negotiated at channel open and accepted by that peer. Existing channels that do not meet the chosen policy cannot be silently upgraded. Either open a suitable channel or specify and validate the necessary chain-watch service.
  • Recovery needs both signed channel state and the required preimages. A seed alone is not a substitute for the epoch record or witness data.
  • Witness availability is an explicit assumption. Multiple services under one operator share an administrative failure domain.
  • A witness has a bounded store-before-propagate barrier. Its deadline path can propagate before storage succeeds and later mark a record unbarriered. Do not claim unconditional durable receipt availability from payer success.
  • Invoice/hash reuse is a documented protocol limitation. A malicious settlement peer or on-path witness that knows a preimage can settle another payment on the same hash without another receiver credit. Honest duplicate rejection and single-use invoice distribution mitigate this, but do not cryptographically close it. BOLT 12 issuance alone does not eliminate the limitation.
  • MPP/AMP, amountless receive, simple-taproot channels, A/B/C variants, automatic multi-peer budget allocation, and indefinite terminal recovery are outside the initial profile.

Reusable offers

A BOLT 12 issuer is a separately gated extension in milestone M10. It is needed if the product must serve new invoice requests while Bitkit is offline. It still draws from the precommitted fixed-amount book. Completing the initial BOLT 11 profile does not imply Lightning Address/LNURL support or unrestricted arbitrary-amount offers.

Deferred terminal recovery is tracked upstream in coreyphillips/ffor#32.

3. Threat model and architectural boundaries

Protect the user's existing balance, every enforceable paid voucher, the settlement peer's unpaid liquidity, channel signing material, and the accuracy of application payment history.

Assume:

  • A peer can send malformed or contradictory signed messages, omit acknowledgements, replay old epochs, withhold preimages, disappear, or publish a revoked commitment.
  • A witness can fail, lose records, withhold responses, equivocate, or return malformed ciphertext and paging cursors.
  • The mobile process can terminate at any instruction, including during activation, receipt persistence, reconciliation, or event handling.
  • Storage can fail or contain an older backup. An app update, rollback, or second restored device can expose incompatible or stale state.
  • Payments, close requests, disconnects, and block notifications can race. Chain reorgs and high fees can consume recovery margins.
  • Inputs and peer capabilities are untrusted until validated. UI state, a push notification, or an HTTP success response is not evidence of an enforceable Lightning credit.

Ownership

Layer Responsibilities
rust-lightning Channel-owned epoch state, voucher recognition, commitment and signer integration, freeze enforcement, reestablishment, monitor persistence and on-chain claim safety
ldk-node High-level receiver service, configuration, public operations, witness client orchestration, payment history/events, startup recovery, storage integration and bindings
Kotlin/Swift applications User intent, readiness and recovery UI, invoice sharing, lifecycle coordination and display of verified results
Blocktank settlement implementation Settlement role, admission policy, voucher setup, irreversible upstream settlement, durable close accounting and interoperability
Witness/issuer services Receipt durability and retrieval; optional single-slot invoice issuance

Do not create a second independent channel state machine in ldk-node or the apps. Pure codecs, checked amount calculations, transcript hashing, and validated protocol types can be isolated for testing. Channel transitions must remain owned by the component that signs and persists the channel.

Custom peer messages are a transport mechanism, not sufficient authority to mutate commitments. Existing signing and monitor APIs should be extended narrowly where required. Keep keys inside the existing Rust signer boundary; do not export funding, revocation, or HTLC private keys to Kotlin/Swift or a service.

4. Protocol invariants

These are required review and test assertions, not optional implementation suggestions.

Activation and voucher commitments

  • Both parties negotiate the same profile, parameters, hash/amount pairs, channel, and epoch before signing vouchers.
  • Every voucher is irrevocably committed in both commitment views before either side sends stfu for activation.
  • All required second-stage signatures verify and every voucher remains an enforceable output in both relevant views.
  • Recompute T_init, T_setup, H_book, H_commit, and H_act from local validated state. Verify the peer's node-key signatures over the exact domain-separated wire representation.
  • Preserve canonical integer encoding, internal txid byte order, one-based slot indices, and LSB-first close bitmap encoding.
  • No ordinary update is emitted between either stfu and the activation acknowledgement.
  • S persists the full ACTIVE record before sending ff_activate_ack; R persists it before reporting readiness or exposing an invoice.
  • The durable ACTIVE freeze survives disconnect and restart, independently of BOLT 2 quiescence.
  • R does not know a voucher preimage at setup. Reject prohibited bindings to already-revealed commitment secrets, including checks repeated when the voucher round reveals another secret.
  • A prepared voucher is not an incoming paid invoice and does not create a payment-received notification.

Settlement and reconciliation

  • S releases a preimage only against the matching irrevocably committed upstream HTLC and its durable slot transition.
  • Admission respects ACTIVE, deadline D, upstream CLTV safety, fixed payee amount, fees, slot availability, and required witness path policy.
  • The invoice and voucher amount are d_k. The last-hop forwarding fee is added upstream and is not subtracted from the voucher or charged twice.
  • SETTLING is durable before the fulfil leaves. Recovery identifies the specific upstream circuit, not just a hash shared by multiple attempts.
  • A slot in SETTLING counts as settled in ff_close_ack and includes its preimage. Restart must finish the owed fulfil even when admission has since closed.
  • Close and settlement races have a durable serialization point. A crash cannot move a payment to the other side of the close bitmap.
  • R verifies all ack fields and preimages. It never fails a voucher for which it holds a valid preimage, even if the peer's bitmap clears that bit.
  • R never fails a slot declared settled merely because its preimage is missing. Reject the malformed acknowledgement and preserve the claim/recovery path.
  • CLOSED means all vouchers are irrevocably resolved in both views. It does not mean a close request was sent or the local HTLC map became empty.
  • Variant D uses unchanged BOLT 2 commitment-number rules. Do not port the A/B reestablishment exceptions.

Replay and recovery

  • Identical replay is idempotent. Conflicting authenticated replay is a protocol error with retained evidence.
  • Epoch identifiers cannot be reused after refusal, abort, closure, or restart.
  • Setup abort normally unwinds any real committed voucher HTLCs. It never deletes those liabilities from memory or storage.
  • Preserve the acknowledgement-loss exception: an ACTIVATING receiver retains enough state to accept a matching retransmitted activation ack from a peer already durably ACTIVE.
  • An active epoch cannot be aborted. Normal exit requires drain or on-chain enforcement.
  • Recovery, monitor updates, event replay, and backup restore never turn stale state into permission to issue another invoice or spend a reserved claim.

5. Implementation map

Paths below identify current integration points. Proposed new names are illustrative and require API review.

Area Existing or proposed location Work
Core channel integration rust-lightning lightning/src/ln/channel.rs, channelmanager.rs Epoch state and transitions, voucher marking, freeze, timers, safe drain and reconnect
Core wire/features rust-lightning message, wire and feature definitions FFOR negotiation, lifecycle messages and channel_reestablish extension
Core enforcement rust-lightning lightning/src/chain/channelmonitor.rs, signer and on-chain transaction handling Durable preimages, both commitment views, force-close, timeout and revoked-state recovery
Node configuration src/config.rs, src/builder.rs, src/lib.rs, src/types.rs Explicit opt-in, eligible channel policy, startup wiring and capability/readiness queries
Custom messages src/message_handler.rs Compose FFOR/witness transport with existing LSPS handling; preserve LSPS behavior and feature advertisement
Receiver API proposed src/payment/offline_receive.rs or focused src/ffor/ module Prepare, expose, inspect, close, recover and enforce operations
Invoice integration src/payment/bolt11.rs; later bolt12.rs Peer-provided hashes, correct fees/routing hints, readiness and expiry enforcement
Events/history src/event.rs, src/payment/store.rs, src/data_store.rs Distinguish vouchers from payments, durable evidence, idempotent history and replayable events
Persistence src/io/, SQLite, filesystem and VSS adapters Versioned records, persistence acknowledgements, restore validation and migration
Public bindings bindings/ldk_node.udl, src/ffi/mod.rs, src/ffi/types.rs, generated bindings Consistent Rust/Kotlin/Swift types, errors and examples
Tests/CI tests/, existing LND/VSS integration harnesses and workflows Beignet interop, real LND settlement, fault injection and device acceptance

The present NodeCustomMessageHandler supports ignoring messages or handling liquidity messages. FFOR must coexist with that functionality. A single new handler that accidentally disables LSPS is unacceptable.

6. Milestone M0: freeze the supported profile and dependency plan

  • Confirm the exact rust-lightning base and whether to extend Synonym's fork or prepare upstreamable changes. Pin immutable revisions for every affected Lightning crate without accidentally changing the legacy migration-test dependency graph.
  • Confirm Blocktank's deployed LND version/fork, channel types, maximum accepted delays, HTLC limits, and operational upgrade constraints.
  • Record the initially supported profile: Variant D, anchors, fixed amounts, single-part, one active epoch per channel, BOLT 11 first, D-R recovery required for the intended app rollout.
  • Choose maximum budget, slot count, offline admission window, claim margin, witness policy, and on-chain fee reserve policy. Establish which values are protocol bounds versus deployment limits.
  • Record channel eligibility and migration behavior for existing users, including users with no channel or inadequate inbound liquidity. Complete channel provisioning before voucher setup; do not assume a new channel can be signed after the phone is offline.
  • Compare the spec, errata, and Beignet behavior and record every unresolved discrepancy before relying on it.
  • Write an architecture decision describing the state owner, durability protocol, recovery ordering, and downgrade policy.
  • Add linked child issues for the core port, node/bindings, Blocktank settlement, witnesses, Android, iOS, and verification. This tracker owns the cross-repository completion gate.

Exit gate: reviewed profile, immutable reference inputs, explicit product constraints, and named owners for the external services. Unknown deployment parameters remain visible blockers rather than guessed defaults.

7. Milestone M1: codecs, validated types, arithmetic and vectors

  • Implement only the agreed profile while rejecting unsupported variants explicitly. Feature/message identifiers in the draft are provisional, including option_ff_receive bits 560/561. Centralize them and document compatibility policy.
  • Implement ff_init, ff_accept, ff_activate, ff_activate_ack, ff_abort, ff_close, ff_close_ack, applicable error handling, and the reestablishment TLV.
  • Support the required fixed-amount and transcript TLVs. Treat witness peers and hash-chain options according to the selected profile rather than silently ignoring behavior-changing input.
  • Use bounded decoding, canonical TLVs, checked length/count arithmetic, strict field validation, unknown mandatory-TLV rejection, and total message limits compatible with BOLT 8.
  • Implement checked fee arithmetic from section 7.6. Cover underpayment, excess payee amount, allowed upstream fee overpayment, and blinded-path rounding separately.
  • Validate dust, both commitment views, funder-specific reserve obligations, maximum in-flight value, fee buffers, anchors, and slot count. Include the S-funded, zero-R-balance vector.
  • Do not copy fixture channel-delay values into production policy. The canonical vectors test transaction construction; their sample delays do not establish a safe offline window.
  • Validate deadline ordering and unknown/stale chain-tip behavior. Treat wall-clock invoice expiry as a conservative estimate; authoritative admission and claims use block heights.
  • Reproduce the Variant D transaction bytes, txids, voucher scripts, transcript hashes, and bitmap layout. Verify signatures with the target implementation; signature byte equality is required only where the fixture fixes the signing inputs and nonce behavior.
  • Reuse established secp256k1, hashing, HKDF, and AEAD implementations. Review signer domain separation, secret lifetime, randomness and nonce/key uniqueness at the new boundaries.
  • Add unit/property tests and fuzz targets for decoders, signed transcript parsing, books, amount bounds, and invalid combinations. No panic or unbounded allocation on hostile input.

Exit gate: Rust and Beignet agree on the committed state and canonical vectors before a real channel is activated.

8. Milestone M2: rust-lightning receiver lifecycle

Implement the persistent lifecycle:

NEGOTIATING -> VOUCHERS_COMMITTED -> ACTIVATING -> ACTIVE -> DRAINING -> CLOSED

NEGOTIATING / VOUCHERS_COMMITTED / ACTIVATING -> ABORTED

The ACTIVATING reconnect exception and on-chain exits must be represented explicitly. On-chain enforcement is not an invented wire state.

  • Match setup HTLCs by channel, epoch, negotiated HTLC id, hash, amount and expiry. Validate the specified voucher onion without processing it as an ordinary incoming payment.
  • Park matching vouchers without auto-claim, ordinary invoice failure, MPP grouping, or forwarding. A malformed/mismatching setup must unwind safely.
  • Integrate with the ordinary commitment/signature/revocation flow until both views contain every voucher and all required signatures are present.
  • Expose a production-safe activation/quiescence integration. Do not depend on test-only methods or treat an idle peer connection as quiescence.
  • Persist lifecycle transitions through the existing channel/monitor ordering rules. A failed or pending monitor persistence operation blocks dependent signatures, acknowledgements and readiness.
  • Enforce the active freeze across adds, fulfils/fails, fee updates, new commitment rounds, stfu, splice, and cooperative close negotiation, while permitting protocol-required replays.
  • Implement drain-only permissions after a valid close ack and prevent normal traffic from overtaking recovery after reconnect.
  • Persist replayable activation/close acknowledgements and apply section 7.5's exact retransmission rules.
  • Treat activation-hash disagreement as a recovery/enforcement condition. Do not silently reset the epoch and resume normal activity.
  • Scope timer behavior to state. The setup timeout must not abort a durably active epoch or destroy an unresolved acknowledgement-loss record.
  • Review lock ordering, callback reentrancy, Send/Sync, cancellation and drop behavior. Avoid network I/O under channel locks and new unjustified unsafe code.

Exit gate: a Beignet settlement peer and the Rust receiver complete activation, disconnect, independently restart, and converge through a correct drain over real peer transport.

9. Milestone M3: persistence, restoration and on-chain enforcement

Persist enough information to recover without the settlement peer, including validated terms/book, channel/epoch identity, transcript and activation evidence, current lifecycle, invoice exposure state, verified preimages, witness metadata and key references, close evidence, and replay/event progress. Preserve signed claim material through the existing channel monitors and signer architecture rather than duplicating a second mutable transaction database.

  • Specify how epoch records, ChannelManager, ChannelMonitor updates, and application history recover consistently when writes are separate. Do not assume cross-key or cross-store atomicity that an adapter does not provide.
  • Add versioned serialization and schema migration with forward-compatibility and downgrade rules for unresolved epochs.
  • Prove recovery with each storage backend supported for the feature, including the VSS configuration actually used by Bitkit. Explicitly reject unsupported configurations.
  • A storage failure must not report ACTIVE, expose an invoice, discard a preimage, or acknowledge a safety-critical transition that was not durable.
  • Store verified preimages in the monitor path before reporting a recoverable credit. Cover retrieval after a funding spend has already been observed.
  • Recover paid vouchers when R publishes its commitment and when S publishes its current commitment. Exercise the actual signer, fee bumping, sweep destination, and claim scheduler.
  • Let S reclaim unpaid vouchers only under the proper HTLC timeout conditions. Preserve R's unrelated balance.
  • Prove punishment of a revoked pre-epoch S commitment after the entire supported offline window, using the negotiated delay and real claim margins.
  • Cover delayed chain synchronization, fee spikes, insufficient fee reserves, chain reorgs and disconnected chain sources. Surface actionable recovery status without claiming that a broadcast is confirmed.
  • Preserve invoice exposure and epoch uniqueness across restored backups. A restore must not reissue a consumed slot or release an uncertain claim.
  • Define the supported policy for stale backups, missing witness keys, concurrent restored devices, and legacy recovery paths. Never bypass monitor consistency to make the feature appear usable.
  • Provision a sweep destination and adequate fee funding before reporting offline readiness. An anchor output alone is not a fee budget.

Exit gate: killed processes and failed writes at each critical boundary recover the same liabilities and evidence. Real regtest transactions demonstrate both receiver claim paths, peer timeout, and stale-state justice.

10. Milestone M4: ldk-node API, payment history and events

Expose a focused receiver API. The following names describe required capabilities and are not final Rust or UDL signatures:

Proposed operation Required behavior
offline_receive().capabilities(peer, channel) Eligibility, negotiated support, configured profile, limits and precise refusal reasons
prepare_epoch(channel, amounts, policy) Start/resume a correlated preparation operation; return an operation/epoch reference, not premature readiness
epoch_status(epoch) / list_epochs() Durable lifecycle, remaining unexposed slots, budget, deadline heights, recovery health and current restrictions
create_invoice(epoch, slot, description) Expose exactly the authorized fixed amount/hash once ready, with signed terms and bounded expiry
close_epoch(epoch) Request admission stop and drain; completion is observed separately and is idempotent
recover_epoch(epoch) Fetch/verify receipts, reconcile if possible, and report any remaining evidence or chain action needed
import_payment_proof(epoch, proof) Accept a payer-supplied preimage/proof through full hash, slot and epoch validation
enforce_epoch(epoch) Use the existing force-close/claim machinery with an explicit, reviewable result and cost semantics
  • Wire configuration end to end from Builder through runtime, feature negotiation and status. Disabled/unsupported modes must not advertise readiness.
  • Keep the Rust implementation authoritative. The app never implements fee arithmetic, commitment verification, transcript validation, or preimage storage policy itself.
  • Define stable errors for unsupported peer/channel, insufficient liquidity/reserves, invalid profile, busy epoch, expired admission, persistence failure, transcript conflict, unavailable witness, and recovery required.
  • Make cancellation semantics explicit. A caller timeout or dropped future does not prove that an activation/close failed or that it is safe to start a new epoch.
  • Make invoice creation retry-safe. Persist the selected slot and canonical invoice/exposure result so a crash around the response cannot issue it as a different logical invoice. Returning the same authorized invoice to the same operation is distinct from allocating the slot again.
  • Keep slot selection and exposure atomic within an epoch. Concurrent UI requests cannot select the same available slot independently.
  • Suppress ordinary auto-claim behavior for setup vouchers in src/event.rs.
  • Specify event states for readiness, verified paid voucher, reconciliation progress, deadline/recovery warning, on-chain claim progress and closure.
  • Separate reserved budget, evidenced but unresolved credit, spendable Lightning balance and on-chain pending claims. Never count all prepared vouchers as received money.
  • Correlate events with channel, epoch, slot and payment identity. Preserve receipt provenance and distinguish peer assertions from verified preimages.
  • Integrate payment history and replay with Make Lightning submission recovery durable across crashes #116's durability requirements where relevant. Event delivery may be repeated; application-visible accounting must be idempotent.
  • Do not infer payment failure from missing local history, an expired API timeout, or the absence of a witness response.
  • Keep notification/event acknowledgement retry-safe and document when event_handled() may be called.

Exit gate: public Rust API tests cover the complete receiver lifecycle without reaching into private channel state, and an interrupted caller can recover the operation's actual outcome.

11. Milestone M5: D-R witness client and service contract

Port the receiver side of section 9.6 and Appendix F. A compatible Beignet witness can be the initial reference service. A production service and its operator/failure assumptions must be identified before app rollout.

  • Generate and securely persist per-epoch fetch/encryption keys, mailbox identity, expected witness identity, manifest, H_act, retention deadline and provisioning state.
  • Provision via authenticated transport and signed manifests. Verify the witness acknowledgement and retention promise before depending on it.
  • Require all witnesses selected for readiness to acknowledge before exposing invoices. Failed provisioning has an explicit retry/removal policy, with no silent weakening of the chosen profile.
  • Configure invoice paths and negotiated witness peers so conforming payments traverse the intended witness. Verify this in the wire-level harness, not just in UI configuration.
  • Fetch with signed requests and fresh nonces. Implement v0.9.3 authenticated paging, increasing cursors, record bounds, maximum page count, and termination on malicious/non-progressing responses.
  • Verify record signature, expected witness, version/profile, mailbox, activation hash, terms hash, slot, epoch, amounts, deadlines, ciphertext hash and decrypted preimage before crediting anything.
  • Implement Appendix F.3 exactly: secp256k1 ECDH, specified hashing and HKDF, ChaCha20-Poly1305, and AAD with ciphertext_hash zeroed. Match Beignet's ECDH hashing convention to avoid a double-hash mismatch. A fixed AEAD nonce is valid only with a fresh per-record derived key; add negative and cross-language vectors.
  • Preserve unbarriered and service-health information. A valid late record can still recover a voucher; its flag must not be confused with proof that storage preceded payer success.
  • Deduplicate the same preimage from S, a payer, or multiple witnesses. Handle one witness withholding while another returns valid evidence.
  • Treat guardian receipts only as storage acknowledgements under their documented assumptions. Refuse unsupported min_receipts policies rather than claiming them satisfied.
  • Close witness/issuer service activity explicitly while preserving records for the agreed recovery/audit retention period. Do not garbage-collect unresolved claims or required evidence.
  • Protect fetch keys and decrypted preimages from logs, analytics, exception strings and ordinary UI export.

Required service-side contract, whether implemented in Beignet, LND or another reviewed service:

  • The witness records the matching preimage durably before propagating fulfilment during the normal path.
  • The barrier is bounded by wall-clock and CLTV safety, with a safety delta larger than the witness's own force-close/claim buffer.
  • Deadline fallback, late storage, retries, restart and idempotency match the spec and expose unbarriered accurately.
  • Provisioned manifests and records rehydrate after service restart while R remains offline.
  • Capacity, retention, authentication, rate limits and privacy protections apply to real network requests.

Exit gate: stop S permanently after payer success. R restarts, retrieves evidence from W alone, verifies it, and confirms the voucher claim on regtest with no assistance from S or the payer.

12. Milestone M6: Blocktank/LND settlement interoperability

This is a required production dependency. An unmodified LND settlement peer does not implement FFOR.

  • Create and link the Blocktank/LND companion issue against the actual deployed revision and assign ownership.
  • Identify the minimum engine changes versus service orchestration. Existing custom-message RPCs and HtlcInterceptor are useful, but do not alone establish channel-owned activation, durable freeze or recovery semantics.
  • Implement Variant D negotiation, S-generated preimages, voucher HTLC setup, both-view commitment verification, activation and signed close acknowledgements.
  • Integrate the durable UNUSED -> SETTLING -> SETTLED slot ledger with upstream HTLC/circuit identity and LND's persistence/contract resolution.
  • Ensure settlement occurs before the normal offline-next-hop failure path for eligible delegated payments, without attempting a fresh HTLC to the disconnected recipient.
  • Enforce the fixed amount/fee/deadline policy, honest duplicate rejection, configured budgets, eligible peers and required witness routing policy.
  • Serialize incoming settlement with close and block-height admission changes. Recover interrupted fulfils without losing upstream claims or changing the signed bitmap.
  • Protect the active channel from ordinary updates and apply correct drain/reestablishment behavior after independent LND and mobile restarts.
  • Prove current-commitment recovery, peer timeout, force-close and breach handling across the LND/LDK boundary.
  • If LND also serves as a witness, add the actual fulfil-path durability barrier. An asynchronous HTLC event subscription is not a substitute.
  • Keep administrative APIs authenticated and scoped, and expose configuration/status that proves the runtime role is enabled.
  • Define rolling upgrade, schema compatibility, draining and rollback procedures for nodes holding active epochs. Turning off new admissions must not disable recovery of existing epochs.

Exit gate: an unmodified external payer completes a single-part payment through the supported Blocktank deployment while the actual LDK receiver process is stopped. Both parties later recover correct balances, including independently injected restarts.

13. Milestone M7: Kotlin and Swift bindings

  • Add the reviewed API, records, enums, errors and events to bindings/ldk_node.udl and the required Rust FFI conversions.
  • Use explicit integer units and bounded collections. Preserve full millisatoshi and block-height values through both languages.
  • Expose statuses and evidence summaries, not private keys, mutable protocol internals or application-controlled signature operations.
  • Verify object ownership, callback threading, reentrancy, lifecycle cancellation and shutdown while an operation is in progress.
  • Add compiling Rust, Kotlin and Swift examples for prepare, ready invoice, resume/reconcile, witness recovery and force-close status.
  • Regenerate all bindings using ./bindgen.sh, run in the background as required by this repository. Do not run the individual generation scripts or hand-edit generated Kotlin/Swift code.
  • Compile the generated Android and Swift artifacts and exercise the public API through each binding.
  • Coordinate enum/API changes and persistence compatibility so an app rollback cannot misread an unresolved epoch.

Exit gate: the released artifacts expose the same validated receiver behavior on Android and iOS, including errors and replayed events.

14. Milestone M8: Bitkit Android and iOS integration

Integration starting points:

Implement and verify each requirement on both platforms:

  • Update the dependency to the tested FFOR-capable binding release.
  • Add an explicit receive flow for the supported fixed-amount profile. Retain ordinary receive when the peer/channel is ineligible, with an honest explanation of which behavior applies.
  • Start preparation while the app is online and present an invoice only after the library reports durable readiness. Do not rely on a last background callback to prepare an epoch.
  • Show the supported amount, admission expiry, remaining capacity, readiness/recovery state and any required user action using library-provided data.
  • Disable or explain unsupported amountless/MPP behavior. Do not relabel an ordinary online-only invoice as offline-capable.
  • Resume and reconcile active epochs before using the frozen channel for sends or new channel mutations. Support other independently usable channels according to library state.
  • Fetch witness records and import verified outcomes on startup, foreground, and network recovery without duplicating history or notifications.
  • Treat push notifications as hints. Payment success and balance updates depend on verified library state, and payment settlement must work without push delivery.
  • Handle process death, force-stop, device reboot, airplane mode, OS suspension, loss of background execution and a long offline interval inside the supported deadline.
  • Preserve epoch metadata and key references through the application's actual backup/restore flow. Handle stale restore and another restored device under the agreed policy.
  • Show an unresolved recovery state when evidence is unavailable. Do not turn a temporarily missing receipt into payment failure or silently release reserved value.
  • Present on-chain recovery progress and fees accurately. A pending claim is not spendable Lightning balance.
  • On feature disable or app downgrade, preserve active obligations and recovery. A remote kill switch may stop new epochs; it must not delete existing ones.

Exit gate: physical-device tests on Android and iOS demonstrate payer success with the wallet process absent, followed by correct reconciliation, history, balances and notifications. Repeat with S unavailable and witness-based recovery.

15. Milestone M9: verification and release qualification

Cross-implementation matrix

Payer Settlement peer Receiver Witness Purpose
Beignet Beignet Rust receiver harness None initially Differential protocol development
Stock LND Beignet ldk-node Beignet Ordinary BOLT 11 payer compatibility
Another supported stock payer Beignet ldk-node Beignet Independent payer behavior and single-part limits
Stock payer Modified Blocktank LND ldk-node Compatible witness Required production engine path
Stock payer Modified Blocktank LND Bitkit Android Production-equivalent witness Android product gate
Stock payer Modified Blocktank LND Bitkit iOS Production-equivalent witness iOS product gate

Use separate processes and real BOLT 8/TCP connections. In-memory loopback tests supplement this matrix but do not replace it. Stop the receiver process and isolate its network during the offline payment. Assert the payer's terminal success and preimage before restarting it.

Mandatory fault and boundary matrix

Case Required observation
Before/after each setup commitment and revocation Both sides recover signed state; partial setup unwinds without a false payment
S persisted ACTIVE, activation ack lost R retains ACTIVATING, receives matching replay, and becomes ACTIVE exactly once
R fails to persist ACTIVE No ready event or invoice exposure
Disconnect/restart in ACTIVE Freeze remains; no accidental ordinary update
Invoice exposure write/response crash Same correlated result is recoverable; slot is not independently reissued
Concurrent same-slot invoice requests One allocation/exposure outcome
S crash before/after SETTLING and fulfil No unbacked reveal; owed fulfil resumes with the same circuit identity
Payment races close/deadline Durable admission order and a consistent final bitmap
Close ack lost; one side DRAINING and other ACTIVE Correct replay precedes drain traffic
Both sides restart during drain Idempotent fulfil/fail; CLOSED only after both commitments resolve
S lies in bitmap Valid known preimages remain claimable; contradictory evidence retained
W fails before/after record persistence and propagation Barrier ordering, deadline fallback and replay behave as specified
W pages backwards, repeats, truncates or exceeds bounds Client terminates safely and retains already verified records
One witness withholds; another responds Recovery credits the voucher once
All evidence sources unavailable Explicit unresolved recovery and deadline behavior; no invented successful claim
Force-close by R and by S Correct claims from both views, with real mined sweeps
R never returns S's unpaid timeout claims obey expiry and CSV; R's unrelated balance remains protected
Revoked S state during offline window Receiver can confirm justice inside the configured margin
Reorg/fee spike/chain source failure Claims and deadlines remain safe or surface a tested actionable limitation
Disk full, write failure, stale backup, missing record/key No success-before-durability, duplicate credit, stale broadcast or silent readiness
Wrong role/key/domain, high-S, bad transcript, unknown mandatory TLV Rejection without illegal state transitions
Wrong amount/hash/id/onion, missing HTLC signature, dust/reserve boundary No activation of an invalid or unenforceable voucher book
Ordinary LSPS, online receives, sends and non-FFOR peers Existing behavior remains covered
Same invoice paid twice, concurrent reuse, ambiguous retry, MPP attempt Honest refusal plus explicit characterization of the malicious same-hash limitation

For hash reuse, preserve the distinction between a test that proves an honest peer rejects duplicates and a characterization test that demonstrates a malicious preimage holder can still take a reused-hash payment. The latter must not be presented as a fixed security property merely because the test passes.

Test sources to port or adapt

At the pinned Beignet test directory:

  • ffor-variant-d-vectors.test.ts, ffor-variant-d-setup.test.ts, ffor-variant-d-settlement.test.ts.
  • ffor-variant-d-networking.test.ts, ffor-variant-d-m8.test.ts, ffor-variant-d-quorum.test.ts.
  • ffor-adversarial-safety.test.ts, ffor-adversarial-recovery.test.ts, ffor-settle-policy.test.ts.
  • ffor-witness-manifest.test.ts, ffor-witness-store.test.ts, ffor-witness-transport.test.ts.
  • ffor-witness-barrier.test.ts, ffor-witness-crash.test.ts, ffor-witness-paging.test.ts.
  • ffor-variant-d-hash-reuse.test.ts, ffor-witness-reuse.test.ts, ffor-issuer.test.ts.
  • interop/ffor-variant-d-regtest.test.ts.
  • Also preserve public-surface coverage from tests/cli/ffor-surface.test.ts and tests/cli/daemon-options.test.ts where applicable.

Rust and integration qualification

  • Unit tests for all new business logic, including state transitions and checked arithmetic.
  • Property/model tests over valid and adversarial transition sequences, replay permutations, budgets and slot accounting.
  • Fuzz targets for wire messages, books, restoration records and witness decryption/validation boundaries.
  • Concurrency/cancellation tests for activation, close, event handling, storage callbacks and node shutdown; use targeted model checking where it meaningfully covers shared-state ordering.
  • Measured line and branch coverage for new protocol logic, targeting complete coverage of safety-critical transitions and rejection branches. Publish actual reports and explain any unreachable/uncovered cases; do not substitute estimated coverage for executed evidence.
  • Persist-to-wire fault injection at every safety boundary, with actual process kills in addition to simulated errors.
  • On-chain regtest enforcement tests that mine commitment, HTLC and final sweep/justice transactions rather than only checking signatures or mempool acceptance.
  • Required CI fails when an expected daemon, fixture, feature or infrastructure dependency is absent. Missing prerequisites cannot produce a silently green skipped gate.
  • Independent protocol and implementation review, with findings resolved or explicit release-blocking limitations recorded.

Run and attach results for the actual implementation revision, using the repository's existing infrastructure setup and required environment variables:

cargo fmt --check
cargo build --all-targets --locked
cargo test --lib
cargo test --all-targets
cargo test --features uniffi
cargo test --doc
cargo clippy --all-targets
RUSTFLAGS="--cfg lnd_test" cargo test --test integration_tests_lnd
RUSTFLAGS="--cfg vss_test" cargo test --test integration_tests_vss

Add the new named FFOR test targets, fuzz commands and platform jobs when implemented. On macOS raise the file-descriptor limit as documented before infrastructure tests. Run Miri on compatible targeted Rust tests where it can validate unsafe/ownership concerns, and record unsupported cases accurately. Review warnings rather than declaring a clean build without captured output.

Exit gate: executed cross-engine, storage, on-chain and device evidence at pinned revisions, including the negative cases. A green Beignet suite alone does not qualify this port.

16. Milestone M10: optional BOLT 12 issuer and reusable offer experience

This milestone is required if the release promises that unknown payers can request fresh invoices while Bitkit remains offline. It may ship after the fixed-invoice release, but must not be implied by it.

  • Provision the issuer only after activation and its witness provisioning; verify ownership of the offer path and the receiver's signed attestation.
  • Build offer paths that the issuer can actually validate, following section 9.7's introduction/terminal-node requirements.
  • Serve each request using one unconsumed fixed-amount slot, with durable single issuance before returning the invoice.
  • Build fresh blinded payment paths through the required witness and settlement peer, bound to the negotiated relay terms and deadlines.
  • Define exact amount matching, exhausted-book refusal, duplicate request behavior, concurrent selection and crash-after-allocation behavior.
  • Preserve privacy through fixed refusals and avoid disclosing the voucher book to probing requesters.
  • Retire offers on deadline/close while retaining the required fixed refusal and recovery records.
  • Prove an unmodified supported BOLT 12 payer can obtain and pay the invoice with R offline, including witness-only recovery afterwards.
  • Preserve and document the same-hash reuse limitation. Do not describe delegated issuance as a cryptographic fix for it.
  • Design and test any separate Lightning Address/LNURL adapter explicitly before advertising that capability.

17. Rollout, documentation and completion criteria

Rollout

  • Start with regtest, then controlled funded testing, then a limited opt-in release with explicit budgets and supported windows.
  • Deploy and verify settlement/witness capability before mobile clients expose offline-ready invoices.
  • Monitor activation failures, pending age, deadline proximity, receipt-fetch failures, unbarriered records, recoveries, reserved liquidity and force-close outcomes without logging secrets or sensitive payment payloads.
  • Document capacity planning for booked liquidity, HTLC slots, witness storage, fee reserves and mass recovery during a service outage.
  • Define upgrade and rollback handling for unresolved epochs, including the exact point after which an old binary cannot safely open the store.
  • A disable switch stops new preparation/admission where permitted while continuing evidence retention, drain, enforcement and user recovery.
  • Follow the repository's release procedure: all required version files, cumulative fork changelog, generated bindings, committed and pushed release contents before tagging, matching Swift archive checksum, and consumer-facing delta release notes.

Documentation

  • Publish the supported profile, threat model, availability assumptions, hash-reuse limitation, finite capacity, deadlines and channel eligibility requirements.
  • Document each public API's purpose, preconditions, idempotency, cancellation behavior, persistence guarantees and examples, with compiling doctests where applicable.
  • Document backup/restore requirements, witness key retention, on-chain fee funding and the user-facing recovery procedure.
  • Add an operator runbook for Blocktank and witnesses, including service outage and rolling upgrade scenarios.
  • Add reproducible local/CI setup, exact interop revisions, fault-injection instructions and a fuzzing guide.

Definition of done

  • rust-lightning implements the reviewed Variant D receiver profile with no duplicate channel authority and no unjustified unsafe code.
  • ldk-node exposes durable, retry-safe operations, verified payment history, meaningful readiness and recovery status.
  • Kotlin and Swift artifacts compile and behave consistently through the generated bindings.
  • The supported Blocktank settlement peer interoperates with the released mobile library.
  • Android and iOS both receive a payment with their wallet process stopped; the payer finishes before either app resumes.
  • Both apps later reconcile correct spendable balances, history and notifications without duplicate credit.
  • With the settlement peer unavailable, witness/payer evidence plus persisted channel state produces confirmed on-chain recovery within the supported bounds.
  • Crash, replay, storage failure, restore, deadline and reorg tests pass with captured evidence.
  • Ordinary Lightning and LSPS behavior remains covered and operational.
  • Security limitations and release policy are reviewed explicitly; no unsupported claim of indefinite recovery, arbitrary amounts or cryptographic invoice-reuse prevention is made.
  • Documentation, measured coverage, required builds/tests, independent review and release artifacts are complete.

Closing this tracker requires links to the implementation PRs, exact tested artifact versions, CI runs, interoperability results and Android/iOS acceptance evidence. Creating the APIs or demonstrating only the happy path is insufficient.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions