Extend KeysInterface with derive_channel_keys
authorAntoine Riard <ariard@student.42.fr>
Wed, 6 May 2020 00:00:01 +0000 (20:00 -0400)
committerAntoine Riard <ariard@student.42.fr>
Thu, 28 May 2020 08:21:46 +0000 (04:21 -0400)
A dynamic-p2wsh-output like `to_local` on local commitment/HTLC txn
require a signature from delayed_payment_key to be spend. Instead of
sending private key in descriptor, we ask for spender to derive again
the corresponding ChannelKeys based on key state, uniquely identifying
a channel and encompassing its unique start data.

Descriptor modification is done in next commit.

fuzz/src/chanmon_consistency.rs
fuzz/src/full_stack.rs
lightning/src/chain/keysinterface.rs
lightning/src/ln/channel.rs
lightning/src/ln/channelmonitor.rs
lightning/src/util/enforcing_trait_impls.rs
lightning/src/util/test_utils.rs

index e9f56a3e4605426881b442e580ef04ef8c5f9b97..b5fc307a713a39e93aa5a6ba14cf0a37cc33fefb 100644 (file)
@@ -164,6 +164,7 @@ impl KeysInterface for KeyProvider {
                        SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, self.node_id]).unwrap(),
                        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, self.node_id],
                        channel_value_satoshis,
+                       (0, 0),
                ))
        }
 
index 2af173cfcb4374ff3bdc9ffc6c9f69a900dbc1c8..d6ea085a782442a2df3e4dfa36a78c9220bec0a8 100644 (file)
@@ -262,6 +262,7 @@ impl KeysInterface for KeyProvider {
                                SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, ctr]).unwrap(),
                                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, ctr],
                                channel_value_satoshis,
+                               (0, 0),
                        )
                } else {
                        InMemoryChannelKeys::new(
@@ -273,6 +274,7 @@ impl KeysInterface for KeyProvider {
                                SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, ctr]).unwrap(),
                                [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, ctr],
                                channel_value_satoshis,
+                               (0, 0),
                        )
                })
        }
index 7b71784ca2736f482f9189dea9d42b8f9f1811f8..fea11367eca56cb4fc6732ed1aa8c497b1e5b621 100644 (file)
@@ -207,6 +207,10 @@ pub trait ChannelKeys : Send+Clone {
        fn commitment_seed<'a>(&'a self) -> &'a [u8; 32];
        /// Gets the local channel public keys and basepoints
        fn pubkeys<'a>(&'a self) -> &'a ChannelPublicKeys;
+       /// Gets arbitrary identifiers describing the set of keys which are provided back to you in
+       /// some SpendableOutputDescriptor types. These should be sufficient to identify this
+       /// ChannelKeys object uniquely and lookup or re-derive its keys.
+       fn key_derivation_params(&self) -> (u64, u64);
 
        /// Create a signature for a remote commitment transaction and associated HTLC transactions.
        ///
@@ -329,6 +333,8 @@ pub struct InMemoryChannelKeys {
        pub(crate) remote_channel_pubkeys: Option<ChannelPublicKeys>,
        /// The total value of this channel
        channel_value_satoshis: u64,
+       /// Key derivation parameters
+       key_derivation_params: (u64, u64),
 }
 
 impl InMemoryChannelKeys {
@@ -341,7 +347,8 @@ impl InMemoryChannelKeys {
                delayed_payment_base_key: SecretKey,
                htlc_base_key: SecretKey,
                commitment_seed: [u8; 32],
-               channel_value_satoshis: u64) -> InMemoryChannelKeys {
+               channel_value_satoshis: u64,
+               key_derivation_params: (u64, u64)) -> InMemoryChannelKeys {
                let local_channel_pubkeys =
                        InMemoryChannelKeys::make_local_keys(secp_ctx, &funding_key, &revocation_base_key,
                                                             &payment_key, &delayed_payment_base_key,
@@ -356,6 +363,7 @@ impl InMemoryChannelKeys {
                        channel_value_satoshis,
                        local_channel_pubkeys,
                        remote_channel_pubkeys: None,
+                       key_derivation_params,
                }
        }
 
@@ -384,6 +392,7 @@ impl ChannelKeys for InMemoryChannelKeys {
        fn htlc_base_key(&self) -> &SecretKey { &self.htlc_base_key }
        fn commitment_seed(&self) -> &[u8; 32] { &self.commitment_seed }
        fn pubkeys<'a>(&'a self) -> &'a ChannelPublicKeys { &self.local_channel_pubkeys }
+       fn key_derivation_params(&self) -> (u64, u64) { self.key_derivation_params }
 
        fn sign_remote_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
                if commitment_tx.input.len() != 1 { return Err(()); }
@@ -488,6 +497,8 @@ impl Writeable for InMemoryChannelKeys {
                self.commitment_seed.write(writer)?;
                self.remote_channel_pubkeys.write(writer)?;
                self.channel_value_satoshis.write(writer)?;
+               self.key_derivation_params.0.write(writer)?;
+               self.key_derivation_params.1.write(writer)?;
 
                Ok(())
        }
@@ -508,6 +519,8 @@ impl Readable for InMemoryChannelKeys {
                        InMemoryChannelKeys::make_local_keys(&secp_ctx, &funding_key, &revocation_base_key,
                                                             &payment_key, &delayed_payment_base_key,
                                                             &htlc_base_key);
+               let params_1 = Readable::read(reader)?;
+               let params_2 = Readable::read(reader)?;
 
                Ok(InMemoryChannelKeys {
                        funding_key,
@@ -518,7 +531,8 @@ impl Readable for InMemoryChannelKeys {
                        commitment_seed,
                        channel_value_satoshis,
                        local_channel_pubkeys,
-                       remote_channel_pubkeys
+                       remote_channel_pubkeys,
+                       key_derivation_params: (params_1, params_2),
                })
        }
 }
@@ -616,34 +630,25 @@ impl KeysManager {
                unique_start.input(&self.seed);
                unique_start
        }
-}
-
-impl KeysInterface for KeysManager {
-       type ChanKeySigner = InMemoryChannelKeys;
-
-       fn get_node_secret(&self) -> SecretKey {
-               self.node_secret.clone()
-       }
-
-       fn get_destination_script(&self) -> Script {
-               self.destination_script.clone()
-       }
-
-       fn get_shutdown_pubkey(&self) -> PublicKey {
-               self.shutdown_pubkey.clone()
-       }
+       /// Derive an old set of ChannelKeys for per-channel secrets based on a key derivation
+       /// parameters.
+       /// Key derivation parameters are accessible through a per-channel secrets
+       /// ChannelKeys::key_derivation_params and is provided inside DynamicOuputP2WSH in case of
+       /// onchain output detection for which a corresponding delayed_payment_key must be derived.
+       pub fn derive_channel_keys(&self, channel_value_satoshis: u64, params_1: u64, params_2: u64) -> InMemoryChannelKeys {
+               let chan_id = ((params_1 & 0xFFFF_FFFF_0000_0000) >> 32) as u32;
+               let mut unique_start = Sha256::engine();
+               unique_start.input(&byte_utils::be64_to_array(params_2));
+               unique_start.input(&byte_utils::be32_to_array(params_1 as u32));
+               unique_start.input(&self.seed);
 
-       fn get_channel_keys(&self, _inbound: bool, channel_value_satoshis: u64) -> InMemoryChannelKeys {
                // We only seriously intend to rely on the channel_master_key for true secure
                // entropy, everything else just ensures uniqueness. We rely on the unique_start (ie
                // starting_time provided in the constructor) to be unique.
-               let mut sha = self.derive_unique_start();
-
-               let child_ix = self.channel_child_index.fetch_add(1, Ordering::AcqRel);
-               let child_privkey = self.channel_master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx(child_ix as u32).expect("key space exhausted")).expect("Your RNG is busted");
-               sha.input(&child_privkey.private_key.key[..]);
+               let child_privkey = self.channel_master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx(chan_id).expect("key space exhausted")).expect("Your RNG is busted");
+               unique_start.input(&child_privkey.private_key.key[..]);
 
-               let seed = Sha256::from_engine(sha).into_inner();
+               let seed = Sha256::from_engine(unique_start).into_inner();
 
                let commitment_seed = {
                        let mut sha = Sha256::engine();
@@ -674,9 +679,32 @@ impl KeysInterface for KeysManager {
                        delayed_payment_base_key,
                        htlc_base_key,
                        commitment_seed,
-                       channel_value_satoshis
+                       channel_value_satoshis,
+                       (params_1, params_2),
                )
        }
+}
+
+impl KeysInterface for KeysManager {
+       type ChanKeySigner = InMemoryChannelKeys;
+
+       fn get_node_secret(&self) -> SecretKey {
+               self.node_secret.clone()
+       }
+
+       fn get_destination_script(&self) -> Script {
+               self.destination_script.clone()
+       }
+
+       fn get_shutdown_pubkey(&self) -> PublicKey {
+               self.shutdown_pubkey.clone()
+       }
+
+       fn get_channel_keys(&self, _inbound: bool, channel_value_satoshis: u64) -> InMemoryChannelKeys {
+               let child_ix = self.channel_child_index.fetch_add(1, Ordering::AcqRel);
+               let ix_and_nanos: u64 = (child_ix as u64) << 32 | (self.starting_time_nanos as u64);
+               self.derive_channel_keys(channel_value_satoshis, ix_and_nanos, self.starting_time_secs)
+       }
 
        fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
                let mut sha = self.derive_unique_start();
index c9eebf41eb4706b3a7d39c10600c92e7c8e8949e..b0f8ef9abee0d60441aaf80563098cf43f880bd9 100644 (file)
@@ -4439,6 +4439,7 @@ mod tests {
                        // These aren't set in the test vectors:
                        [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
                        10_000_000,
+                       (0, 0)
                );
 
                assert_eq!(PublicKey::from_secret_key(&secp_ctx, chan_keys.funding_key()).serialize()[..],
index 8a05ed4fb8c107fd64ae1d95e12f627800e7503c..5ccde163ce351c19b0acbf2363ec29742ca3d911 100644 (file)
@@ -2539,6 +2539,7 @@ mod tests {
                        SecretKey::from_slice(&[41; 32]).unwrap(),
                        [41; 32],
                        0,
+                       (0, 0)
                );
 
                // Prune with one old state and a local commitment tx holding a few overlaps with the
index dacec1a936adcc9e33c711dc5977f2f96c1b5a4f..5c5b7044334a9a44322a75c588395726242eacc6 100644 (file)
@@ -60,6 +60,7 @@ impl ChannelKeys for EnforcingChannelKeys {
        fn htlc_base_key(&self) -> &SecretKey { self.inner.htlc_base_key() }
        fn commitment_seed(&self) -> &[u8; 32] { self.inner.commitment_seed() }
        fn pubkeys<'a>(&'a self) -> &'a ChannelPublicKeys { self.inner.pubkeys() }
+       fn key_derivation_params(&self) -> (u64, u64) { self.inner.key_derivation_params() }
 
        fn sign_remote_commitment<T: secp256k1::Signing + secp256k1::Verification>(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1<T>) -> Result<(Signature, Vec<Signature>), ()> {
                if commitment_tx.input.len() != 1 { panic!("lightning commitment transactions have a single input"); }
index 0dddd499670f7723b9c13d998e7e43b6379707fd..0e9c20252cca9a1147960fc0866edf1f3a2d5d37 100644 (file)
@@ -283,6 +283,9 @@ impl TestKeysInterface {
                        override_channel_id_priv: Mutex::new(None),
                }
        }
+       pub fn derive_channel_keys(&self, channel_value_satoshis: u64, user_id_1: u64, user_id_2: u64) -> EnforcingChannelKeys {
+               EnforcingChannelKeys::new(self.backing.derive_channel_keys(channel_value_satoshis, user_id_1, user_id_2))
+       }
 }
 
 pub struct TestChainWatcher {