Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save editor-Ajian/602589da4a3dd584fecde3a5ddbecc4d to your computer and use it in GitHub Desktop.
Save editor-Ajian/602589da4a3dd584fecde3a5ddbecc4d to your computer and use it in GitHub Desktop.

This post introduces a combination of MuSig2 with Signature Adaptors to enable PTLC (Point-Time-Locked-Contract) in LN channels, which is explicitly rely on option_simple_update.

In the process, I thought I find two things less being discussed:

  1. When combining signature adaptors with MuSig2 (-like aggregated signatures), the offer of a PTLC must get the partial signature of the receiver in advance, otherwise he have no guarantee to be able to extract the secret from the final aggregated signature. See the "A note about combining MuSig2 with signature adaptors" section.
  2. Using aggregated signatures to enforce PTLC doesn't allow us to leverage the SIGASH_SINGLE|SIGHASH_ANYONECANPAY trick, results in sub-optimal (even unacceptable) fee payment schemes (including the one in this design).

However, after I finish this work, (with some of my friends' help) I find that they ware already discovered by at least two authors. The first problem is found by, e.g. instagibbs1 and t-bast2. The second problem is noticed by, e.g. t-bast3.

In this design, the first problem is overcame by a new set of messages that are also used to exchange nonces. However, the second problem is critical, in my opinion. Maybe we should give up the idea of executing PTLC on aggregated signatures (at least in current LN protocol).

Further, this work should be viewed (at best) a proof of concept, as it still lacks some details about signatures being exchanged (which is necessary for executing conditions with signatures) and the implement of MuSig2 adaptors.

For readers familiar with this topic, feel free to begin with the 'Design Overview' section.

Overview of PTLC

PTLCs is a multi-hop lock enabled by 'Signature Adaptors', for which Taproot (specifically, Schnorr signatures) 4 5 has paved the way.

When being used in Lighting Network, PTLCs have better privacy and security than HTLCs, as the latter uses the same hash lock in every hops relaying an economic payment. If an adversary has two nodes in the route, he will be able to identify these two nodes are relaying the same economic payment. As the same reason, when the preimage is transmitted backward, he can bypass intermedia nodes between his two nodes, effectively stealing their routing fee, which is called 'wormhole attack' 5.

Instead of using hashes and preimages, PTLCs leverage (Elliptic curve) points and scalars, which allow us to assign a different point lock for every hop (only solvable after getting a secret from its downstream node) while keeping the atomic feature of multi-hop lock (success together or fail at all).

Beyond that, PTLCs can enable some advanced use-cases, e.g. ensuring the payment would not be stack in the relaying(update) stage (but not settlement stage) and atomic multi-path payments (AMPs). Interestingly, it can also enable trustless signature selling 6, which solves a key incentive problem in Discreet Log Contracts ecosystem.

Currently, the primary document for PTLCs in LN seems to be this article 4 from Jonas Nick.

A paper discussing the security of signature adaptors in multi party Schnorr signatures (specifically, MuSig) is here 7, from AdanISZ. The paper is still in 'DRAFT, UNREVIEWED' state, and the author said (in an issue) the work is not enough.

In one of the original PR discussing the security of MuSi2 signature adaptors, the author said (on Jun 3, 2024) we should base on a paper presented in 'Advances in Cryptology – EUROCRYPT 2024'8.

Design Choices and challenges

As far as I can tell, the design choices with regard to PTLCs could be summarized as these two questions (sorted by my subjective evaluation of importance):

  1. PTLCs in LN have to be taproot outputs (actually, it is our choice). However, should we implement it as an exclusive feature for taproot channels (defined by the scriptPubkey of the funding output is SegWit v1 form)? Or an option also open to SegWit v0 channels?
  2. Given that in current LN channels, PTLCs have to take forms of a 2-of-2 multi signature output in commitment transactions and be executed by two-stage transactions (like HTLCs), and that the implement possibilities of multi signatures in taproot age is rich, which signature technology should we adapt? Script-based multi-sig? or scriptless multi signatures (e.g. MuSig2)?
  3. Taproot-lization of three spending conditions of PTLCs and corresponding transactions.

As the (1) question, I strongly support adding PTLCs as an open option, as network effect matters. It only requires nodes to upgrade their software. Instead, if PTLCs is only usable for taproot channels, we may have to wait a long time for enough channels being upgraded to taproot type.

As the (2) question, here we would try scriptless multi signatures. Although it would introduce interactive complexity, but it may be worthy, as it brings a privacy, base protocol scalability, and operational economics triple-wins.

Importantly, as this LN summit 2024 note9 said, the interactive complexity, which is almost overburdened in current asynchronous update mode, is significantly alleviated by an ongoing feature called 'option_simplified_update'10, which introduces round-based update mode.

Based on these choices, the biggest challenge comes from MuSig2, which requires every aggregated signature to be produced in an interactive manner. Specifically, we can brake it out as three requirements:

  1. Relevant public keys of both of peers should be shared in advance, to produce aggregated public keys.
  2. The signer have to get an nonce from his/her peer in advance, to produce a valid (partial) signature (which would be aggregated with the peer's partial signature).
  3. Signers must carefully handle used nonces to avoid nonce reuse behaviour (which will expose the signing key.)

Further, for supporting PTLCs, we need to create 'signature adaptors' for some partial signatures on PTLC-txs. Theoretically, the adaptor can be a tweak on the aggregated nonce, essentially make the validity of the partial signature verifiably conditional on knowledge of a specific scalar. However, a complete specification for it is still lacking as far as I know. (It turns out that I underestimate this challenge.)

So, how can we do?

Pioneers

A cold(?) news: some excellent work have explored the possibility of PTLCs in LN.

First, we have a MuSig2 BIP - BIP032711. The 'Overview' section (specifically, the 'General Signing Flow' section) is really helpful in building up the intuition for MuSig2 signing flow, and (the 'Nonce Generation' section) gives a practical recommendation to avoid nonce reuses, i.e. feed some signing-session-specific data (e.g. sk, pk, addpk, m) to NonceGen algorithm.

(Note: in MuSig2, a public nonce is 66 bytes, which consists of 2 points.)

Second, the 'simple taproot channels' proposal 12 has developed a method to handle MuSig2 nonces. Here are key insights:

  1. Both of peers would in advance share a set of public nonces (called 'verification nonces'), to be consumed by the peer in commitment signing.
  2. When generating a partial signature for commitment, the signer generate a new nonce (called 'signing nonces'), to be aggregated with one of counterparty's verification nonce. The signing nonces will be transmitted with the partial signature in the same commitment_signed message, to be verified by the peer.

This method keeps signers stateless as much as possible: they just store a set of verification nonces from the peer, which can be deleted after consume. At the same time, it is also convenient for verifier: the verification nonces could be generated by hashchains, hence could be stored efficiently.

(This method is very essential in constructing MuSig2-based funding outputs and commitments. Although I personally prefer to share a new verification nonce in commitment_signed/revock_and_ack messages, just like per_commitment_point. t-bast also proposed to use the latter13.)

However, 'simple taproot channels' didn't add MuSig2 into HTLCs.

Last but not at least, BOLTs itself. Unsurprisingly, current P2P messages and scripts for channels and HTLCs tell a lot for designing PTLCs. We absolutely don't need to build from scratch. For example, every individual public key need to be aggregated is knowable/derived by open_channel/accept_channel messages and per_commitment_point in advance, so we don't need to design a new message to exchange public keys. HTLC-txs is another example.

Design Overview

Here we propose a new set of P2P messages to operate PTLCs in an environment constrained by option_simplified_update10.

We also apply a separation design: handle MuSig2 nonces for PTLCs and commitments separately. Thus, the proposal is compatible/pluggable with 'simple taproot channels' and similar proposals.

We use two of these new messages to require and response MuSig2 public nonces. These nonces are consumed in the same turn they are shown. When response, the node should also provide some partial signatures, to ensure functions of point locks for both parties. This design comes from a special challenge raised by combining MuSig2 and signature adaptors, we have to explain it in a separate section 'A note about combining MuSig2 with signature adaptors'. The messages are also designed to be able to cover MuSig2-inserted HTLCs.

After all, the update flow is like this:

        +-------+                               +-------+
        |       |--(1)---- update_add_ptlc ---->|       |
        |       |--(2)---- update_add_ptlc ---->|       |
        |       |--(3)- require_musig2_nonces ->|       |
        |   A   |<-(4)- reply_nonces_and_sign --|   B   |
        |       |--(5)--- commitment_signed --->|       |
        |       |<-(6)---- revoke_and_ack ------|       |
        |       |                               |       |
        |       |<-(7)---- update_add_htlc -----|       |
        |       |<-(8)-- require_musig2_nonces -|       |
        |       |--(9)-- reply_nonces_and_sign->|       |
        |       |<-(10)-- commitment_signed ----|       |
        |       |--(11)--- revoke_and_ack ----->|       |
        +-------+                               +-------+

To follow other basic designs of channels, like the max number and amount of payments being held at the same time, we also have to modify other messages.

As the scripts of offered/received PTLCs, we use MuSig2 aggregated public keys as the internal keys for both of them, and insert the revocation condition and the remote-redeem condition into the taproot script tree. The main consideration is privacy, which means ensuring the outputs of commitment transactions to be spent by key spend path as much as possible in this case.

As the implement of signature adaptors, we follow this document4, which means, both of peers have to sign with the sum of aggregated nonce and adaptor (R' = R + T ) as their signature nonce.

As the document details, in the multi-hop payments via PTLCs, the payment senders send a different scalar to each node in the route, which makes their upstream point lock solvable after getting a secret (scalar) from their downstream node. The payees (the last node) will solve their point lock by their knowledge of a secret (wanted by the payer) and the received scalar. Along the route, every point lock for every hop is different.

More details for this proposal see below (divided by the BOLTs been affected).

BOLT9

Make option_ptlc a feature, explicitly dependent on option_simplified_update. The feature should presented in the init message and node_announcement message.

Rationale

option_simplified_update builds an easy environment for requiring nonces.

BOLT2

Here is the new messages:

require_musig2_nonces

  1. type: ??? (require_musig2_nonces)
  2. data:
    • [channel_id:channel_id]
    • [u64:num_musig2_public_nonces]
    • [num_musig2_public_nonces*nonce:musig2_public_nonce]

Requirements

A sending node:

  • if in the other node's turns:
    • MUST NOT send this message after the other node have sent any messages about update and signing, including updates, commitment_signed, require_musig2_nonces and commitment_signed_ep(see below)
  • otherwise:
    • MUST include one public nonces for every PTLC output
    • MUST NOT send this message twice or more in the same turn
    • MUST remember relevant nonces (secret nonces and public nonces) with the order of them in the message, for the signing and verification of this turn
    • expect to be replied with reply_nonces_and_sign. If not, MUST send a warning and close the connection, or send an error and fail the channel.

A receiving node:

  • if in its turns:
    • if it has sent any messages about update and signing:
      • MUST ignore this message
    • otherwise:
      • MUST reply with yield and process this message
  • otherwise:
    • if it is the second time receiving this message in the same turn:
      • MUST send a warning and close the connection, or send an error and fail the channel.
    • otherwise:
      • if the number of public nonces not equal to the number of PTLC outputs in the local commitment transaction:
        • MUST send a warning and close the connection, or send an error and fail the channel.
      • otherwise:
        • if there is any invalid Musig2 public nonce in these public nonces:
          • MUST send a warning and close the connection, or send an error and fail the channel.
        • otherwise:
          • MUST remember these public nonces in order until they are aggregated with local public nonces
          • Reply with a reply_nonces_and_sign

reply_nonces_and_sign

  1. type: ??? (reply_nonces_and_sign)
  2. data:
    • [channel_id:channel_id]
    • [u64:num_musig2_public_nonces]
    • [num_musig2_public_nonces*nonce:musig2_public_nonces]
    • [u64:num_ptlcs]
    • [num_ptlcs*signature:signature]

Requirements:

A sending node:

  • MUST NOT send this message before received a require_musig2_nonces
  • MUST NOT send this message twice in the same turn
  • MUST include the same number of musig2 public nonces with received require_musig2_nonces
  • MUST remember these nonces (secret nonces and public nonces) until confirm they are useless (e.g. they aren't use in time, the commitment is revoked)
  • MUST aggregated these public nonces with nonces in received require_musig2_nonces one by one and save these aggregated nonces to verify future signatures of PTLC outputs ordered in the commitment transaction (see BOLT #3)
  • MUST included one (partial) signature for every PTLC outputs being held in local commitment transaction, the order must match
    • if the PTLC output is a 'received PTLC output', use aggregated public nonces in corresponding location, tweaked by the adaptor, sign a partial signature on the PTLC-success transaction
    • if the PTLC output is a 'offered PTLC output', use a fresh nonce instead of any aggregated public nonce, tweaked by the adaptor, sign a signature on the point lock path of the PTLC output
      • (I didn't specify the transactions being signed here when I write this post. Finally I realized it is the Claim-PTLC-success tx in t-bast's article3.)

A receiving node:

  • if:
    • receive this message before sent a require_musig2_nonces, OR
    • receive this message second time in the same turn, OR
    • the number of public nonces is not equal to the required number, OR
    • the number of (partial) signatures is not equal to the desired number, OR
    • there is any invalid musig2 public nonces included,
      • MUST send a warning and close the connection, or send an error and fail the channel.
  • otherwise:
    • MUST generate aggregated nonces with sent public nonces earlier and received public nonces just now
    • MUST base on the order and the types of PTLC outputs in the peers' commitment transaction, verify partial signatures in the message
      • if the PLTC output is a 'received PTLC output', use the aggregated nonce in corresponding location and the adaptor to verify it
      • if the PTLC output is a 'offered PTLC output', just verify it
        • if there is any invalid signature, MUST send a warning and close the connection, or send an error and fail the channel.
        • otherwise, must used these aggregated nonces in order in the PTLC signing of this turn

For example:

Now is Alice's turn. And Bob's local commitment has three PTLC outputs (PTLC1, PTLC2, PTLC3) in order.
PTLC1 and PTLC2 are received PTLC outputs. PTLC3 is a offered PTLC output.

When Alice sent `require_musig2_public_nonces`, must include (RA1, RA2, RA3) three nonces.
When Bob received it, must generate three nonces (RB1, RB2, RB3) to be included in `reply_nonces_and_sign`.

In addition, Bob must generate aggregated nonces for (RA1, RB1), (RA2, RB2), then generate partial signatures for PTLC1, PTLC2 correspondingly.

As PTLC1 and PTLC2, Bob signs on the PTLC-success transaction, tweak by the corrsponding adaptor.
As PTLC3, Bob signs a signature with the adaptor and his ptlc public key. (Note, he don't use (RA3, RB3).)
Bob also includes these two partial signatures and one signature in `reply_nonces_and_sign` and send it to Alice.

After generate aggregated nonces, Bob can delete (RA1, RA2, RA3), just keep aggregated nonces.

When Alice received `reply_nonces_and_sign`, she verify the number and validity of nonces, then verify the number and validity of signatures based on the number and the requirements of PTLC outputs.

If pass, Alice generate aggregated nonces for (RA1, RB1), (RA2, RB2), (RA3, RB3), then generate partial signatures for PTLC1 (PTLC-success), PTLC2 (PTLC-success), PTLC3 (PTLC-timeout) correspondingly.

Then, Alice can delete RA1(rA1), RA2(rA2) and RA3(rA3).

Rationale

Here we propose a explicit way to require and response MuSig2 public nonces with order for PTLC outputs/transactions, which is certainly beneficial from option_simple_update. As require_musig2_public_nonces is the prepare for commitment signing when the commitment contains PTLC outputs, we have to allow it to be sent at anytime.

update_add_ptlc

  1. type:???(update_add_ptlc)
  2. data:
    • [channel_id:channel_id]
    • [u64:id]
    • [u64:amount_msat]
    • [point:signature_adaptor]
    • [u32:cltv_expiry]
    • [1366*byte:onion_routing_packet]
  3. tlv_stream:update_add_ptlc_tlvs
  4. types:
    1. type: 0 (blind_path)
    2. data:
      • [point:path_key]

Rationale

The main difference with update_add_htlc is that it includes signature_adaptor instead of payment_hash. The general considerations about forwarding, cltv_expiry_delta and trimmed output remain the same.

Other requirements about channel reserve, feerate_per_kw, 'fee spike buffer' and id, max in-flight number/amount also remain the same with HTLCs.

Note that, we propose use the identical dust_limit, max in-flight number, max in-flight amount for both HTLCs/PTLCs.

update_fulfill_ptlc, update_fail_ptlc and update_fail_malformed_ptlc

The general considerations remain the same with their HTLC equivalent. Note that the content of onion payload is slightly different with that in HTLC payments.

open_channel_ep and accept_channel_ep

'ep' is for 'enhanced payment/pricacy' : )

See open_channel_ep as an example:

  1. type:???(onen_channel_ep)

  2. data:

    • [chain_hash:chain_hash]

    • [32*byte:temporary_channel_id]

    • [u64:funding_satoshis]

    • [u64:push_msat]

    • [u64:dust_limit_satoshis]

    • [u64:max_value_in_flight_payment_msat]

    • [u64:channel_reserve_satoshis]

    • [u64:in_flight_payment_minimum_msat]

    • [u32:feerate_per_kw]

    • [u16:to_self_delay]

    • [u16:max_accepted_in_flight_payment]

    • [point:funding_pubkey]

    • [point:revocation_basepoint]

    • [point:payment_basepoint]

    • [point:delayed_payment_basepoint]

    • [point:htlc_basepoint]

    • [point:ptlc_basepoint]

    • [point:first_per_commitment_point]

    • [byte:channel_flags]

    • [open_channel_tlvs:tlvs]

  3. tlv_stream: open_channel_tlvs

  4. types:

    1. type: 0 (upfront_shutdown_script)
    2. data:
      • [...*byte:shutdown_scriptpubkey]
    3. type: 1 (channel_type)
    4. data:
      • [...*byte:type]

Rationale

Modify from original open_channel:

  • the max_htlc_value_in_flight_msat to be max_value_in_flight_payment_msat,
  • the htlc_minimum_msat to be in_flight_payment_minimum_msat,
  • the max_accepted_htlcs to be max_accepted_in_flight_payment,

and make them unique values to constrain both HTLCs/PTLCs.

Add a new field ptlc_basepoint for PTLCs.

Beside the requirements from opech_channel, we additionally require the opening node MUST NOT send this message if it have not negotiated option_ptlc.

Here we just use 'channel establish v1' protocol as an example. It is easy to produce similar variant for 'channel establishment v2' protocol. Or, we can make option_ptlc explicitly dependence on 'channel establishment v2', to save addition into BOLTs.

commitment_signed_ep and revoke_and_ack_ep

Also modified from commitment_signed.

Here is commitment_signed_ep(requirements simplified):

  1. type: ??? (commitment_signed_ep)
  2. data:
    • [channle_id:channle_id]
    • [signature:signature]
    • [u16:num_htlcs_and_ptlcs]
    • [num_htlcs_and_ptlc*signature:htlc_or_ptlc_signature]

Requirements

A sending node:

  • MUST NOT send a commitment_signed_ep message that does not include any updates
  • MUST include one valid htlc_or_ptlc_signature for every HTLC/PTLC corresponding to the ordering of the commitment (see BOLT #3).
    • When generating MuSig2 partial signatures for PTLC outputs, MUST use aggregated nonces determined by require_musig2_public_nonces and reply_nonces_and_sign earlier in this turn, meanwhile the using order is also determined by them.
  • expect to be replied with rovoce_and_ack_ep.

A receiving node:

  • if the number of signatures not equal to the number of HTLC outputs and PTLC outputs in the commitment transaction OR there is any invalid signature of corresponding HTLC/PTLC:

    • MUST send a warning and close the connection, or send an error and fail the channel.
    • When verifying MuSig2 partial signatures for PTLC outputs, MUST use aggregated nonces determined by by require_musig2_public_nonces and reply_nonces_and_sign earlier in this turn, meanwhile the using order is also determined by them.
  • otherwise:

    • MUST reply a rovoce_and_ack_ep.

Here is recoke_and_ack_ep(requirements omitted):

  1. type: ??? (commitment_signed_ep)
  2. data:
    • [channle_id:channle_id]
    • [32*byte:per_commitment_secret]
    • [point:next_per_commitment_point]

BOLT3

received PTLC output

The receiving node of update_add_ptlc will get a corresponding 'received PTLC output' in its latest commitment transaction. The output means, either the receiving node get its value by shown a certain scalar to the peer, or the peer withdraw its value after a time window. The constructure of these taproot output is like:

1. Musig2 Aggregated Public Key(remote_ptlc_key, local_ptlc_key) as the internal key (the point lock condition) 
2. remote_ptlc_key plus OP_CLTV time lock (the remote withdrawal condition)
2. revocation key as the second script spend path (the penalty conditon)

The internal key is used to enable point locks. After the local node reply the relevant nonce and the partial signature (tweaked by the adaptor) of the PTLC-success transaction, the remote node (the sender of update_add_ptlc) is expected to provide partial signature tweaked by the adaptor of the PTLC-success transaction in commitment_signed_ep. The requirement to the local node is to ensure that the remote node is always able to extract the secret scalar it want, no matter the local node show it in update_fulfill_ptlc or put the MuSig2 aggregated signature on chain. Detailed explain see below 'A note about combining MuSig2 with signature adaptors'.

We don't sort ptlc_keys before aggregate them.

We use OP_CLTV to execute a time lock explicitly, to save interactive need brought by scriptless multi signatures, which is really complicated.

PTLC-success transactions

It is the PTLC transactions that the offers have to signed. Details:

  • version: 3

  • locktime: 0

  • txin count: 1

    • txin[0] outpoint: txid of the commitment transaction and output_index of the matching PTLC output for the PTLC success transaction
    • txin[0] sequence: 0 (set to 1 for option_anchors)
    • txin[0] script bytes: 0
    • txin[0] witness stack: <signature for the taproot public key>
  • txout count: 2

    • txout[0] amount: the PTLC amount_msat divided by 1000 (rounding down)

    • txout[0] script: version-1 P2TR with as shown below

      1. revocation key as the internal key
      2. Local delayed public key plus OP_CSV time lock as the script spend path
      
    • txout[1] amount: 0

    • txout[1] script: keyless P2A

Note that the PTLC-success transaction is zero-fee, but it can not use SIGHASH_SINGLE|SIGHASH_ANYONECANPAY to enable a fee bump way and a sweep way, as the MuSig2 don't allow. Execute PTLC in script-based multi-signatures can solving this problem (while other transactions based on MuSig2 remain).

So, we propose using TRUC transactions and a keyless PayToAnchor output to bump fee.

offered PTLC output

The sending node of update_add_ptlc will get a corresponding 'offered PTLC output` in its latest commitment transaction. The constructure of these taproot output is like:

1. Musig2 Aggregated Public Key(remote_ptlc_key, local_ptlc_key) as the internal key (the local withdrawal condition) 
2. remote_ptlc_key plus locl_ptlc_key as 2-of-2 multi-sig (the point lock condition)
2. revocation key as the second script spend path (the penalty conditon)

When holding offered PTLC outputs, the local node should, after received require_musig2_nonces, send corresponding signatures tweaked by the adaptor based on the point lock condition, and the remote node is expected to send partial signatures on PTLC-timeout transactions based on the local withdrawal condition. The PTLC-timeout transactions us nLocktime field to execute time lock, i.e. the local (PTLC offeror) withdrawal condition.

PTLC-timeout transactions

It is the PTLC transactions that the receivers have to signed. Details:

  • version: 3

  • locktime: cltv_expiry

  • txin count: 1

    • txin[0] outpoint: txid of the commitment transaction and output_index of the matching PTLC output for the PTLC success transaction
    • txin[0] sequence: 0 (set to 1 for option_anchors)
    • txin[0] script bytes: 0
    • txin[0] witness stack: <signature for the taproot public key>
  • txout count: 2

    • txout[0] amount: the PTLC amount_msat divided by 1000 (rounding down)

    • txout[0] script: version-1 P2TR with as shown below

      1. revocation key as the internal key
      2. Local delayed public key plus OP_CSV time lock as the script spend path
      
    • txout[1] amount: 0

    • txout[1] script: keyless P2A

Just like PTLC-success transactions, as we use MuSig2, both signers must be consistent in message (sighash) being signed, we can't use SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, which is mempool-unsecure if both signers use it.

Note

What make SIGHASH_SINGLE|SIGHASH_ANYONECANPAY special is that, it make the input-output pair composable, enabling sweep. In practice, it means you can compose (sweep) several (zero-fee) HTLC transactions into one transaction (and use SIGHASH_ALL in local signatures to protect it) and bump fee for all of them with (just) one additional input.

This feature is really useful, as it can simplify implement for fee payment while reducing the burden of keeping additional UTXO in users' wallet as much as possible. Think about this, you may have many many HTLC outputs in a commitment transaction, the commitment may be sent to the chain as need, and all of these HTLC transaction are time-sensitive. CPFP-based methods (including TRUC) is not good at this case.

Thus, the fee payment of PTLC transactions here, is far from optimal. And the root is the scriptless multi signature.

I am seriously thinking whether we should use script-based multi-sig to enforce the point lock for received PTLC outputs and the time lock for offered PTLC outputs, to go back to SIGHASH_SINGLE|SIGHASH_ANYONECANPAY currently using in HTLC transactions of anchor channels.

BOLT4

When constructing multi-hop payments via PTLCs, the payment sender have to generate scalars y_0, y_1 ... y_(n-1) at random (n is the number of hops) , then send y_(m+1) to the m th node (except for the payee, i.e. the last node) and require them to offer their downstream node a PTLC conditional on knowledge of z + y_0 + y_1 + ... + y_(m+1)(the sum of z and all of generated scalars sent) (z.G is a point from the payee, just like the payment hash) (the sender also offer a PTLC conditional on knowledge of z + y_0 to its peer, i.e. the 0 th node). The scalar sending to the payee is y_0 + y_1 + ... + y_(n-1), i.e. the sum of all generated scalars, when combining with its z, is able to solve its upstream point lock and trigger the settlement stage of the multi-hop payment.

Here, the payment sender must encode a scalar and the information of PTLC offering to downstream node (if existed) into the payload for every node in the route. And every node, when receives a PTLC onion, must verify the difference value between point lock from the upstream node and that to the downstream node is a point generated by received scalar, to ensure it is able to get payment.

This proposal is also compatible with blind_path.

A note about combining MuSig2 with signature adaptors

Say Alice offers a PTLC to Bob.

We set Alice as the first signer, Bob as the second signer. Which means, the internal key for the PTLC taproot output is $P = Musig2keyagg(P_A, P_B)$, in which:

$$ \begin{align} L = Hash(P_A||P_B) \\\ c_{signer} = Hash(L||P_{signer}) \\\ P = c_a*P_A + c_b * P_B \end{align} $$

(As BIP0327 said, the order of individual public keys will affect the aggregated public key.)

The public nonces from Alice and Bob correspondingly: $(R_1', R_1''), (R_2', R_2'')$.

The aggregated nonce (aggnonce in BIP0327) is calculated like this: $R' = R_1' + R_2' ; R'' = R_1'' + R_2''$.

Assume Alice tweak Musig2 final nonce $R$ with the adaptor $T$, then sign the PTLC-success transactions, the computation is like this14:

$$ \begin{align} b = Hash(P||R' + T||R''||m) \\\ R_A = R_1' + b * R_1'' \\\ R_B = R_2' + b * R_2'' \\\ R = R_A + R_B \\\ --- \\\ r_a = r_1' + b * r_1'' \\\ --- \\\ RT = R + T \\\ st = r_a + Hash(RT||P||m) * c_a * p_a\\\ \end{align} $$

In which, $p_a$ is the private key of Alice. The partial signature with adaptor that Alice shows to Bob is $(RT, st, T)$ .

As you can see, given public nonces of Alice and Bob, the signing secret nonces for them are certain (independent of their location as signers).

When Bob finally get $t$ the underlying scalar of $T$, he is able to get a valid partial signature of Alice, and aggregate it with his own partial signature, like this:

$$ \begin{align} s_a = st + t = r_a + t + Hash(RT||P||m) * c_a * p_a \\\ s_b = r_b + Hash(RT||P||m) * c_b * p_b \\\ s = s_a + s_b \\\ s = r_a + r_b + t + Hash(RT||P||m) * [c_a * p_a + c_b * p_b] \\\ --verificaion-- \\\ s.G = R_a + R_b + T + Hash(R_a + R_b + T||P||m) * P \end{align} $$

As you can see, if Bob put the signature $(RT, s)$ on chain, and Alice don't have any other information, she would be unable to extract $t$, as $t = s - st - s_b$ , the $s_b$ is also unknown to her.

That's why we require Bob to provide his partial signatures at first. Otherwise, the adaptor would be a unilateral option, rather than a payment contract.

Footnotes

  1. https://gist.github.com/instagibbs/1d02d0251640c250ceea1c66665ec163#musig2-based-adaptor-signatures-sync-updates

  2. https://github.com/t-bast/lightning-docs/blob/master/schnorr.md#musig2-adaptor-signatures

  3. https://github.com/t-bast/lightning-docs/blob/master/taproot-updates.md#point-time-locked-contracts 2

  4. https://github.com/BlockstreamResearch/scriptless-scripts/blob/master/md/multi-hop-locks.md 2 3

  5. https://suredbits.com/payment-points-part-1/ 2

  6. https://suredbits.com/payment-points-part-4-selling-signatures/

  7. https://github.com/AdamISZ/AdaptorSecurityDoc

  8. https://link.springer.com/chapter/10.1007/978-3-031-58723-8_6

  9. https://delvingbitcoin.org/t/ln-summit-2024-notes-summary-commentary/1198

  10. https://github.com/lightning/bolts/pull/867 2

  11. https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki

  12. https://github.com/lightning/bolts/pull/995/files

  13. https://github.com/t-bast/lightning-docs/blob/master/taproot-updates.md#musig2-channel-funding

  14. https://github.com/BlockstreamResearch/scriptless-scripts/blob/a8b6ff21fc7f4529eabbe639fbff49f047a3579d/md/musig2-adaptorsig.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment