← index

Forwardable Peerswap Incentives, And Closing Surreptitious Communications Tunnels

An archive of delvingbitcoin.org · view original topic →

· ZmnSCPxj jxPCSnmZ · #1 ·

Subject: Forwardable Peerswap Incentives, And Closing Surreptitious Communications Tunnels

Introduction

Forwardable Peerswaps is a proposed protocol for managing Lightning Network liquidity.

It has the following advantages over splicing:

For these reasons, we should really devote much more developer time on forwardable peerswaps than on splicing.

Participant Incentives In Forwardable Peerswaps

We can broadly divide the participants in a forwardable peerswaps into three main classes:

The Initiator offers “I want to give you onchain funds for more outbound Lightning liquidity”.

The Acceptor accepts the above offer; because of the symmetric nature of liquidity, it now has responsibility for a new onchain fund, but also gets inbound Lightning liquidity.

The Forwarders then effectively:

Incentive To “Skip The Forwarders”

As noted, the trade deal offered by the Forwarders to the Initiator is:

Ideally, we want this trade deal to be atomic.

The problem is that when forwarding, it is possible to use cryptographic data — the hashes and public keys — for the Initiator to provide the Acceptor with enough information that they can make a direct Lightning Network channel to each other.

Suppose that we designed the protocol naively, and that the Forwarders simply directly transfer the public key of the Initiator for the timelock branch of the onchain HTLC, as well as the hash, verbatim.

In that case, the Initiator might use its own node ID as the public key to be used for the timelock branch. Then the Acceptor can look up the node ID in its gossip map, see the Initiator node ID, and connect to the Initiator, and they can directly create a Lightning Network channel, cancelling the protocol with the Forwarders.

The Forwarders, who provided the service of “looking for someone who needs inbound liquidity”, are then left without what they expected to get out of the trade deal, i.e. two channels rebalanced in their favor.

Thus, we should always remember that public keys and hashes can always be used as surreptitious communications tunnels.

We should remember that one reason for wanting data on network conditions (i.e. channel data) is to discover opportunities to undercut competitors. Even though the Forwarders have not handed over their channel balance data directly, the fact that the Initiator and Acceptor can make a direct connection means that the Initiator has still taken advantage of that data (as a form of summary). The important part is to protect against this abuse, not just protect actual bits of the data from being leaked.

Closing The Surreptitious Communications Tunnel

In order to close this surreptitious communications tunnel, the forwarders need to add their own randomizing data.

For public and private keys, this is trivial — we just use actual addition modulo p (or is it n? whatevs) on 256-bit numbers for the private keys, and the homomorphic equivalent for public keys.

SHA256, however, is not linear, and there is no similar way to add randomizing data.

Fortunately, we do not need to actually provide the hash, as we shall see. The Forwarders can force the Initiator and Acceptor to commit to continuing the Forwardable Peerswap protocol by the time the hash is exchanged, so even if the hash is actually used to encode the node ID of the Initiator, it would be pointless; the Initiator is already committed to completing the protocol.

Forcing Commitment To The Forwardable Peerswaps Protocol

Now, typically when we think about the onchain hop of an onchain-offchain swap, we think of it in terms of creating an onchain HTLC directly.

However, another way to view it is that the onchain hop is actually a temporary, time-bound, unidirectional channel (the timelock of the HTLC being the time bound), which then hosts an HTLC. Then the onchain-offchain swap can be seen as a simple circular rebalance, i.e. a self-payment.

Have we seen time-bound unidirectional channels before?

Why yes we have, it is called the Spilman channel.

A Spilman channel uses a SCRIPT that encodes:

Then, the Initiator can provide a signature to a transaction that spends the above SCRIPT using the first branch (Initiator AND Acceptor) to the Acceptor. This transaction now contains any payments or HTLCs the Initiator wants to offer to the Acceptor.

The drawback is that it is unidirectional, and the second branch timelock creates a time bound; the channel must be closed before this time.

This is ideal for the protection of the interests of the Forwarders.

Thus, the Forwarders can use this mechanism to force the Initiator to commit to completing the Forwardable Peerswap protocol.

The only requirements are:

Transferring The Initiator Public Key

Let us begin with the easy part: transferring the timelock-branch Initiator public key.

During the first step of the Forwardable Peerswap protocol, the Initiator must provide the public key for the timeout branch of the Spilman channel:

I = i * G

Suppose the peerswap is forwarded by the peer by one hop away, i.e. the Initiator’s direct peer is actually a Forwarder and not the final Acceptor. In that case, the Forwarder will generate a 256-bit tweak less than n (or is that p), and will send the point as:

I + F = I + f * G

If the next peer then decides to be an Acceptor, it also further generates another tweak, and in its forwardable_peerswap_accept message, will also reply with the tweaking scalar in addition to the final point:

a <- random
I + F + A = I + F + a * G

The Forwarder can validate that a * G, added to the original I + F it forwarded, matches the I + F + A that the Acceptor replied with.

Then the Forwarder itself also replies to the Initiator, sending:

a + f
I + F + A (copied verbatim from Acceptor)

Then the Initiator will validate that (a + f) * G, added to the original I it requested, matches the I + F + A that the Forwarder replied with.

Since the Initiator is the only one that knows the original i, and learns a + f from the Forwarder and Acceptor, the Initiator can use I + F + A as an Initiator-only public key.

Notice also that the Acceptor cannot generate a as some encryption of its own node ID; even if it did, what the Forwarder will reply to the Initiator is a + f, i.e. it will get obfuscated using a scalar that only the Forwarder knows.

There are two things we need to ensure here:

Thus, we know our method of obfuscating the Initiator public key will prevent surreptitious communication between Initiator and Acceptor, and prevents key cancellation:

Transferring the Initiator AND Acceptor Public Key

From the previous section, we already argued that if the Acceptor can demonstrate knowledge of a, this prevents both surreptitious data transfer from Acceptor to Inititator, and key cancellation by the Acceptor.

The easiest way to demonstrate knowledge is to reveal a openly, but there is also a thing called “zero-knowledge proof of knowledge”.

As it happens, Schnorr signatures are actually an example of “zero-knowledge proof of knowledge”; they prove that the signature generator has knowledge of the scalar.

Let us review how a Schnorr signature (e, s) is generated:

A = a * G ; public and private keys
m ; message

r <- random
R = r * G
e = hash(A | R | m)
s = r + a * e
(e, s)

The above can be validated by:

R[v] = s * G - e * A
e[v] = hash(A | R[v] | m)
e ?= e[v]

Now, we should ask: why does the e calculation include A in the hash?

The reason: because without it, it is possible to malleate a signature for A into a signature for A + b * G where b is a new random number.

However, for our purpose, that is actually what we want! The thing is that the Forwarder also needs to demonstrate knowledge of f, without revealing how many hops away the Acceptor is. Thus, it should always provide a single (e,s) that can be used with A + F = f * G + A. So we can simply remove the A from the hash. In addition, we can also remove m: what we need is only to prove knowledge of a and f.

Thus generation of the zk proof-of-knowledge:

A = a * G ; public and private keys

r <- random
R = r * G
e = hash(R)
s = r + a * e
(e, s)

And validation:

R[v] = s * G - e * A
e[v] = hash(R[v])
e ?= e[v]

When the Forwarder has validated the response from the Acceptor, it can the malleate the above proof by computing s[a + f] as:

s[a + f] = s + f * e
(e, s[a + f])

This then serves as a combined proof of knowledge of a + f, the same as needed in the previous section.

Notice that we use (e, s) instead of (R, s). The reason is that R might be used to surreptitiously transfer data from the Acceptor to the Initiator; notice how in the forwarding case above, e is copied verbatim, and if we were using R, then it would have to be copied verbatim as well. However, e is the output of a hash function, and using e to transmit information would require reversing a hash function.

Then, for this particular public key, I + F + A can be treated by the Initiator as “Initiator AND Forwarder AND Acceptor”. This can be trivially reduced to “Initiator AND Acceptor”, after the Spilman channel is opened using I + F + A as the combined key, by having the Forwarders then reveal f to the Acceptor. The f can be included in the message sent by the Initiator once the Spilman channel is deeply confirmed forwardable_peerswap_onchain_ready, with the Initiator providing a 0 f, and each Forwarder adding its own f when forwarding that message until it reaches the Acceptor (the Forwarders should cache the message until it has seen the Spilman channel as deeply confirmed). (For privacy, the Initiator should actually also add its own obfuscating f when it first sends I beforehand, so that the first Forwarder does not learn that the direct peer is the true Initiator; this is left as an exercise to the reader.)

Operation After Spilman Confirmation

Once the Spilman channel has confirmed, as noted, for the “Initiator AND Acceptor” public key, the Forwarders need to reveal their f tweaks to the Acceptor.

After the Spilman channel is confirmed, the Initiator can then offer an HTLC inside the Spilman channel to the Acceptor. This is done by creating a transaction that spends the funding outpoint and has a single output encoding the HTLC, and having the Initiator and Acceptor use a MuSig2-like protocol to generate the signature for that transaction.

Then, once the Acceptor is capable of claiming an HTLC on the Spilman channel, it can forward to the Poon-Dryja Lightning Network channels, eventually reaching back to the Initiator.

At this point, one of the following two things can happen:

In both cases, we can use Private Key Handover to reduce the onchain blockspace used to close the channel.

As noted, in the abort case, we can simply close the channel in favor of the Initiator, and once that is confirmed, the Initiator knows that the abort completed and it can recover its funds.

· ZmnSCPxj jxPCSnmZ · #2 ·

Closing the Amount And Timelock Surreptitious Tunnels

Despite this, there are still two remaining pieces of information that cannot be obfuscated by Forwarders:

Of this, the amount is the one with the most bits available where the Initiator can provide information about its identity. While a node identifier is 257 bits formally, what you need to locate a node on the actual living Lightning Network is far fewer bits; we only need to surreptitiously transfer enough bits to identify ONE node on the gossip map. To close the surreptitious communication tunnel in the amount, we can standardize the onchain amounts among a small number of options, e.g.

For the timelock, we can standardize that the timelock must be +2016 blocks from the current block height, and standardize a tolerance from +2012 to +2016 blocks (to tolerate new blocks arriving during initial negotiation).

Another piece of data that we might be concerned with be onchain feerates, which would need to be agreed upon by the Initiator and Acceptor, who are involved in onchain operations. However, feerate decisions can now be deferred today by simply using a 0-feerate transaction for the in-Spilman HTLC offer; then if the Acceptor has to use it to claim the fund, it can pay using the child transaction, or if the Initiator has to use it to reclaim its fund in a protocol abort, it can pay using the child transaction as well. Thus, feerates can simply not be transmitted in the protocol, preventing it from being used as a surreptitious communications tunnel.

· ZmnSCPxj jxPCSnmZ · #3 ·

No no no no this is wrong and broken.

The problem is that if e[v] = e, then R[v] = R because e[v] = hash(R[v]) and if the hashes are equal, with very high probability R[v] = R. Then the computed R[v] during validation can be used as the surreptitious communications tunnel from the Acceptor that generated the original R to the Initiator (e.g. the Acceptor can just use its node ID as the R directly in this case, and R reuse is not a thing here since the Acceptor can trivially use different a scalars for each such zkpok), letting them bypass the Forwarders and establish a channel directly.

To fix this protocol, observe that we already have established a way to transfer a key controlled only by the Initiator to the Acceptor, but obfuscated so that it cannot be used by the Initiator to surreptitiously signal its node ID to the Acceptor, and vice versa. We can use the same protocol in reverse for a key controlled only by the Acceptor (A + F + I with Initiator revealing i and Forwarder revealing i + f), and thus similarly tweaked by the Forwarders to prevent surreptitious tunnels.

Then the condition “Initiator AND Acceptor” can be a simple MuSig2 of the two shared, tweaked public keys, one of which is solely controlled by the Initiator and the other is solely controlled by the Acceptor.