From 2f4f0aa7660d5f72d0bcbdea7fa3ab3f9ff35d2d Mon Sep 17 00:00:00 2001 From: Antoine Riard Date: Tue, 5 May 2020 20:00:01 -0400 Subject: [PATCH] Extend KeysInterface with derive_channel_keys 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 | 1 + fuzz/src/full_stack.rs | 2 + lightning/src/chain/keysinterface.rs | 80 ++++++++++++++------- lightning/src/ln/channel.rs | 1 + lightning/src/ln/channelmonitor.rs | 1 + lightning/src/util/enforcing_trait_impls.rs | 1 + lightning/src/util/test_utils.rs | 3 + 7 files changed, 63 insertions(+), 26 deletions(-) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index e9f56a3e4..b5fc307a7 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -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), )) } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 2af173cfc..d6ea085a7 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -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), ) }) } diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 7b71784ca..fea11367e 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -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, /// 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(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { 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(); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index c9eebf41e..b0f8ef9ab 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -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()[..], diff --git a/lightning/src/ln/channelmonitor.rs b/lightning/src/ln/channelmonitor.rs index 8a05ed4fb..5ccde163c 100644 --- a/lightning/src/ln/channelmonitor.rs +++ b/lightning/src/ln/channelmonitor.rs @@ -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 diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index dacec1a93..5c5b70443 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -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(&self, feerate_per_kw: u64, commitment_tx: &Transaction, keys: &TxCreationKeys, htlcs: &[&HTLCOutputInCommitment], to_self_delay: u16, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { if commitment_tx.input.len() != 1 { panic!("lightning commitment transactions have a single input"); } diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 0dddd4996..0e9c20252 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -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 { -- 2.39.5