Arrekta payment channels (L2): specification
Status: draft v1, 27 October 2026. Phase 1 (this document and the arrekta-channels library) is
built. Phases 2–4 (daemon, multi-hop routing, wallet integration) follow; see §12. This is
off-chain protocol, not consensus: it needs no fork, and it uses only lock features consensus
already has (PROTOCOL.md §4, policy locks).
1. Why channels, and why this design
Goal: instant, near-free payments in ARRK and in issued assets between parties who do not trust each other, settling on Arrekta only when they open or close.
What the research shows:
| Design | How it works elsewhere | Usable with ML-DSA today? |
|---|---|---|
| Lightning payment channels | 2-of-2 funding, revocable commitments, HTLCs (BOLT 2/3/5) | Yes, once revocation stops depending on elliptic-curve key tweaking (§4) |
| Statechains (Spark) | Key handed over through threshold Schnorr (FROST) | No. Threshold ML-DSA is still research: the 2026 NIST threshold-call previews (TALUS, Mithril, Quorus) have one open key-recovery claim |
| Ark (virtual UTXOs) | Covenants, or many pre-signed transactions with MuSig2 | No. There is no ML-DSA signature aggregation, and pre-signing without it is impractical at about 3.3 KB per signature |
| Eltoo / LN-Symmetry | Needs SIGHASH_ANYPREVOUT | No. Arrekta signatures commit to the whole transaction |
| Sidechains / federations | Custodial multisig (Liquid: 11-of-15) | Possible, but it is not trust-minimised |
The gap this fills: Lightning's revocation derives keys by elliptic-curve point addition, which no post-quantum scheme offers. The 2026 PQLN paper adds ML-DSA and ML-KEM to Lightning's messages but keeps secp256k1 for the channel funds themselves. A post-quantum migration roadmap proposes "per-state hash commitments with post-quantum signatures" for revocation, but nobody has specified or built it. Arrekta can express it natively with SHA3 hashlocks in policy locks, which makes these the first channels that are post-quantum end to end: funds, revocation, messages and routing.
Deliberate differences from Lightning:
- Hash-based revocation (§4). Revealing the preimage of a per-state SHA3 hash revokes that state; there are no derived keys.
- No second-stage HTLC transactions (§5.4). Arrekta has only whole-transaction signatures
(no
SINGLE|ANYONECANPAY), so the delay goes directly into the HTLC output's policy. Each HTLC output can then be swept with one transaction. The cost is that HTLC expiry margins must exceed the self-delay (§8). - Zero-fee commitments with anchors (§5.3). Commitments are TRUC (v3) transactions paying no
fee. Whoever broadcasts one attaches the fee with a CPFP child spending their anchor, relayed
as a package (PROTOCOL.md §13.2). There is no fee negotiation (
update_fee) and no fee-related force-closes; this is where Lightning is heading too (BOLT #1228). - Simplified updates (§6). One side proposes at a time, and both commitments always hold the same HTLC set. This is the "simplified update" variant proposed for Lightning, chosen for provable correctness over the last few percent of throughput.
- Assets are first class (§7). A channel is denominated in one asset (ARRK or any issued asset). Arrekta's strict asset conservation means nothing can be "trimmed" into fees.
2. Notation and constants
H(x)= SHA3-256.‖= concatenation. All integers little-endian (as in PROTOCOL.md).- Policies are written in the policy-lock language:
key(K),multi(m, K…),sha3(D),after(h)(absolute height),older(n)(relative blocks),and,or. Ais the channel opener (funder) andBthe acceptor. "Local" and "remote" are relative to the owner of a commitment.
| Constant | Value | Why |
|---|---|---|
COMMITMENT_VERSION | 3 (TRUC) | Package relay with CPFP, pinning limits |
ANCHOR_AMOUNT | 10,000 base units of ARRK | A positive amount (consensus forbids zero-value spendable outputs) and cheap |
ANCHOR_SWEEP_DELAY | 16 blocks | After this, anyone may sweep an unused anchor (it keeps the UTXO set clean) |
TO_REMOTE_DELAY | 1 block | The remote output is spendable only after confirmation (pinning protection, as in anchor channels) |
MAX_HTLCS | 30 per direction | A commitment with 60 HTLCs stays under the 40 KB TRUC limit |
default to_self_delay | 720 blocks (~24 h) | The time a party has to punish a revoked state; negotiable from 144 to 4,032 |
MIN_FUNDING_DEPTH | 6 blocks, or final (a signer checkpoint), whichever comes first |
3. Keys
Each party derives a fresh set per channel from its wallet's master seed (hardened path
m/9000'/1'/channel'/k', same scheme as account keys):
| Key | Used in |
|---|---|
funding | the 2-of-2 funding output |
payment | to_remote outputs paying this party, and HTLC success/timeout branches |
delayed | this party's to_local output (after to_self_delay) |
revocation | the revocation branches on the other party's commitments |
anchor | this party's anchor |
shachain seed | 32 bytes from which every per-commitment secret is derived (§4.2) |
The keys are ML-DSA-65 by default; the policy-lock scheme mask allows ML-DSA-87.
4. Revocation without key tweaking
4.1 Rule
Every commitment n held by party P carries a revocation hash R(P,n) = H(s(P,n)), where
s(P,n) is a secret only P knows. Every output that could benefit P on that commitment has a
branch and(key(revocation of the other party), sha3(R(P,n))).
When P moves to state n+1, it sends s(P,n). From then on the other party can take every
one of P's outputs on commitment n the moment it appears on chain, because P's own branches
are delayed by to_self_delay. Publishing an old state therefore costs P everything.
4.2 Deriving and storing secrets (shachain over SHA3)
This follows BOLT 3's per-commitment secret generation with SHA3-256 in place of SHA-256:
s(n) = derive(seed, I)withI = 2^48 − 1 − n. The derivation walks bits 47 down to 0 ofI, and for each set bitbit flips bitbof the running value and hashes.- The receiver stores at most 49 secrets and can rebuild any earlier one, because a secret with more trailing zero bits derives all secrets below it. It checks every new secret against what it has stored, so a peer cannot hand it a secret that fails to derive the earlier ones.
- Before signing, each side sends the revocation hash for its next commitment, so updates never wait on a round trip for hashes.
5. Transactions
5.1 Funding
| Output | Asset | Amount | Lock |
|---|---|---|---|
| funding | the channel asset | channel capacity | multi(2, A.funding, B.funding) |
| fee reserve (asset channels only) | ARRK | 2 × ANCHOR_AMOUNT | multi(2, A.funding, B.funding) |
The opener builds, signs and broadcasts the funding transaction only after holding B's signature on A's first commitment. A ARRK channel has no reserve output: its anchors come out of the opener's balance, as in Lightning.
Dual funding. Open may ask the acceptor to contribute (requested_funding, at the opener's
fee_per_kb). The acceptor may put in up to that much: its Accept lists the contribution, its
inputs and its change.
- The opener checks the contribution's accounting:
- the channel asset it adds equals the contribution exactly;
- its ARRK pays for its own inputs and outputs at the agreed rate.
- The opener builds the funding transaction from its own coins plus the acceptor's, and sends
it unsigned in
FundingCreated, with the outputs each input spends and its signature on the acceptor's commitment 0. - The acceptor checks that the transaction carries its inputs once each, its change intact, and
the right funding output. Then it answers
FundingSignedand, only after that,FundingTxSigswith witnesses for its inputs. - The opener adds its own witnesses and broadcasts.
Ordering: commitment signatures always go before transaction signatures, as in BOLT 2 interactive funding. So neither side signs coins into a 2-of-2 it cannot get back.
The acceptor's opening balance is its contribution. A peer that withholds FundingTxSigs only
stops the opening; the opener's coins were never signed away.
5.2 Commitment (owned by P; Q is the other party)
version 3,lock_time 0.- Inputs: the funding output, plus the fee reserve for asset channels. Each input has
sequence
0xFFFFFFFF. - Outputs, sorted by
(asset, amount, lock bytes)so both sides build identical bytes:
| Output | Asset | Amount | Policy |
|---|---|---|---|
to_local (P) | channel asset | P's balance | or(and(key(Q.revocation), sha3(R(P,n))), and(key(P.delayed), older(to_self_delay_P))) |
to_remote (Q) | channel asset | Q's balance | and(key(Q.payment), older(1)) |
anchor_P, anchor_Q | ARRK | ANCHOR_AMOUNT each | or(key(X.anchor), older(16)) |
| offered HTLC (P → Q) | channel asset | HTLC amount | §5.4 |
| received HTLC (Q → P) | channel asset | HTLC amount | §5.4 |
- Empty outputs: an output with amount 0 is left out. In ARRK channels, the anchors come out of the opener's balance.
- Delay:
to_self_delay_Pis the delay Q chose for P, so that Q has time to react.
5.3 Fees: zero-fee commitments
Commitments pay no fee, so their total output equals their total input. To get one confirmed,
the broadcaster also sends a child that spends its own anchor plus wallet coins and pays the fee
for both, as a package to /v1/txs/package (CPFP; PROTOCOL.md §13.2).
Because commitments never pay fees, the parties never negotiate or update fees. A commitment is never stuck below the fee floor, and a fee spike never forces a close.
5.4 HTLC outputs, with no second stage
For an HTLC with payment hash X and absolute expiry E, on P's commitment n:
| HTLC | Policy |
|---|---|
| offered by P (P pays Q) | or(and(key(Q.revocation), sha3(R(P,n))), or(and(key(Q.payment), sha3(X)), and(key(P.payment), and(after(E), older(to_self_delay_P))))) |
| received by P (Q pays P) | or(and(key(Q.revocation), sha3(R(P,n))), or(and(key(P.payment), and(sha3(X), older(to_self_delay_P))), and(key(Q.payment), after(E)))) |
How each party gets its money:
- Q (remote): claims with the preimage at once (offered), or after
E(received). Neither needs a delay, because Q's claims on P's commitment can never be revoked. - P (local): both of P's own branches wait
to_self_delay_P, just liketo_local. That gives Q time to punish if this commitment turns out to be revoked. - Revoked commitment: Q takes every output with its revocation key and the revealed secret.
Lightning would put P's delay into a pre-signed second-stage transaction. Arrekta cannot sign part of a transaction, so the delay is in the output itself. The rule this imposes is in §8.
5.5 Cooperative close
- Shape: a zero-fee TRUC (version 3) transaction spending the funding input (and the reserve,
for asset channels). It has one output per party with a non-zero balance, at the destination
each party named in
Shutdown, sorted like a commitment. Both sides build identical bytes, so there is nothing to negotiate. - Fee: paid by CPFP. Either party attaches a child spending its own close output, as it would for a commitment anchor, and submits the pair as a package. Agreeing a fee in advance would bring back the stale-fee problem that zero-fee commitments solve (§5.3).
- Anchors and reserve: the close has no anchors. In a ARRK channel the two anchor amounts go back to the opener's balance; in an asset channel the ARRK reserve goes back to the opener.
- Preconditions: both
Shutdowns were exchanged, no HTLC is pending, and no round is in progress. After aShutdownno new HTLCs may be added; existing ones settle or fail.
5.6 Claim transactions (built by arrekta-channels, broadcast by the daemon)
| Situation | Transaction |
|---|---|
| Our commitment confirmed | to_local after to_self_delay; our offered HTLCs after E + delay; our received HTLCs with the preimage after the delay |
| Their current commitment confirmed | to_remote after 1 block; their offered HTLCs (received by us) with the preimage at once; our offered HTLCs after E |
| Their revoked commitment confirmed | One justice transaction taking every output (to_local and all HTLCs) through the revocation branches, before their delay runs out |
| Anchor | CPFP child, or a sweep after 16 blocks |
5.7 Splicing
Splicing changes a ARRK channel's capacity without closing it. A splice-in adds the initiator's coins; a splice-out pays out of its balance to any outputs.
The splice transaction:
- Inputs: the funding output (shared, 2-of-2) plus the initiator's coins.
- Outputs: the new funding output (same 2-of-2 lock, capacity ± the contribution) plus the initiator's outputs.
- Fee: paid by the initiator, at the rate it proposes.
- Result: commitment n+1 has the initiator's balance adjusted by the contribution.
| Step | Message | Effect |
|---|---|---|
| 1 | SpliceInit | contribution, inputs, outputs, fee rate; both sides build the same transaction |
| 2 | SpliceAck | the peer agrees |
| 3 | SpliceCommit (both ways) | signatures on each side's commitment n+1 on the new funding output |
| 4 | SpliceTxSigs (initiator first) | the 2-of-2 signature on the shared input, and the initiator's witnesses; then either side broadcasts |
| 5 | SpliceLocked (both ways, at min_depth) | revoke commitment n on the old funding output; the channel lives on the new one |
Safety:
- Commitment n+1 is held before anyone signs the splice. Commitment n is revoked only once the splice is buried, so whichever funding output survives, a valid commitment exists for it.
- If the splice confirms but the peer never locks, the node adopts n+1 and can close on it; the old output is spent, so n can never confirm.
- A splice nobody has signed is abandoned on reconnect (
SpliceAbort). - The channel id stays the one from opening; the public graph re-announces the channel on its new funding output.
Limits of this version:
- The channel must be quiescent (no HTLCs, no round in flight, no shutdown).
- It stays quiescent until the splice locks, so payments pause for
min_depthblocks. Lightning's design keeps both commitment sets updating in parallel meanwhile; that is the planned next step. - Asset channels do not splice yet. Their ARRK reserve output would need its own shared input.
6. Update protocol
Messages travel over the authenticated post-quantum P2P transport (PROTOCOL.md §12.1). Each one
names the channel id, which is H(funding txid ‖ vout).
| Type | Message | Fields |
|---|---|---|
| 1 | Open | temporary id, network magic, asset, capacity, to_self_delay the acceptor must use, keys, first two revocation hashes |
| 2 | Accept | temporary id, to_self_delay the opener must use, keys, first two revocation hashes |
| 3 | FundingCreated | funding outpoint, optional ARRK reserve outpoint, the opener's signatures on the acceptor's commitment 0 |
| 4 | FundingSigned | channel id, the acceptor's signatures on the opener's commitment 0 |
| 5 | Ready | sent once the funding is at MIN_FUNDING_DEPTH |
| 6 | UpdateBatch | a list of updates (AddHtlc id/amount/hash/expiry/onion, FulfillHtlc id/preimage, FailHtlc id/reason), and signatures on the receiver's next commitment |
| 7 | RevokeAndSign | the secret of the sender's current commitment, its next-but-one revocation hash, and signatures on the proposer's next commitment |
| 8 | RevokeAndAck | the proposer's secret and next-but-one revocation hash |
| 9 | Shutdown | close destination (any lock) |
| 10 | ClosingSigned | signatures on the zero-fee close (§5.5) |
| 11 | Reestablish | commitment number held, number of the peer's secrets received |
| 12 | Error | text; the channel is failed and closed with the latest commitment |
| 13 | FundingTxSigs | dual funding: the acceptor's witnesses for its funding inputs (§5.1) |
| 64–69 | SpliceInit, SpliceAck, SpliceCommit, SpliceTxSigs, SpliceLocked, SpliceAbort | splicing (§5.7) |
Every message is u8 type ‖ body with little-endian integers and length-capped vectors;
decoding rejects unknown types, truncation and trailing bytes. Signatures are one per funding
input (two in an asset channel).
One round (simplified updates):
- The proposer sends
UpdateBatch: its updates, and its signatures on the other party's next commitment. - The other side applies every update to the current state, checks the signatures against the
commitment it would own, and replies
RevokeAndSign: the secret for its old commitment and its signatures on the proposer's next commitment. - The proposer checks the signatures and the secret, commits, and replies
RevokeAndAckwith its own secret. The other side checks it and commits.
After each round both commitments hold the same balances and HTLC set. When both sides send
UpdateBatch at once, the side with the lexically lower funding key goes first: the other
abandons its proposal, answers, and re-proposes its updates afterwards. It also remembers the
abandoned state, because the peer holds its signature on a commitment for it; that commitment
carries the same revocation hash as the round that did happen, so it is revoked with it and
justice applies to it too. Nobody revokes a state before holding a valid signature on the next
one, so at every point each party can close unilaterally with its latest commitment.
Reconnecting: after a disconnect both sides send Reestablish:
- their commitment number;
- how many of the peer's secrets they hold, proved by including the last one;
- whether a round is in flight.
Each side then retransmits exactly the message the other is missing (UpdateBatch,
RevokeAndSign or RevokeAndAck).
Data-loss protection: a peer that proves it holds more of our secrets than we revealed means we lost state. Then we never broadcast our commitment, which is revoked; we wait for the peer to close. A peer that is behind is failed with our latest commitment. A claim without the right last secret is refused. Details: CHANNELD.md §4.
7. Assets and issuer controls
- One asset per channel, set at open. HTLC amounts are in that asset; fees are always ARRK.
- Conservation: every unit of the asset sits in an output of every commitment and close. There is no dust trimming. The minimum HTLC is 1 base unit, and the HTLC count limit keeps commitments small.
- Issuer controls apply. If the issuer freezes a channel's funding lock or a party's address, or claws back, consensus enforces it on channel transactions as on any other. The opener's wallet warns when the asset is freezable or clawback-enabled. That is correct behaviour for regulated assets, not a bug.
8. Timelocks and safety rules
| Rule | Why |
|---|---|
to_self_delay is from 144 to 4,032 blocks; default 720 | Enough time to see and punish a revoked state, including through a watchtower |
Close on-chain when E − tip ≤ to_self_delay + 144 for a received HTLC whose preimage we know | Our success branch waits to_self_delay after confirmation, and must land before the remote timeout at E |
Forwarding: incoming expiry ≥ outgoing expiry + cltv_delta, with cltv_delta ≥ to_self_delay + 144 | No second-stage transactions, so a hop needs the delay plus a grace window to settle upstream (phase 3) |
Final hop: E ≥ tip + min_final_expiry (default 288) | |
| Reserve: each side keeps ≥ 1% of capacity after the first payment | Revocation must cost the cheater something |
Funding usable at MIN_FUNDING_DEPTH | Reorg safety. A final (checkpointed) funding transaction is safe at once |
9. Invoices
-
Contents:
- network, payment hash and a payment secret;
- asset and amount (the amount is optional for a "pay what you want" invoice);
- description (≤ 256 bytes), creation time and expiry, minimum final expiry;
- up to 4 route hints (private last hops, each with its forwarding policy);
- the payee's identity key and onion key (ML-KEM-768).
It is signed with ML-DSA by the payee's node identity.
-
Payment secret: it goes into the final onion payload. Only someone who saw the invoice can complete the payment, so intermediate hops cannot probe the payee (as in BOLT 11).
-
Encoding: binary with a version byte, carried as
arrektapay:+ base64url. -
Size: a signed invoice is about 6.5 KB, too big for one QR code. Wallets show it as an animated UR (
ur:arrekta-invoice, see docs/AIRGAP_SIGNING.md) or fetch it over HTTPS.
10. Security analysis (summary)
- Revoked-state theft: every output that benefits the cheater waits
to_self_delay, and the victim's justice transaction is valid at once. The secret check (§4.2) stops a peer from handing over a bad secret. - HTLC races: the rules in §8 make sure each party's claims confirm before the other's conflicting branch becomes valid.
- Fees: zero-fee commitments plus anchors mean nobody depends on a fee agreed in the past; TRUC limits stop pinning by giant children.
- Quantum: every signature (funding, commitments, claims) is ML-DSA, revocation is SHA3, the transport is X25519 + ML-KEM-768, and onions use ML-KEM (phase 3). No classical public-key primitive protects funds.
- Not covered: a party that stays offline longer than
to_self_delaywhile its peer publishes an old state. That is why watchtowers exist (phase 3).
11. Verification
node-rs/crates/arrekta-channels/tests/chain.rs builds every transaction in §5 and submits it to
the real consensus code (an in-process regtest chainstate and mempool):
- funding; payments in both directions; a cooperative close confirmed by CPFP;
- a unilateral close with anchor CPFP (and that the zero-fee commitment alone is refused);
to_remoteafter one block;to_localand the HTLC success and timeout branches refused before their delay or expiry and accepted after it; - a revoked commitment answered with a justice sweep, after which the cheater's delayed claim is invalid;
- the same flows for an issued-asset channel with its ARRK reserve.
tests/protocol.rs runs randomised two-party sequences (property tests, including simultaneous
proposals), with every message passed through the wire encoding. After every round it checks
that both sides agree on the state, the channel total is conserved, each side holds the peer's
secret for every superseded commitment, and each side recognises every revoked commitment the
peer could broadcast. It also checks that forged signatures, bad secrets, wrong preimages,
replays and malformed messages are rejected, and covers invoices.
12. Phases
| Phase | Contents | Status |
|---|---|---|
| 1 | This specification; arrekta-channels library (keys, shachain, policies, commitments, update state machine, closes, claims, justice, invoices) with consensus-validated tests | built |
| 2 | arrekta-channeld daemon: P2P sessions with identity, persistence, reconnection with data-loss protection, chain watcher with automatic justice and sweeps, REST API, payments, regtest tests | built (CHANNELD.md) |
| 3 | Multi-hop: ML-KEM onion routing (Outfox-style, fixed length), signed gossip checked against the chain, probabilistic pathfinding, forwarding with fees, watchtower server and client | built |
| 4 | SDKs (TypeScript, Python: invoice decoding, channel-node client), wallet-core (invoice preview, ur:arrekta-invoice, ChannelsWallet), desktop and mobile Instant payments screens, docs site (guide, API, design), Docker/Compose/release images, alerts | built |
| 5 | Multi-path payments, dual funding, splicing (quiescent), browser extension, explorer channel view | built |
| next | Splicing with HTLCs in flight (parallel commitments), splicing asset channels, liquidity ads, asset swaps across channels |
Sources
- Lightning BOLT 3 and 5 (transactions, on-chain handling): BOLT 5 · model checking Lightning (arXiv 2505.15568) · revocation keys · anchor outputs
- Zero-fee / TRUC commitments: Lightning with v3 and ephemeral anchors (Delving Bitcoin) · which ephemeral anchor script (Delving Bitcoin) · ephemeral anchors
- Post-quantum Lightning: PQLN (arXiv 2609.13781) · Fixing the Lightning Network (PostQuantum) · Lightning's quantum problem · post-quantum adaptor signatures (ePrint 2020/845)
- Threshold ML-DSA status: NIST threshold cryptography · TCPT2 preview talks · Efficient Threshold ML-DSA (ePrint 2026/013) · Quorus
- Post-quantum onion routing (phase 3): Outfox (arXiv 2412.19937) · KEM Sphinx (ePrint 2023/1960) · Katzenpost KEM Sphinx spec
- Assets over channels: Taproot Assets channels (Lightning Labs) · edge nodes
- Channel operations: dual funding (Bitcoin Optech) · splicing (OpenSats) · Lightning in 2026