-
Notifications
You must be signed in to change notification settings - Fork 521
extension-bolt: simple taproot channels (feature 80/81) #995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
d7b1fe6 to
ec8c7b4
Compare
|
Some things that came up in meatspace discussions:
|
|
I think the commit_sig should contain the sender's "remote nonce" and the revoke_and_ack contain the sender's "local nonce". Also since funding_locked will be sent repeatedly with scid-alias when that is merged and deployed, then there should probably be language to define that the nonces are only sent the first time? |
|
let's try to pick naming conventions for nonces that doesn't make me cry over the asymmetry |
|
Some points: This interacts with the 2-of-3 goal of @moneyball . If one participant uses a 2-of-3 and owns ALL 3 keys, then it is fine and we can just have MuSig2 with both channel endpoints. But the 2-of-3 goal is that one channel endpoint is really a nodelet-like setup: there is one sub-participant with 2 keys and another "server" participant with 1 key, a la GreenWallet. This requires composable MuSig2. Now I think composable MuSig2, if it can be proven safe, just requires two -- This interacts with VLS as well @ksedgwic . The nonce A similar technique may also be useful for the server in the 2-of-3 of @moneyball; rather than maintain a state for each channel of each client, the client could store the per-channel |
|
So I talked to @jonasnick, and as I understand it, we can work with just two |
|
Re recursive musig2: I'm gonna give the implementation a shot (outside the LN context, just the musig-within-musig) just to double check my assumptions re not needing to modify the (revised) nonce exchange flow. |
|
i made a pull request on this pull request with script fixes |
Why not the revocation key? When i publish an old state, the remote party can claim my output and htlcs with the key path, but not his own output, and also has to wait a block. If we set the internal key to the revocation key it will give the remote party more privacy, nobody on chain can see which outputs were to local and to remote (and htlcs if they are swept along). It will also give more consistency with other output as they also have the revocation key as internal key. it will also be cheaper (or get a higher fee rate with the same amount of sats), this only requires a signature from a taptweaked revocation key (65) instead of a signature (65), the script (36) and the controlblock (34) (incl length prefix) |
|
#995 (comment) makes it invisible for outside observers to identify the to_remote output in case of a revoked commitment. if there are some htlcs on it that are long expired and the second stage is broadcasted (like in the fee siphoning attack), the funds go to the local delayed pubkey + relative timelock. outside observers can now see which output was the to_local one, just search the output of an htlc 2nd stage tx in the commitment transaction. example ctx: 15c262aeaa0c5a44e9e5f25dd6ad51b4162ec4e23668d568dc2c6ad98ae31023 (testnet) the transaction with the expired htlc reveals the to_local output. (it is already revealed by the script, but this wouldnt be the case with a revoked taproot ctx) this can be fixed by tweaking the local delayed pubkey with the hash of EDIT: no secret is needed, instead a taptweak like tweak can be done. everywhere where a local delayed pubkey is used, it is tweaked with for clarity: htlc outputs that send funds to the local delayed pubkey use a tweaked local delayed pubkey where the output index of the htlc output on the commitment transaction is used, not the htlc success or timeout tx |
this would preserve privacy, but you'd also need to do this for the |
hmmm true, so it is either privacy, with no key reuse or no utxo set bloat. btw another idea about anchors and less utxo set bloat: |
The |
if this is a problem B can tweak the key before using it without A even knowing (also A has to do this because the lexicographically smaller key is tweaked), but i dont think it is, lnd uses a separate bip32 tree for this (separate from the wallet) (btw without taproot funding pubkeys were revealed every time a channel was closed) |
afaict the algorithm in this bip is generalized for 32 byte pubkeys and more than 2 signers, the 'simple' musig2 with the pubkey's with the parity bit known looks like this equation i used, btw i got it here https://github.com/t-bast/lightning-docs/blob/master/schnorr.md#musig2 |
instagibbs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some old comments I forgot to submit
Most recent comment is noting that partial sigs are 32 bytes, so this needs explicit defining somewhere, since signature types seem to assume 64(may have missed it).
nvm this wouldn't work because keys are only revealed when swept without signature to make this problem somewhat easier i suggest to remove the now that the this special case can of course be seen from both sides:
even more rare: revocation no anchor keys are revealed here because with the revocation key the taproot key path is used. i don't know to make anchor sweepable in this case long story short:
Questions/feedback welcome! |
MPins
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text says that n_A_L is sent in the open_channel message, but in the diagram (taproot_channel_open.jpg) it is sent in the funding_created message.
In this commit, we make a few changes to the local nonces map as
defined:
* Drop the length prefix, just encode the nonces concatenated to each
other. The number of nonces to decode can be computed from the size
of the nonces and a txid.
* Using the funding txid in the nonce map instead of the empty hash.
|
@Roasbeef is |
|
@t-bast here's a branch that has everything combined: https://github.com/Roasbeef/lnd/commits/taproot-interop/. To activate taproot and rbf close you'll need these arguments/flags: |
Hi @Roasbeef I tested that branch at Roasbeef/lnd@5d6551c. Closing channels (using the "simple close" protocol) only works partially. When closing an eclair<->lnd taproot channel, eclair receives |
So in As far as If you look at this commit: 4c1314a, you'll see that the nonce map was only added to I'll look into the regression for rbf coop close in that interop branch I made, could have been some stray line in the refactor, or the large-ish series of changes that separate that branch from master. |
We'll return that if we expect the |
|
Looks like the difference is the newer branch has stricter validation for when we expect Will need to reload some context to resolve this |
You need new nonces to sign all the pending, active commitments. When a splice is unconfirmed, you have two commitments to sign (that spend a different funding transaction). So you need a map of nonces, a single one won't be enough. That was updated by @sstone in Roasbeef#8, but it looks like you haven't fully integrated the changes from that PR...maybe we should close it and re-open another PR with a single commit that contains the remaining changes we need in the spec? |
But when a splice is signed, isn't it the case that for those two commitments, a new
Yeah I gave some feedback on that, but some of my comments were never responded to. It was also based off a dated version of this branch. Part of my feedback was that it added a lot of context re splicing that would be better done as a follow up. I tried to integrate just the essentials in my last commit, but looks like I missed some details. I've pushed a new version of the above branch with the following changes:
You can find the new branch here: https://github.com/Roasbeef/lnd/commits/taproot-interop/ Fresh branch, still need to do some testing of my own. I also need to update this PR to track on that extra commit with the additions for revoke and ack. |
We must use the `next_local_nonces` TLV instead of a single nonce TLV in the following messages to be future-proof with splicing: - `channel_reestablish` - `revoke_and_ack` We must include one nonce for each commitment transaction that can be published: when there is a pending splice, that means we need one nonce for the current funding transaction, another nonce for the commitment transaction that spends the splice transaction, and additional nonces for each RBF attempt of that splice transaction (if any). All of those commitment transactions use the same `per_commitment_point` and are all revoked with the same secret: even though we send multiple `commit_sig` messages while splices are pending, we send always send a single `revoke_and_ack` message. This is why it must contain the map of all next local nonces.
If we want to be future-proof with splicing, we must include the `funding_txid` in the shachain root, which allows having distinct shachains for each splice transaction to deterministically derive local commitment nonces.
The specification for `option_simple_close` was inconsistent with regards to closee and closer nonce. We fix the incorrect or unclear requirements.
While `lnd` uses the legacy `closing_signed` mechanism for early taproot channels support, this shouldn't be in the BOLT: we should require `option_simple_close` whenever using taproot channels, which was explicitly designed for taproot.
We send one
I have opened Roasbeef#9 which contains independent commits that would bring the spec to match what is future-proof with splicing, without including unnecessary splicing extensions. You're right that we should only include the minimal stuff needed for splicing, and will open a separate PR later to add extensions to the splice messages. I hope it clarifies the remaining work needed to get us to cross-compat!
Note that the nonce map must also be used in |
|
Additional info: I tested against https://github.com/Roasbeef/lnd/commits/taproot-interop/, I can open channels, pay, but not close them, though lnd sends a different error this time: |
Updates to simple taproot channels for cross-compatibility
|
I would like to bring up the following thought: Taproot allows MuSig2 to be used, and in addition, nested. (Let us set aside the fact that MuSig2-in-MuSig2 has not been proven safe as of now yet) So, in theory, it would be possible with Simple Taproot Channels to have Bob be composed of two signers, Bobbie and Bobbette. However, a difficulty arises due to the use of shachains in the revocation scheme. As far as I can tell from this current spec, we are still using shachains for the revocation scheme (and in fact it is being recommended, though not required (?), to be used to derive the JIT nonces The problem is that who knows the revocation key seed? Ideally, given signers Bobbie and Bobbette, neither should be able to know the entire revocation key seed. The problem is that we use shachains for the revocation key in order to reduce storage for the channel state. A shachain is constructed by repeatedly hashing the revocation key seed. This requires that some single entity have the revocation key seed in order to be able to give a specific revocation key to the counterparty. This loses some of the advantages of having multisignature signers. If both Bobbie and Bobbette know the revocation key seed, Alice need only hack one of them, extract the revocation key seed, then send an On the other hand, we should note the following facts:
Can we consider removing the shachain requirement and just have the other side always store all revocation keys in an incompressible manner? If not now, then maybe at some point in the future? |
t-bast
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly good to me, I have a couple of minor nits but that's all! I wonder if we should now include test vectors (since you prepared a section for it at the bottom) if we're almost ready to merge?
| Compared to the base segwit v0 channel type, for simple taproot channels, the | ||
| co-op close transaction now always signals RBF. In other words, the sequence | ||
| field of the sole input to the cooperative close transaction MUST be | ||
| less-than-or-equal to `0xfffffffd`. This enables a future cooperative closure | ||
| flow to support increasing the fee of subsequent close offers via RBF. | ||
|
|
||
| In addition, rather than adopt the existing cooperative closure fee rate | ||
| "negotiation", the responder SHOULD now always accept the offer sent by the | ||
| initiator. In other words, the cooperative close process now terminates after | ||
| exactly 1 RTTs: initiator sends sigs with offer, with the responder echo'ing | ||
| back the same fee rate. This serves to ensure that the co-op close process | ||
| always terminates deterministically, and also plays nicer with the nonces: only | ||
| a single message is ever signed by both sides for a coop close workflow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like I missed that paragraph in my commits, which refers to the legacy closing protocol. Now that we updated this spec to rely on option_simple_close whenever using official taproot channels, we should remove this paragraph?
It looks like we can actually merge this section and the "Channel Cooperative Close Extensions" below into a single section and remove references to legacy vs modern close entirely?
| will use when sending `closing_sig` messages. This applies to both the legacy | ||
| `closing_signed` flow and the modern RBF flow using | ||
| `closing_complete`/`closing_sig`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed that one as well :/
| will use when sending `closing_sig` messages. This applies to both the legacy | |
| `closing_signed` flow and the modern RBF flow using | |
| `closing_complete`/`closing_sig`. | |
| will use when sending `closing_sig` messages. |
| `inclusion_proof` must be specified along with the control block. This | ||
| `inclusion_proof` is simply the `tap_leaf` hash of the path _not_ taken. | ||
|
|
||
| TODO(roasbeef): specify full witnesses? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove, I don't think we need to include that?
I think it's out of scope for this feature, there's already a lot going on in this "simple" taproot channel! It would be a big change to also change the revocation mechanism, especially since using nested musig is not something anybody plans to seriously work on in the near future. We can have a dedicated feature bit (and dedicated PR) for that whenever we have implementations willing to actually ship nested musig features. |
Update: I tested again against Roasbeef/lnd@7552d18, using our own branch at ACINQ/eclair@1413e8c
|
Update: all basic interop tests pass (open/send/receive/close/re-connect) between lnd (lightningnetwork/lnd@d4097b1) and eclair (ACINQ/eclair@ea9c4ca) . The version of eclair used for testing implements the changes and messages defined in this PR at 9150ef6, without extensions/interop workarounds (they're not needed anymore). |
This PR puts forth two concepts:
The extensions described in this document have purposefully excluded any gossip related changes, as the there doesn't yet appear to be a predominant direction we'd all like to head in (nu nu gossip vs kick the can and add schnorr).
Most of the changes here described are pretty routine: use musig2 when relevant, and create simple tapscript trees to fold in areas where the script has multiple conditional paths. The main consideration with
musig2is ofc: how to handle nonces. This document takes a very conservative stance, and simply proposes that all nonces be 100% ephemeral, and forgotten, even after a connection has been dropped. This has some non-obvious implications w.r.t the retransmission flow. Beyond that, it's mostly: piggy back the nonce set of nonces (4 public nonces total, since there're "two" messages) on a message to avoid having to add additional round trips.The other "new" thing this adds is the generation/existence of a NUMs point, which is used to ensure that certain paths can only be spent via the script spend path (like the to remote output for the remote party, as this inherits anchor outputs semantics).
This is still marked as draft, as it's just barely to the point of being readable, and still has a lot of clean ups to be done w.r.t notation, clarify, wording, and full specification.