ð
Original date posted:2023-01-03
ð Original message:
Subject: Swap-in-Potentiam: Moving Onchain Funds "Instantly" To Lightning
by Jesse Posner, ZmnSCPxj
Introduction
============
Moving funds from an onchain-only address to Lightning Network is slow,
especially if you desire trust-minimization (which removes solutions
relying on 0-conf).
Basically, to reduce trust requirements in all onchain transactions,
onchain receivers *MUST* ensure confirmation of the onchain transaction
that creates their UTXO.
In practice, the minimum should be at least 3 blocks, since reorgs of
up to 2 blocks are common occurences, but even 1 confirmation can
take an inordinately long time in the real world due to the random
nature of mining.
This is particularly acute for mobile phone use-cases.
As mobile phones run on a battery, mobile phone OSs often greatly
restrict CPU and other resource consumption when an app is not
currently open by the user.
Now consider this user story, for a wallet app that supports both
Bitcoin blockchain and Lightning Network operations:
* The user wants to be paid over the Bitcoin blockchain.
* The user gets an address from their wallet and provides it to
some other party to receive their payment.
* The user closes their wallet app, which causes the mobile phone
OS to kill all its threads.
* The other party sends their payment over blockchain while the
user is asleep.
* The blockchain transaction confirms and time passes.
* The user wakes up and checks their favorite blockchain explorer,
and sees they received funds on their wallet address.
* The user opens their wallet app and decides they need a coffee,
so they buy coffee over Lightning.
For current solutions to move funds from the blockchain layer to
Lightning, however, the above user story would need to complete
over a long time, possibly measurable in dozens of minutes or
even hours in the worst case, due to the need for confirmation:
* Channel open: Requires confirmation, many nodes require 6 or
more confirmations.
* Submarine swap/peerswap: Requires confirmation before the swap
service will send out the HTLC on Lightning.
* Splice-in: Channel remains operational, but until the splice
transaction confirms, the channel operates in "dual mode"
where both pre-splice and post-splice state is valid, and
that means only the lower amount of the pre-splice and
post-splice can be used in the mean time.
For splice-in, the pre-splice amount will be lower, thus
the amount being spliced in will not be credited until
the splice transaction is confirmed.
In this writeup, we present a novel protocol, swap-in-potentiam,
that can be used for immediate transfer from the blockchain
layer to the Lightning layer, in the above user story.
Advantages And Limitations
--------------------------
To whet your appetite, here are the advantages:
* Immediate transfer of already-confirmed-received onchain
funds to Lightning.
* Onchain funds can also be transferred to another onchain
address (subject to normal onchain confirmation rules).
* This can be "immediate" if sending to a receiver that
accepts the risk of 0-conf onchain transactions.
* Minimized trust requirement.
The disadvantages, to help convince you that yes, this is
technology and not magic beans (and to not oversell this
tech, Bitcoin media reporting often tend to oversell
new technologies because the disadvantages are often
hidden away behind technical minutae):
* Requires a cooperating LSP.
If LSP is down or refuses to cooperate, onchain funds
are locked for some time.
This has a timeout (so if the LSP never comes online
again, you just wait out the timeout) and the timeout
starts from when the receiving UTXO is confirmed in a
blocks, so it will not cause loss of funds, only loss
of opportunity (i.e. "involuntary HODLing").
* If you have multiple LSPs, when you generate an address
you *have to* select one of them at that point, you
*cannot* commit to multiple LSPs and select one of them
later when your phone wakes up again.
This exacerbates the above disadvantage, since you have
to select one of your LSPs and hope that when your
phone wakes up the LSP you selected is also up and
cooperative.
* The onchain-received funds have to be confirmed first,
otherwise we still need to wait for confirmation of the
onchain-received funds.
This is generally true for many blockchain-only wallets
anyway and is thus not a worsening, but is also not an
improvement.
* If the timeout is too near, actions must be performed
onchain that require confirmation.
Swap-in-Potentiam
=================
All onchain fund movements, as noted, require confirmation.
These include onchain fund movements to the Lightning network.
If the onchain address that the wallet provides was controlled
solely by that wallet, then any action that requires cooperation
with a Lightning Network participant --- channel open, swap, or
splice --- would require an onchain transaction that commits to
that specific Lightning Network participant.
Only when the new onchain transaction is confirmed, can that
Lightning participant rely on the transaction output without
having to trust the initiator.
>From there, we can consider: what if the wallet provides an
address that *already* commits to that specific Lightning Network
participant?
If so, then the "timer for confirmation" starts as soon as
the wallet receives on the blockchain, not as soon as the wallet
decides to move funds from blockchain to Lightning.
This is a significant difference for a mobile wallet: the mobile
environment does not support the mobile wallet being online for
long.
Thus, the mobile wallet may not have any CPU to make the decision
to move funds from blockchain to Lightning, until the actual user
explicitly opens the mobile wallet app.
It has already been generally accepted that due to the limitations
of the mobile phone environment, a mobile phone wallet with
Lightning support would need some LSP anyway.
Thus, a mobile wallet that can receive on the blockchain layer
and then send on the Lightning layer can commit to a specific,
different, Lightning participant: the LSP it has channels with.
Thus, the mobile wallet can provide an address that commits to
one particular Lightning Network participant: its LSP.
The mobile wallet can then initiate a single-hop swap with the
LSP when the mobile wallet app is in the foreground and has CPU
to think with.
If it received funds into the address that have already been
confirmed, then it can do this single-hop swap immediately with
its LSP.
The LSP can immediately resolve this swap, crediting funds to
channel, while atomically ensuring it has sole claim to the
onchain UTXO.
The Contract
------------
The contract has two participants: Alice the funds owner, and
Bob its potential swap partner.
Once *any* funds have been confirmed received into an address
committing to this contract, Alice owns the funds and can
dispose of them as it likes (with cooperation from Bob).
The source of the funds need not be Alice, it could be a third
party that has an obligation to pay Alice onchain.
The contract has only 2 branches:
* Onchain/channel branch: Alice and Bob.
* Timelock branch: Alice plus a relative timelock (`OP_CSV`)
measurable in weeks.
Astute readers will realize that the above is really a variant
of [CLTV-style unidirectional time-limited channels][1],
themselves a variant of Spilman-style channels:
* Uses an explicit opcode to simplify channel setup (no need
to pre-sign a timeout transaction between Alice and Bob,
can just send funds directly to the address).
* Uses a relative locktime instead of an absolute one to
allow funding of the channel address (= receive onchain
funds ready to spend over Lightning) at any time.
The use-cases this enables are:
* If Alice wants to pay to another onchain address, and Bob
is also online and cooperative, Alice can ask Bob to help
sign the Onchain/channel branch to move the funds in any
arbitrary onchain manner.
* If Alice wants to pay to a Lightning invoice / keysend, and
has insufficient Lightning outgoing capacity (but has
sufficient *total* capacity), it can swap with Bob, by
offerring a transaction that spends via the Onchain/channel
branch and instantiates a fresh onchain HTLC that Bob can
then forward over Lightning.
As soon as Alice offers its signature of that transaction,
Bob can immediately offer an in-Lightning HTLC to Alice on
their channel, and then Alice can immediately resolve it
(thus immediately getting its funds into Lightning).
* If Bob is offline or uncooperative, Alice can unilterally
recover its funds after the timeout in the Timelock
branch.
Trust is required only to the extent that Alice trusts Bob to
be cooperative so that Alice can dispose of its funds immediately.
In case Bob turns out to be non-trustworthy, Alice can recover
its funds via the timelock branch after the timeout period.
There is no scope for Bob to steal funds (indeed, it is easier
for Bob to steal Lightning funds than to steal swap-in-potentiam
funds).
The intent here is that the mobile wallet is Alice, while the
LSP is Bob.
### Bob Security
Bob *MUST* ensure that, for each UTXO, it is either asked to
sign an arbitrary onchain transaction (i.e the first use-case
above) *OR* it gets offered an onchain HTLC from that UTXO.
Once Alice has asked Bob to cooperate in either case for a
particular UTXO, Bob *MUST* ensure that it does not sign the other
case (and Bob *MUST* refuse to cooperate in the other case once
one case has been requested).
In addition, Bob *MUST* ensure that, if it is used in the
"channel" case (i.e. the second use-case above), the timeout of
the Timelock branch is far enough in the future that it is likely
that spends using the Onchain/channel branch have confirmed by
then.
With both invariants enforced by Bob, Bob can ensure that, if
Alice requests a swap using the Onchain/channel branch, only Bob
can spend the UTXO (at least before the timeout), and thus can
safely offer a Lightning HTLC to Alice immediately without any
additional waiting for onchain confirmations.
As Bob needs to know the UTXO in the first use-case above, this
requirement prevents the use of blind signing techniques when
implementing the first use-case.
Basically, when being asked to sign, Bob must generate the entire
`SIGHASH` from data that Alice provides, so that Bob is able to
keep track of UTXOs it is signing for.
### Remote Swap
While Bob is generally considered "the" LSP of the mobile wallet
Alice, nothing in the Lightning protocol actually requires that
Bob be a direct peer of Alice.
The only real requirement is that Bob is able to send funds to
Alice over Lightning in exchange for possession of the equivalent
onchain funds.
Against this, we should note that the mobile wallet is already
dependent on one or more LSPs anyway, so it may as well just use
its direct peer LSPs instead of a remote node.
### Address Derivation
Swap-in-potentiam addresses can be derived from a root public or
private key.
We only need one keypair each from Alice and Bob.
Alice can use standard derivation paths for its keypair.
As Bob is intended to be an LSP, we can just use its Lightning
node ID as the public key.
Bob needs to be in possession of the corresponding private key
anyway in order to set up BOLT 8 encrypted transports.
As LSPs are part of the public network, Alice can simply try to
scan for all published nodes that advertise support for
swap-in-potentiam.
Alternately if the wallet has a short list of fixed LSPs it
will use, it can simply refer to that list.
Thus:
* Alice uses a derived keypair.
* Bob uses a fixed keypair (its Lightning node ID).
The above is sufficient to derive swap-in-potentiam addresses
from an `xprv` or `xpub` root key.
Swap-in-potentiam For LSPs
==========================
While the original design of swap-in-potentiam has the mobile
wallet in the "Alice" role and its LSP in the "Bob" role, it
turns out that LSPs can provide special service to improve
receiving mobile wallets.
Suppose that the LSP keeps track of statistics, and thus has
an idea of which of its mobile wallet clients are likely to
be net receivers.
Net receivers will often have low inbound capacity (since the
inbound capacity has been used up during previous LN receives).
During times of low onchain fees, an LSP can check which of its
offline mobile wallet clients have low inbound capacity, and are
likely to come online in the future to receive.
In those cases, the LSP can commit funds to a swap-in-potentiam
with the mobile client, with the LSP as "Alice" and the mobile
client as "Bob".
This at least lets the LSP set up half of a swap during a time of
low fees.
If the transfer to swap-in-potentiam addresses is confirmed by
the time the mobile wallet client comes online, the LSP can
immediately initiate a swap, giving inbound capacity towards the
mobile client.
This swap can be immediately resolved, and allows the mobile
wallet client to immediately receive funds over Lightning.
In particular, if "offline receive" as designed by TheBlueMatt
is implemented, then the LSP already has indication of a pending
payment towards an offline mobile wallet client.
The LSP can check if the offline mobile wallet client has
insufficient incoming capacity to receive the funds, and if so,
arrange to fund a swap-in-potentiam with that client.
Then, when the mobile wallet client comes online, the LSP can
initiate the swap with them, and once the swap completes (and
thus the mobile wallet client has sufficient incoming capacity)
the LSP can contact the sender LSP to complete the payment.
In particular, this use-case allows for *immediate* receives as
soon as the mobile wallet client gets foregrounded and has CPU
time, **without** requiring 0-conf trusted transactions and
thus without requiring any kind of semi-custodial trust, even
if the mobile wallet client had insufficient incoming capacity.
A channel still has to be set up beforehand (without 0-conf, if
trusting funds to the LSP is undesirable).
Implementation Sketch
=====================
The intent is to use Taproot with Schnorr signatures, but
**without** using the keyspend path (at least initially).
The plan currently is to use a `MuSig(A, B)` as the internal
pubkey, but with the branches still explicitly laid out as
tapleaves.
That is, there are two tapleaf SCRIPTs corresponding to the
two branches described above:
* `<A> OP_CHECKSIGVERIFY <B> OP_CHECKSIG`
* `<timelock> OP_CHECKSEQUENCEVERIFY OP_DROP <A> OP_CHECKSIG`
Using an explicit 2-of-2 branch rather than a MuSig allows
for a simple protocol at least for initial deployment:
we can have Alice send the signature using `A` in a single
half-round without having to engage in a 2-round MuSig2 signing
ritual.
We intend to use Taproot since the mobile wallet client
may need to use a 2-of-3 or 2-of-2 signing scheme, similar
to Blockstream Green.
This allows either Alice or Bob in the contract to secretly
be a FROST 2-of-3 or MuSig 2-of-2 (or any FROST k-of-n or
MuSig n-of-n).
This is also another reason for avoiding a 2-of-2 MuSig
keyspend path between Alice and Bob, as there is (to our
knowledge) no publicly-reviewed security proof that
FROST-in-MuSig and MuSig-in-MuSig are safe (or the
corresponding variants using MuSig2 for the signing
ritual).
Later, when we are more confident of the use of MuSig2 and
FROST inside a MuSig2, and with using MuSig2 with possibly
untrusted outsiders (who might exploit any mis-implementation
of the MuSig2 signing protocol if we are not careful with
designing it), we can seamlessly upgrade the protocol
to use the keyspend path later, to save witness bytes.
For the Onchain use-case (i.e. Alice wants to spend the UTXO
to an onchain address), the protocol betweeen Alice and Bob
would be:
* `request_arbitrary_signature` Alice->Bob: Requests Bob
to sign a PSBT spending a swap-in-potentiam address
using the Onchain branch.
* `response_arbitrary_signature` Bob->Alice: Response to
the above, returning the requested signature.
* `reject_arbitrary_signature` Bob->Alice: Sent in
response to `request_arbitary_signature` if Bob refuses
to cooperate (e.g. the UTXO being spent has already
been accepted by Bob in a Channel use-case below).
For the Channel use-case (i.e. Alice wants to spend the UTXO
to a Lightning receiver), we operate the swap-in-potentiam
UTXO(s) as a Spilman-like channel over two states:
* HTLC-offering: Offering an amount `N` HTLC from Alice to
Bob, with any remaining amount to a change address to
Alice.
* Resolved: Giving the amount `N` outright to Bob, with any
remaining amount to a change address to Alice.
The intention is that the channel is initially put into
the HTLC-offerring state.
Then Bob offers a corresponding in-Lightning HTLC to Alice
over their channel.
When Alice resolves the in-Lightning HTLC, it can then
send a new signature for the Resolved state.
Once the channel is in a Resolved state, Bob *SHOULD* sign
the last state and broadcast it on the blockchain, thereby
closing the Spilman-like channel.
The protocol messages for the Channel use-case are:
* `request_swap_in` Alice->Bob: Tell Bob the UTXOs with
the same swap-in-potentiam address to spend, how
much to put into the Alice->Bob channel direction,
what channel to move into, and (optionally) a change
address for Alice.
* `reject_swap_in` Bob->Alice: Sent in response to
`request_swap_in` if Bob refuses to cooperate (e.g.
one of the UTXOs on offer was already signed with
`response_arbitrary_signature`, or Bob cannot legally
accept control of funds from one or more of the UTXOs
offerred).
* `accept_swap_in` Bob->Alice: Sent in response to
`request_swap_in`, containing the Bob-side address to
send funds to later once the state is Resolved.
* `swap_in_signed` Alice->Bob: Response to
`accept_swap_in`, containing the Alice-side signature
for the HTLC-offering state transaction.
Once Bob receives this, Bob can safely construct a
new on-Lightning HTLC using BOLT1
`update_offer_htlc`.
* `swap_in_resolved` Alice->Bob: Sent after Alice has
acquired the funds via `update_fulfill_htlc` of the
corresponding on-Lightning HTLC, containing the
Alice-side signature for the Resolved state
transaction.
The Resolved state transaction spends to the Bob-side
address given in `accept_swap_in`, and any change to
the Alice-side change address in `request_swap_in`.
The plan is to reserve only one odd BOLT1 message ID,
and to embed the actual swap-in-potentiam message ID
as the first 2 bytes of the BOLT1 message, to reduce
pollution of the limited BOLT1 message ID space and to
allow more flexibility for swap-in-potentiam to expand
to new messages inside its own message ID space.
For a richer future protocol, we will want to consider
how a swap-in can be combined with a splice-in.
This is useful if the current total capacity of the
channel is lower than the available onchain funds.
The swap-in can be credited immediately (and is limited
to the current total capacity) while additional funds
can be added to the channel via splice-in (which is
credited only once the splice-in is confirmed).
As-is, a similar result can be obtained using openv1,
wherein a swap-in is combined with a channel open,
with the swap-in immediately credited while the channel
open is awaiting confirmation:
* Alice and Bob currently have one or more existing
channels, and Alice has a UTXO in a swap-in-potentiam
address whose value exceeds the incoming capacity of
the existing channel(s).
* Alice->Bob `open_channel`.
* Bob->Alice `accept_channel`.
* Alice->Bob `request_swap_in` with the "change address"
being the funding address of the channel.
* Bob->Alice `accept_swap_in` provides the TXID of the
funding transaction (Alice now knows the "change
address" and the Bob final Resolved address, letting it
know the final Resolved state transaction TXID).
* Alice->Bob `funding_created` with the TXID.
* Bob->Alice `funding_signed`.
* Alice->Bob `swap_in_signed` to provide the signature
spending the swap-in-potentiam address to Bob.
* Bob then constructs an HTLC over the existing channel,
which Alice claims, revealing the preimage.
* Alice->Bob `swap_in_resolved`.
* Bob then broadcasts the Resolved state transaction,
which is also the funding transaction of the new channel.
* Both Alice and Bob await confirmation of the transaction
in order to use the new channel.
Alice can still use the existing channel, which has been
topped-up with fresh outgoing capacity by the swap-in.
(The above is not safe, as Bob can complete the protocol
by using the HTLC-offerring state transaction; this can
be fixed by having Alice open *2* channels with the same
amount, one with the HTLC-offering state transaction as
the funding tx, the other with the Resolved state
transaction as the funding tx, and later `error`ing the
channel corresponding to the transaction that is not
confirmed; this is left as an exercise to the reader,
though note that it requires two different change addresses
for Alice for both HTLC-offerring and Resolved states,
which can be arranged for the protocol)
--
[1]: https://en.bitcoin.it/wiki/Payment_channels#CLTV-style_payment_channels