-
Couldn't load subscription status.
- Fork 519
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 |
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! |
In this extension BOLT, we specify the initial flavor of taproot channels to be deployed. This channel type uses musig2 aggregated keys and signatures for the funding output, making it a normal single signature key path spend. All outputs are then updated to use P2T2 (segwit v1) outputs. The coop close process has been simplified to always terminate, and the co-op close transaction now also flags RBF to make way for future schemes that enable the process to be restarted which enables co-op close fee bumping. A top-level key spend output is used to the revocation of HTLC outputs. The revocation for the local output uses a script path to ensure that information needed to sweep the anchors by 3rd parties is always revealed on chain.
1ad02ee to
4c1314a
Compare
|
Aight, I just pushed two major updates to the BOLT
|
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.
We've tested simple taproot channels against https://github.com/Roasbeef/lnd/tree/prod-taproot-chans and we can
- open channels
- create and pay HTLCs
For these tests to work we had to:
- modify lnd's commitment weight for taproot channels (which is not specified yet) and set it to 960 instead of 968 (because we expect the tx output count to be encoded on 1 byte instead of 3).
- implement the single nonce TLV fallback when the map is missing for
revoke_and_ack
Closing does not work yet though, as lnd does not seem to understand closing_complete (or I could not configure it properly ?), but we implemented the changes described in #995 (comment) and I believe that there's everything we need for option_simple_close but I proposed changes to some of the descriptions.
| - MUST extract the partial signature (first 32 bytes) and the sender's next | ||
| closee nonce (remaining 66 bytes) |
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.
| - MUST extract the partial signature (first 32 bytes) and the sender's next | |
| closee nonce (remaining 66 bytes) | |
| - MUST extract the partial signature (first 32 bytes) and the sender's JIT | |
| nonce (remaining 66 bytes) |
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.
Here the distinct terminology was used on purpose. I used closee/closer here, as the roles are always non ambiguous. If you're sending closing_complete, then you're the closer.
I think we should agree on what terminology we'll use here. I adopted the closee/closer based on the initial comment @t-bast left outlining a protocol (adapted it a bit as you see here).
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.
It's the use of "next" that is wrong, but I guess that just removing it could be enough.
| (1, 2, 3) to distinguish taproot signatures. Each `partial_sig_with_nonce` | ||
| contains: | ||
| - 32 bytes: MuSig2 partial signature | ||
| - 66 bytes: The sender's next closee nonce for potential RBF iterations |
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.
| - 66 bytes: The sender's next closee nonce for potential RBF iterations | |
| - 66 bytes: The sender's JIT nonce used to generate the partial signature. |
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.
Same here, what about The sender's closee nonce used to generate the partial signature ?
| are delivered just-in-time with signatures using an asymmetric pattern: | ||
|
|
||
| - `closing_complete` uses `PartialSigWithNonce` (98 bytes) to bundle the | ||
| signature with the next closee nonce |
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.
| signature with the next closee nonce | |
| signature with the JIT nonce it was generated with |
| - MUST store (in memory) the extracted nonce for potential future RBF | ||
| iterations |
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.
Remove ? The receiver does not need to store these, only the sender must remember them for when they receive 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.
Agree, will remove.
| the `shutdown` message or the previous `PartialSigWithNonce` in | ||
| `closing_complete` |
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 `shutdown` message or the previous `PartialSigWithNonce` in | |
| `closing_complete` | |
| the `shutdown` message or the `next_closee_nonce` in | |
| the previous `closing_sig` |
| - MUST use the appropriate nonces for verification: | ||
| - The sender's closee nonce (from their `shutdown` message or previous | ||
| RBF round) | ||
| - The receiver's own closer nonce (locally generated) |
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 receiver's own closer nonce (locally generated) | |
| - The receiver's own closer nonce used to generate their `closing_complete` |
| With this JIT nonce approach for coop close, an implementation only needs to | ||
| store (in memory) the current closee nonce for the remote and local party. When | ||
| either side is ready to sign, it'll generate a new closer nonce, and then that | ||
| along with the 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.
| With this JIT nonce approach for coop close, an implementation only needs to | |
| store (in memory) the current closee nonce for the remote and local party. When | |
| either side is ready to sign, it'll generate a new closer nonce, and then that | |
| along with the sig. | |
| With this JIT nonce approach for coop close, an implementation only needs to | |
| store (in memory) the current closee nonce for the remote and local party, and | |
| the closer nonces they used in their `closing_complete` message. When | |
| either side is ready to sign, it'll generate new closer nonces, and send them | |
| along with the partial signatures. |
| 1. `tlv_stream`: `revoke_and_ack_tlvs` | ||
| 2. types: | ||
| 1. type: 4 (`next_local_nonce`) | ||
| 2. data: | ||
| * [`66*byte`: `public_nonce`] |
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.
We should reuse the same nonce map TLV that is attached to channel_reestablish, it is easier to use with splices.
Typically we use the most conservative values for the weight estimates. One example is assuming the largest possible push for op codes like |
The RBF support is behind an additional build flag: |
You mean reading the existing filed if the newer nonce map isn't used? |
Yes. |
|
@Roasbeef @sstone opened a PR to clarify what we think must change to match the latest discussions in Roasbeef#8 This PR also adds support for dual-funded taproot channels and introduces the TLVs used for splicing. |
Using 3 bytes to encode the number of tx outputs is reasonable (though it seems that the BOLTs sometimes use 1 byte) and we can use 968 bytes for the commitment weight of taproot transactions. |
This is broken, actually, because the host can generate its own |
|
@t-bast thx for the PR, I just gave some feedback there. |
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.
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.