[Lightning-dev] Recovery of Lightning channels without backups

Rusty Russell rusty at rustcorp.com.au
Tue Apr 20 04:52:01 UTC 2021


Lloyd Fournier <lloyd.fourn at gmail.com> writes:
> Hi Rusty,
>
> On Tue, 20 Apr 2021 at 10:55, Rusty Russell <rusty at rustcorp.com.au> wrote:
>
>> Lloyd Fournier <lloyd.fourn at gmail.com> writes:
>> > On Wed, Dec 9, 2020 at 4:26 PM Rusty Russell <rusty at rustcorp.com.au>
>> wrote:
>> >
>> >>
>> >> Say r1=SHA256(ss || counter || 0), r2 = SHA256(ss || counter || 1)?
>> >>
>> >> Nice work.  This would be a definite recovery win.  We should add this
>> >> to the DF spec, because Lisa was almost finished implmenting it, so it's
>> >> clearly due for a change!
>> >>
>> >
>> > Yes that's certainly a fine way to do it.
>> > I was also thinking you could eliminate all "basepoints" (not just
>> funding
>> > pubkey) using something like this. i.e. just use the node pubkey as the
>> > "basepoint" for everything and randomize it using the shared secret for
>> > each purpose.
>>
>> OK, I tried to spec this out, to implement it.  One issue is that you
>> now can't sign the commitment_tx (or htlc_tx) without knowing the node's
>> secret key (or, equivalently, knowing the tweaked key and being able to
>> use the derivation scheme to untweak it).
>>
>
> Using node secret key to sign the commitment_tx seems like something you
> will have to accept to introduce this feature. For the idea to work it has
> to be some public key that is known by others and gossiped through the
> network. Of course you could extend the information that is gossiped about
> a node to include a "commit_tx_point" but the nodeid seems the more natural
> choice.

Duh, yes, of course you need the funding_key secret to sign the
commitment tx.

But you really don't want to access the `remote_pubkey` (which in a
modern option_static_remotekey world is simply the payment_basepoint).
It's generally considered good practice *not* to have this accessible to
your lightning node at all.

>> c-lightning currently does a round-trip to the signing daemon for this
>> already, but it'd be nice to avoid requiring it.
>>
>> So I somewhat reluctantly added `commit_basepoint` from which the others
>> are derived: an implementation can use some hardened derivation from its
>> privkey (e.g. SHA256(node_privkey || ss || counter)) to create
>> this in a deterministic but still private manner.
>>
>> Or we could just leave all the other points in and just replace
>> funding_pubkey.
>>
>
> Another approach is to do things in "soft-fork" like manner.
> Each node that wants to offer this feature sets their funding_pubkey to a
> specified DH tweak of the nodeid. Nodes that want backup-free channel
> recovery can just refuse to carry on the funding protocol if the
> funding_pubkey is not set the way it wanted.

Yeah, you can totally do this in an opt-in manner, except it doesn't
work unless your peer does it too.  Since we expect everyone to want to
do this, it's clearer to force everyone to calculate this and not have
redundant and confusing fields in the message.

>>From my pruisit crypto point of view having only one public key is nice but
> I'm not sure how it impacts things architecturally and other protocols like
> watchtowers.

They can operate exactly like the existing scheme, AFAICT.

Here's the spec diff (based on dual-funding, since it's easier to simply
hard change).  Please check my EC math! :)

diff --git a/02-peer-protocol.md b/02-peer-protocol.md
index fbc56c8..1114068 100644
--- a/02-peer-protocol.md
+++ b/02-peer-protocol.md
@@ -867,11 +867,9 @@ This message initiates the v2 channel establishment workflow.
    * [`u16`:`to_self_delay`]
    * [`u16`:`max_accepted_htlcs`]
    * [`u32`:`locktime`]
-   * [`point`:`funding_pubkey`]
+   * [`u64`:`generation`]
    * [`point`:`revocation_basepoint`]
    * [`point`:`payment_basepoint`]
-   * [`point`:`delayed_payment_basepoint`]
-   * [`point`:`htlc_basepoint`]
    * [`point`:`first_per_commitment_point`]
    * [`byte`:`channel_flags`]
    * [`opening_tlvs`:`tlvs`]
@@ -895,13 +893,16 @@ If nodes have negotiated `option_dual_fund`:
 
 The sending node:
   - MUST set `funding_feerate_perkw` to the feerate for this transaction
-  - MUST ensure `temporary_channel_id` is unique from any
-    other channel ID with the same peer.
+  - MUST set `generation` to a number greater than any previous
+    `generation` it has sent to this receiving node which has reached
+    `commitment_signed`.
+  - SHOULD set `generation` to the lowest number which meets this requirement.
 
 The receiving node:
   - MAY fail the negotiation if:
     - the `locktime` is unacceptable
     - the `funding_feerate_per_kw` is unacceptable
+    - the `generation` exceeds expectation by more than the maximum it would scan for recovery.
 
 #### Rationale
 `channel_id` for the `open_channel2` MUST be derived using a zero-d out
@@ -926,6 +927,13 @@ Instead, the channel reserve is fixed at 1% of the total channel balance
 rounded down to the nearest whole satoshi or the `dust_limit_satoshis`,
 whichever is greater.
 
+`generation` is a number which is used to generate the points used for
+this pair of peers, with the aim of allowing automatic onchain
+scanning for channels if all other information is lost.  Since this
+scan would presumably only try a limited number of generations, it is
+best if this number is low, but it also needs to change for each
+successive channel between the peers, to avoid obvious fingerprinting.
+
 Note that `push_msat` has been omitted.
 
 ### The `accept_channel2` Message
@@ -943,11 +951,9 @@ acceptance of the new channel.
     * [`u32`:`minimum_depth`]
     * [`u16`:`to_self_delay`]
     * [`u16`:`max_accepted_htlcs`]
-    * [`point`:`funding_pubkey`]
+    * [`u64`:`generation`]
     * [`point`:`revocation_basepoint`]
     * [`point`:`payment_basepoint`]
-    * [`point`:`delayed_payment_basepoint`]
-    * [`point`:`htlc_basepoint`]
     * [`point`:`first_per_commitment_point`]
     * [`accept_tlvs`:`tlvs`]
 
@@ -967,6 +973,10 @@ additions.
 
 The accepting node:
     - MAY respond with a `funding_satoshis` value of zero.
+    - MUST set `generation` to a number greater than any previous
+      `generation` it has sent to this receiving node which has reached
+      `commitment_signed`.
+    - SHOULD set `generation` to the lowest number which meets this requirement.
 
 #### Rationale
 
@@ -985,6 +995,31 @@ Funding composition for channel establishment v2 makes use of the
 [Interactive Transaction Construction](#interactive-transaction-construction)
 protocol, with the following additional caveats.
 
+#### Point Derivation
+
+The `funding_pubkey` and basepoints are derived from the two
+`node_id`s and the higher of the two `generation` values; the
+`payment_basepoint` is supplied directly.
+
+Derivation is done as follows:
+
+1. Start with two node ids, `N1` and `N2` (`N1` is the lesser of the
+   two SEC1-encoded compressed public keys, `N2` the greater).
+2. Derive a shared secret, `SS`, using ECDH on `N1` and `N2`.
+3. Define tweaks `T` for each peer, using `SHA256(SS || generation || node_id || name)`, where:
+   1. `generation` is the `u64` larger of the two `generation` fields from `open_channel2` and `accept_channel2`.
+   2. `node_id` is the SEC1-encoded compressed public key of the peer.
+   3. `name` is a non-terminated ASCII string, e.g. `htlc` is the four bytes
+   `0x68 0x74 0x6C 0x63`
+4. The `funding_pubkey` is defined as the `node_id` + G*T(`funding`).
+5. The `delayed_payment_basepoint` is defined as `node_id` + G*T(`delayed_payment`).
+6. The `htlc_basepoint` is defined as the `node_id` + G*T(`htlc`).
+
+If the secret for `payment_basepoint` is derived in a similar manner,
+it too can be easily recovered from just the `generation`, node key
+and peer `node_id`.  However, it may also point to an address for a
+completely separate system (e.g. cold storage), so it is specified
+explicitly in the protocol.
 
 #### The `tx_add_input` Message
 


More information about the Lightning-dev mailing list