lightningdevkit / rust-lightning

A highly modular Bitcoin Lightning library written in Rust. It's rust-lightning, not Rusty's Lightning!
Other
1.14k stars 353 forks source link

Experimenting with DLC-style of contract on Rust-Lightning #605

Open ariard opened 4 years ago

ariard commented 4 years ago

@nkohen and Yancy Ribbens are interested to experiment offchain DLC. RL suits very well for this, I think ideally DLC extension should be downstream as another under rust-bitcoin org repository to make progress as its own rhythm but right now opening this issue to track what we should do.

@nkohen I'm willingly to start the rust-dlc skeleton compatible with Rust-Lighning and let you fulfill holes from then to learn Rust but I need a better description of the protocol do you have in one of your blog or in dlcspecs ?

cc @arik-so (you should be definitely interested, DLC where my main reason to experiment with PTLC a while ago :) )

Christewart commented 4 years ago

If anyone is interested in organizing a "formal" effort for this, it appears Fulmo is having another hackday over the weekend of May 9th-11th. It was pretty useful for us all to collaborate on the first version of PTLCs over that weekend and get something working.

@channel Save the date! The next #LightningHacksprint is coming up on the weekend of May 9th/10th 2020. Please use https://wiki.fulmo.org to update your challenges, bounties and self-organized sessions. Ping me or @rootzoll is you have any questions.

LLFourn commented 4 years ago

@Christewart I'll join again :). Another approach we could take would just to implement a normal PTLC payment without randomization at each hop. i.e. just making the one-way function for the secret a point multiplication rather than a hash. This seems like a pre-requisite to doing DLCs anyway. Here's what i think are the most minimal script changes to do a PTLC (fyi I'm a script noob):

Offered HTLC

Before

OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_NOTIF
        # To local node via HTLC-timeout transaction (timelocked).
        OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        # To remote node with preimage.
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF

After

OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    # Goes to either PTLC-success or  PTLC-timeout transaction
    2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
OP_ENDIF

Note that for this case we need a new "PTLC-success" transaction for an offered HTLC which isn't necessary in the original (the output can be spent with any transaction if secret is known).

Received HTLC

Before

OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        # To local node via HTLC-success transaction.
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        # To remote node after timeout.
        OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF

After

OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        # To local node via PTLC-success transaction.
        2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        # To remote node after timeout.
        OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF
nkohen commented 4 years ago

Just a quick note that @ariard made in slack, if we really wanted to go for minimal script changes, we could actually not change any of the above scripts and instead just use adaptor sigs directly where some fixed "payment hash" with known pre-image is used

LLFourn commented 4 years ago

@ariard That's clever but I don't think that works on the offered HTLC side:

# To remote node with preimage.
OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
OP_CHECKSIG

That OP_CHECKSIG checks against the remote_htlcpubkey but it adaptor sig here needs to be given under local_htlcpubkey. It seems like we need to modify script to get this to work.

ariard commented 4 years ago

@LLFourn scalar release is going from Bob to Alice so it's remote_htlcpubkey we're interested with here ?

I think I don't talk with your latest protocol in mind I'm still on https://github.com/ElementsProject/scriptless-scripts/blob/master/md/multi-hop-locks.md

Can you describe or points me towards the latest PTLC flow exchange ? Like Alice send offer to Bob, ... You may be right but we have way to hack on current scripts.

The good news is we have an external signer in RL since soon, API is still WIP, but what we can do is swap commitment transaction HTLC scripts, by PTLC at signing (see ChannelsKeys::sign_local_commitment). It will of course fail verification right now at commitment_signed reception but that's few lines to move behind something ChannelsKeys::verify_remote_commitment.

Anyway we'll need our own set of messages, what I want to avoid is touching the state machine, but round-trip isn't the same ?

ariard commented 4 years ago

On the dependency-tree, is this something like this ?


                                         rust-dlc
                                           /
                                         /
                                    rust-ptlc
                                       /
                                     /
                             rust-lightning 
                                  /
                                /
                     rust-secp256k1 (another fork or Yancy fork?)
                            /
                          /
             secpk256k1 (nickler fork)

We should aim to keep dependency hell manageable. I think rust-ptlc would be an overlay of rust-lightning, until PTLC have formalized in BOLTs (surely gonna take forever...) and we can host them in-tree (aka rust-lighhtning). Having a seperate repo means we can go our own pace.

It should be rebased on downstream but if we do it right I can maintain it. Also making rust-lightning more flexible is something which can be done in-tree.

Thoughts ?

ariard commented 4 years ago

Note that for this case we need a new "PTLC-success" transaction for an offered HTLC which isn't necessary in the original (the output can be spent with any transaction if secret is known).

If you're interested you can go through whole discussion https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-April/002639.html but tl;dr likely we're going to lockdown remote 2nd-stage txn on local commitment to make LN just-secure

Another approach we could take would just to implement a normal PTLC payment without randomization at each hop. i.e. just making the one-way function for the secret a point multiplication rather than a hash.

For a PoC, we should do first a one-hop, so need to touch onions. But yeah the meanwhile, we can think about other data structs we need to touch for multi-hop (onion, invoices)

Fyi, there is idea to move towards a bidirectional communication channel for LN (https://www.scion-architecture.net/pdf/2015-HORNET.pdf), it's something PTLC would benefit

ariard commented 4 years ago

If anyone is interested in organizing a "formal" effort for this, it appears Fulmo is having another hackday over the weekend of May 9th-11th.

@Christewart, thanks for dates I will definitely participate this time :)

ariard commented 4 years ago

@Tibo-lg, how your rust-dlc repo can be combined with PTLCs or are you using DLCs 2nd-stage transaction pluggable on actual HTLC output scripts ?

LLFourn commented 4 years ago

@LLFourn scalar release is going from Bob to Alice so it's remote_htlcpubkey we're interested with here ?

Alice and Bob is too much for me atm so I'll just use "local" and "remote". The payment is going from the local party to the remote party and the scalar release is going from the remote party to the local right? In order for the local party to ensure that there is no way for the remote party to claim the coins other than completing an adaptor signature with the scalar the CHECKSIG that releases the scalar needs to be on a key only the local party knows. I chose local_htlcpubkey for this. Of course, you need another CHECKSIG to make sure that the condition that is spent is completes_adaptor_on_local_key AND is_remote_party. So we use OP_CHECKMULTISIG.

I think I don't talk with your latest protocol in mind I'm still on https://github.com/ElementsProject/scriptless-scripts/blob/master/md/multi-hop-locks.md Anyway we'll need our own set of messages, what I want to avoid is touching the state machine, but round-trip isn't the same ?

I think @jonasnick mentioned he might update that diagram for the OP_CMS adaptor lock. With OP_CMS adaptors round trips should be the same as now -- just the payment hash replaced with 33 byte point (I think xonly here just makes things confusing) + adaptor signatures. Note that when you create a PTLC, two ECDSA adaptor signatures needs to be sent by the party offering the PTLC. One of them is just replacing what used to be a normal signature here:

# To local node via PTLC-success transaction.
# OP_HASH160 <RIPEMD160(payment_hash)> 
# OP_EQUALVERIFY <----- this check is removed and instead of the signatures below becomes adaptor.
2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG

Then the other is a new adaptor for the new "PTLC-success" transaction on the offered side.

If you're interested you can go through whole discussion https://lists.linuxfoundation.org/pipermail/lightjust a new transaction and an adaptor on it.ning-dev/2020-April/002639.html but tl;dr likely we're going to lockdown remote 2nd-stage txn on local commitment to make LN just-secure

Thanks. I only vaguely understand all the RBF discussion here. It's true if this change were implemented now then we could use the dummy payment hash trick but since it probably won't be done I guess we should prepare ourselves to change script and do it properly :P

Tibo-lg commented 4 years ago

@ariard Right now our goal was to get a basic DLC implementation, with the classical construction (so without PTLC or adaptor sigs). It's still WIP, and right now @yancyribbens was working on forking rust-secp256k1 to point to a branch where we can have the latest schnorr module. But I think there can be synergy at the rust-secp256k1 level so that we could maintain a single fork providing functions for both "classical" DLC and "fancy" ones.

yancyribbens commented 4 years ago

@ariard I can share a Repo that's open source that contains my current progress on the "classical" DLC construction (without adapter-sigs and PTLC). The CET (as @Tibo-lg mentioned) and the bindings are still a work in progress at the moment but I think it can help the discussion about how to move forward using either a different repo or adding on to the current construction. Regarding the dependencies, I'm currently using a secp256k1-zkp fork but @nkohen recommended using a schnorr fork of secp256k1.

current "Classic" construction:

                                         rust-dlc
                                           /
                                         /
                             rust-lightning 
                                  /
                                /
                     rust-secp256k1 (jonasnick/schnorrsig?)

I'm not sure how to combine with PTLCs or adapter-sigs at the moment.

ariard commented 4 years ago

@LLFourn Okay gotcha, it's either "adaptor_sig for Alice_adaptor_key + Bob_pubkey" || "Alice_pubkey + timelock". First branch you need a sig from Bob to be sure that Alice doesn't malleate Bob's redeem once seeing scalar. Second you can't reuse same key for Alice because she needs to timeout without cooperation.

I think you can't still reuse offered output redeemscript. Just inverse 2nd-stage transaction. HTLC-timeout becomes Bob''s redeem and HTLC-preimage becomes Alice's timeout. Bob signs Alice's timeout and can so enforces timelock as he is the one interested with this semantic enforcement. Alice key is replaced by the hash, for which she knows preimage.

It's a bit hacky but that may work assuming we have few new messages?:


                                     update_add_htlc

                                ------------------------->

                                     commitment_signed         Bob[Alice_adaptor_sig] // assuming external signer swap on Alice-side

                                ------------------------->

                                       revoke_ack
                                <-------------------------

                                      NEW_send_back_sig

                                <-------------------------

                                     commitment_signed

   Alice[Bob_timeout_sig]       <-------------------------

                                      NEW_release_scalar
                                <-------------------------

                                     commitment_signed

                                <-------------------------

Like I said previously we have an external signer in RL, so you can reimplement the trait with a new one, which does some key swap to get semantic you want. Verification needs also to be abstracted.

Note that when you create a PTLC, two ECDSA adaptor signatures needs to be sent by the party offering the PTLC

I see, in this case, update_add_htlc becomes update_add_ptlc and provides adaptor_sig for spending on Alice commitment transaction? Okay likely it's better to have clean scripts from scratch, but like I said we can swap them against HTLC scripts in the external signer to minimize disruption :)

Thanks. I only vaguely understand all the RBF discussion here. It's true if this change were implemented now then we could use the dummy payment hash trick but since it probably won't be done I guess we should prepare ourselves to change script and do it properly :P

Until we do so, it's not that hard to steal HTLC on the network, so going to happen ;)

To move forward, if nickler or you have a more tied-up description, we can see how to integrate this with DLC?

ariard commented 4 years ago

@Tibo-lg , @yancyribbens @nkohen Yes I think it would be better to focus on Yancy rust-dlc now, to get as soon as we can an offchain-DLC PoC with which to play.

In the meanwhile we can keep thinking on 1p-ECDSA PTLC, but AFAICT the good thing it's not exactly the same component, DLC are different 2nd-stage transaction while PTLC different commitment output . So it should be hacky but far possible to recombine them latter.

If you have a link to your repo, I can start to dig in. I don't have an opinion on either rust-secpk256-zkp or rust-secp256k1

yancyribbens commented 4 years ago

Here's the current state of the Rust DLC implementation I've recently started. There's not much here yet, although hopefully this can help continue the discussion around a Rust implementation we can play with as @ariard mentioned. The next major obstacle (I think) is adding a Secp256k1 fork that includes shnorr sigs with DLC specific functions. Ideally we would incorporate the branch @nkohen is also working on nkohen/schnorrsig-sigpoint and keep that work common among all implementations as @Tibo-lg mentioned. I've researched adding nkohen/schnorrsig-sigpoint into a Rust wrapper but it's a bit of a lift.. Suggestions welcome.

yancyribbens commented 4 years ago

PRs/commits and comments also welcome :)

ariard commented 4 years ago

Fulmo Hackathon 05/09-05/10

The PoC

This hackathon is the occasion to assert the Simple-DLC-Channel design, as roughly laid out in https://github.com/discreetlogcontracts/dlcspecs/issues/3

This design enable single-hop DLC where at every maturation you need to resign the whole range of CET transaction.

CET output are directly bind on the commitment transaction and replace HTLC output. N CET-transaction may replace them, themselves spendable by closing/ penalty transaction.

We don't aim to cover the onchain monitoring during this hackhathon.

PoC deliverable is :

$> ./rust-lightning-bitcoincoreprc generate dlc-offer $> ./rust-lightning-bitcoincoreprc accept dlc-offer $> ./rust-lightning-bitcoincoreprc settle dlc

The Hacks

On the sender-side, starting at accept dlc-offer, we keep the current entry point ChannelManager::send_payment, until we get in returnMessageSendEvent::UpdateHTLCs. Internally, atChannel::commitment_signedwe abstract transaction building by some TxBuilder trait. Behind this trait we may userust-dlcto build transaction accordingly and pass them to ChanSigner::sign_remote_commitment. ChanSigner is already its own interface so we just have to implement a DLC-signing one. After commitment_signed we get a Signature that we return as a MessageSendEvent::UpdateHTLCs for now. Instead of calling directly PeerHandler::process_events, we extract signature and we switch it by MessageSendEvents::UpdateDLCs. We may encapsulate in some generic enum and pass it to PeerHandler::process_event.

We need well-defined update_add_dlc, update_fail_dlc, update_fulfill_dlc in msgs.rs.

On the receiver-side, we add custom message support in PeerHandler::read_event, using a new trait DLCChannelMessageHandler with handle_update_*_dlc. Same, this one call the same workflow, calling Channel::update_add_dlc and committing output in the channel object.

Then we need to reverse the workflow for implementing settle dlc-offer.

Listing tasks to be done:

Christewart commented 4 years ago

nit: I suggest we follow the blockchain rpc api we have for continuity purposes:

ariard commented 4 years ago

I'm fine with this blockchain rpc api, I'm not proposing myself to work on the rpc part so it's up to whom will hack on it :)

LLFourn commented 4 years ago

To avoid doing ffi stuff for different forks etc here's the code that let's you do the very basic schnorr stuff you'll need for this:

https://gist.github.com/LLFourn/ee73d67b3f4645e4c19bd853e1b17062

Good luck!