ElementsProject / peerswap-spec

6 stars 3 forks source link

Proposed Taproot implementation of onchain HTLC #12

Open ZmnSCPxj-jr opened 1 year ago

ZmnSCPxj-jr commented 1 year ago

As requested by @nepet here: https://github.com/ElementsProject/peerswap-spec/issues/4#issuecomment-1279505217

Here is a concrete implementation of the onchain HTLC using Taproot. IMPORTANT: This proposal does NOT require a MuSig2 signing ritual implementation; it only requires an implementation of the MuSig public key aggregation scheme. This has the advantage that the peerswap impl state machine requires practically no change, as there are no extra messages needed to implement the two-round MuSig2 signing ritual. In particular, this should neatly sidestep potential issues with protocol design that could render MuSig2 signing insecure such as this or this.

We also add a new message coop_success which the sender of the onchain HTLC sends to contain s. This message SHOULD be sent any time after the sender of the onchain HTLC claims the corresponding in-Lightning HTLC, i.e. after it sends update_fulfill_htlc for the corresponding in-Lightning HTLC.

In all cases, there is no need for a MuSig2 or MuSig signing ritual in order to utilize the keypath spend path. It is enough to send the r privkey with coop_close in case of an abort (which a receiver MUST NOT send after it has offerred the corresponding in-channel HTLC and it has not been update_fail_htlced), or the s privkey with coop_success in case of protocol completion. Once one node is in possession of both privkeys, it can derive the privkey corresponding to MuSig(S, R), and use a standard single-signing Schnorr algorithm using its own compute resources.

We expect single-signing Schnorr algorithms to be simpler to implement, test, and prove secure, so we expect that we can deploy this earlier compared to relying on MuSig2 signing algorithms (and also removing protocol-related bugs when doing MuSig2 signing).

Both coop_close and coop_success would benefit from an ACK as in #6. In case of a disconnection of the TCP tunnel between the nodes, if no ack has been received, the sender of coop_close / coop_success SHOULD re-send the message on reestablishment of connection. This is not absolutely necessary for security since the hashlock and timelock branches remain valid even if delivery of the r or s privkey fails, but are important for convenience and to avoid having to reveal Tapscript branches, which degrade privacy.

This technique of "private key handover" is generally only utilizable by onchain swap / HTLC protocols, where possession of the entire UTXO is taken by one or the other participant in a 2-participant protocol. The technique does not easily generalize to other protocols and is potentially insecure outside of swap protocols. As peerswap is a swap protocol it is safe to use "private key handover", and simplifies implementation and state machines involved.

Corresponding cases for aborting via the timelock branch, or force-claiming via the hashlock branch, use the obvious approach and will not be described in further detail.

ZmnSCPxj-jr commented 1 year ago

Now that I think about it a little more, we could use ECDSA still with this "private key handover" technique, as the signing algo is strictly single-sig like in most ECDSA implementations extant. This would let us use just a single signature after private key handover even without using Schnorr sigs, instead of 2 signatures. But without Taproot we would need to publish R, S, and MuSig(R, S) on the SCRIPT being revealed. ECDSA signaturese are at least 73 bytes while an extra ECDSA pubkey is 33 bytes, so saving a signature is still 40 weight units cheaper. Taproot just makes the space savings bigger as the pubkey is already revealed on creation of the UTXO.

MuSig(R, S) = hash(R) * S + hash(S) * R, and that can be computed just as well for ECDSA as for Schnorr. Once one side knows both s and r privkeys, the privkey for signing is hash(r * G) * s + hash(s * G) * r, regardless whether using Schnorr or ECDSA.

But Taproot > ECDSA anyway so ...

ZmnSCPxj-jr commented 1 year ago

Note however that private key handover is incompatible with #7, as private key handover assumes that the entire UTXO is owned by one or the other, but #7 wants the option of splitting the HTLC-bearing UTXO. In a "true Taproot" implementation you would need a MuSig2 implementation for that, you could also just use classic OP_CHECKMULTISIG to avoid the MuSig2 dance but at the cost of sacrificing the privacy benefits of Taproot.