Hello List! 

I've been working on a Proof-of-Work, Schnorr-based PTLC demo for Decred's LN 
and stumbled upon an interesting hack that may be of interest, as another point 
in design space for a full blown PTLC impl.

As it happens, this hack was in fact so simple to implement that I was able to 
also code up an interesting PTLC-backed construct (what I'm calling a 
Multi-Redeemer Transaction Tree - MRTTREE) and a demo for offline lightning 
payments over it.

Quick details following below. I have a (crappy) video of the concepts and demo 
here (demo starts at around the 15 min mark): 
https://www.youtube.com/watch?v=m1sQGHUKU7I

# Single Round, Trustless Schnorr-based PTLCs

By "single round" I mean it doesn't require additional comms rounds to 
irrevocably commit to a PTLC (the current `update_add_htlc()` -> 
`commit_signed()` -> `revoke_and_ack()` protocol is preserved).

For the following, assume a {H,P}TLC being added from Alice -> Bob (i.e. Alice 
is offering, Bob is accepting; Alice has the Sender{H,P}TLC output script and 
Bob has the Receiver{H,P}TLC script).

Recall that Schnorr-based PTLCs are constructed by way of adaptor signatures: a 
party that knows the adaptor sig can extract the secret scalar that corresponds 
to some public point after seeing the full signature (potentially on-chain). 
The secret scalar is encoded as an additive component of the full Schnorr sig 
(i.e., `s = (u+t) + ex` where `t` is the target secret nonce and `u` is the 
random nonce). 

Also recall that in the standard protocol to commit a channel to an HTLC, the 
`commit_signed()` msg sends the signatures to the _other_ party's commitment tx.

The most naive way to switch from HTLC to a PTLC is to have the 
{Sender,Receiver}PTLCScript commit to a Schnorr sig instead of a hash preimage.

This is easy to do, however it's not trustless for Alice.

Recall that the point of the preimage redeeming branch in the SenderHTLCScript 
is so that Alice can figure out the preimage if Bob redeems her commitment tx 
onchain (so that if she's forwarding the HTLC she can redeem her upstream 
accepted HTLC).

That's not safe in a naive PTLC setting because the secret scalar contained in 
the full Schnorr sig for Alice's commitment tx (i.e. the receiver-redeeming 
branch of SenderPTLCScript) is under control of Bob (because `r=(u+t)` i.e. the 
standard nonce of a Schnorr sig).

And thus ordinarily, even if Bob sent a valid adaptor sig to Alice during the 
`commit_signed()` msg, he could freely modify the sig once Alice's tx actually 
hits onchain (thus denying Alice the ability to find out the secret scalar).

My first imaginable solution to _that_ would be to have the receiver-redeem, 
scalar-revealing branch of the SenderPTLCScript require a signature from Alice 
as well. However, this breaks the current LN protocol, in that Bob would 
require a signature from Alice to redeem from her commitment _before_ sending 
his `commit_signed()` msg.

So you'd need an additional comms round before `commit_signed` for both parties 
to agree on the layout of the commitment tx and for Alice to send Bob the 
required sigs.

(doing this in the revoke_and_ack msg isn't safe for Bob because in the 
timeframe between his sending of `commit_signed()` and receiving Alice's 
`revoke_and_ack()` he wouldn't be able to redeem from Alice's commitment by 
presenting the secret scalar)

Thus, my hack: riffing off ZmnSCPxj's OP_CAT trick[1] for single-show 
signatures, we modify the secret-scalar-redeeming branch of the 
SenderPTLCScript to commit to a specific R point by encoding it **directly in 
the script**, then having Bob present _only_ the s scalar of the `(R, s)` fully 
valid Schnorr sig in the Signature Script of the redeeming tx, using OP_CAT to 
join both for consumption by the opcode that checks for Schnorr txs 
(OP_CHECKSIGALT in decred's case, not sure what will do that in bitcoin's 
taproot).

In short, that specific branch SenderPTLCScript looks like this (for decred): 

  <ptlc R point> OP_SWAP OP_CAT <ptlc key> 2 OP_CHECKSIGALTVERIFY
  
Thus, Bob is committed to a specific `r = (u+t)`, Alice has verified, upon 
receiving the corresponding adaptor sig `(R, s')` that in fact `R = U+T` (T is 
the original target payment point Alice provided in the `update_add_ptlc()`) 
and that s' is a valid adaptor sig for that combination.

Obviously, there are all sorts of footguns in doing this, specifically related 
with proper nonce selection so that Bob doesn't inadvertently reveal one of his 
private keys but can still redeem in the future even after a software restart. 
So it needs some serious cryptographic look to make sure it's safe in the 
cryptographic sense.

But it seems safe to me in the game-theoretical sense: at every step in the 
channel update protocol Bob can redeem from Alice's commitment and Alice can 
find out the secret scalar if Bob does so.

This is implementable with very few changes in the codebase, and makes PTLCs 
interoperable with standard HTLCs; you can have both in your channel at the 
same time, only small changes in the wire msgs are needed, no new comm rounds, 
etc.

The greatest downside I can see so far is that it makes local commitment txs 
ungeneratable before you've received the adaptor sigs in the `commit_signed()` 
msg.

That is, Alice can't generate her local commitment tx before Bob sends the R he 
wants to commit to by way of his adaptor sig (sent in his `commit_signed()` 
when he wants to lock in a PTLC) because that particular R appears as part of 
the SenderPTLCScript.

I don't _think_ this is a major downside, since Alice's commitment is useless 
(to Alice) before she receives Bob's signatures, but I'd be interested to hear 
otherwise.

And just to make it more explicit: this is implementing PTLCs _without_ any 
additional changes to the currently used HTLC scripts (modulo swapping the 
preimage challenge): in particular, it's _not_ aggregating/MuSig'ing the 
signatures into a single one, not making using of taproot/tapscript 
capabilities to further improve things, _not_ adding a per-hop tweak, etc.

# MRTREEs

This is long enough as it is and my intention was to share this hack as another 
interesting idea for PTLCs.

But since that was actually easy to code up as a PoC, I went ahead and did that 
and also built a PTLC-backed MRTTREE server with an offline lightning 
donation/payment system.

A single-sentence summary of MRTTREE is:

Submarine swaps meets channel factories to build an off-chain binary tree of 
transactions, such that an user that redeems a "leaf" amount of the tree 
reveals one of the keys needed to redeem the upper branches of the tree.

A more extensive description:

This construction is created by a set of Users (with off-chain funds) 
interacting with a Provider (with unemcumbered on-chain funds) to create a tree 
of transactions such that each leaf output is redeemable by 
[MuSig_1(User,Provider) or MuSig_2(User,Provider)+Timelock]. The second redeem 
path is the pre-signed one. Upper branches of the tree are redeemable by a 
similar script, except we include the keys from every child (i.e. for the 
immediate parent of a leaf, the non-timelocked key is 
MuSig_1(UserLeft,UserRight,ProviderLeft,ProviderRight) and so on).

A user can exit the MRTREE by "selling back" his User key to the provider (via 
a PTLC payment) such that the provider can then rewrite (recursively) the 
upward branches with the user key. In the limit where every user cooperates, 
the provider rewrites into a single output, with minimal on-chain footprint.

I have a specific use case in mind for this, and I've written more about this 
construction at [2] (but note that was written before I was aware of PTLCs so 
it uses a different method to achieve the goal of allowing the upward tree 
rewrite).

# Offline Lightning Payments

So, assuming the existence of a MRTTREE service, where LN users can "buy into 
shares" of a utxo (with a long initial timelock, in the months+ range) but 
still preserving their individual exit rights and the non-custodialness 
property, we can build a cute system for offline LN donations/payments:

An LN user joins a MRTREE with a well-known, public provider, creating multiple 
leafs of some set amount (pretty much, any amount higher than dust). The user 
can then go offline (from LN). It can even change/close its channels and 
whatnot. 

When the user wishes to make a donation to some third party, it can send the 
tuple (provider,tree_id,leaf_private_key) to that third party via any async 
method (chat, e-mail, twitter DM, whatever), which then contacts the provider 
and redeems the funds on behalf of the user.

The donation case is illustrative because if the recipient never redeems the 
leaf (before the tree timelock), the user can still redeem it back and thus not 
have the funds go to the void (or to an unrelated third party).

There are probably bunch of other interesting use cases for this construct.

My PTLC Proof of Concept is here:

https://github.com/decred/dcrlnd/compare/v0.3.1...matheusd:ptlc-poc

Sample MRTREE client and server:

https://github.com/matheusd/mrttree

GUI demo of offline LN payments:

https://www.youtube.com/watch?v=m1sQGHUKU7I&t=915s


# References

[1] 
https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-December/002388.html

[2] https://matheusd.com/post/ln-split-tickets-01-mrttree/
_______________________________________________
Lightning-dev mailing list
Lightning-dev@lists.linuxfoundation.org
https://lists.linuxfoundation.org/mailman/listinfo/lightning-dev

Reply via email to