diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 040ac6b3f2f..a6ba3b80747 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1359,8 +1359,9 @@ impl ChannelMonitor { ) -> ChannelMonitor { assert!(commitment_transaction_number_obscure_factor <= (1 << 48)); + let holder_pubkeys = &channel_parameters.holder_pubkeys; let counterparty_payment_script = chan_utils::get_counterparty_payment_script( - &channel_parameters.channel_type_features, &keys.pubkeys().payment_point + &channel_parameters.channel_type_features, &holder_pubkeys.payment_point ); let counterparty_channel_parameters = channel_parameters.counterparty_parameters.as_ref().unwrap(); @@ -1369,7 +1370,7 @@ impl ChannelMonitor { let counterparty_commitment_params = CounterpartyCommitmentParameters { counterparty_delayed_payment_base_key, counterparty_htlc_base_key, on_counterparty_tx_csv }; let channel_keys_id = keys.channel_keys_id(); - let holder_revocation_basepoint = keys.pubkeys().revocation_basepoint; + let holder_revocation_basepoint = holder_pubkeys.revocation_basepoint; // block for Rust 1.34 compat let (holder_commitment_tx, current_holder_commitment_number) = { @@ -5417,6 +5418,7 @@ mod tests { selected_contest_delay: 67, }), funding_outpoint: Some(funding_outpoint), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, }; @@ -5669,6 +5671,7 @@ mod tests { selected_contest_delay: 67, }), funding_outpoint: Some(funding_outpoint), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, }; diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index a52d0eaaf98..e0c2bd1b306 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1352,6 +1352,7 @@ mod tests { selected_contest_delay: 67, }), funding_outpoint: Some(funding_outpoint), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 0, }; diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 1aa370fec21..a07fd9d46a2 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -605,7 +605,20 @@ impl PackageSolvingData { let channel_parameters = &onchain_handler.channel_transaction_parameters; match self { PackageSolvingData::RevokedOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); + let directed_parameters = + &onchain_handler.channel_transaction_parameters.as_counterparty_broadcastable(); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint, + outp.counterparty_delayed_payment_base_key, + ); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().htlc_basepoint, + outp.counterparty_htlc_base_key, + ); + let chan_keys = TxCreationKeys::from_channel_static_keys( + &outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(), + directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx, + ); let witness_script = chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, outp.on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key); //TODO: should we panic on signer failure ? if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_output(channel_parameters, &bumped_tx, i, outp.amount.to_sat(), &outp.per_commitment_key, &onchain_handler.secp_ctx) { @@ -617,7 +630,20 @@ impl PackageSolvingData { } else { return false; } }, PackageSolvingData::RevokedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); + let directed_parameters = + &onchain_handler.channel_transaction_parameters.as_counterparty_broadcastable(); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint, + outp.counterparty_delayed_payment_base_key, + ); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().htlc_basepoint, + outp.counterparty_htlc_base_key, + ); + let chan_keys = TxCreationKeys::from_channel_static_keys( + &outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(), + directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx, + ); let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); //TODO: should we panic on signer failure ? if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_htlc(channel_parameters, &bumped_tx, i, outp.amount, &outp.per_commitment_key, &outp.htlc, &onchain_handler.secp_ctx) { @@ -629,7 +655,20 @@ impl PackageSolvingData { } else { return false; } }, PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); + let directed_parameters = + &onchain_handler.channel_transaction_parameters.as_counterparty_broadcastable(); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint, + outp.counterparty_delayed_payment_base_key, + ); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().htlc_basepoint, + outp.counterparty_htlc_base_key, + ); + let chan_keys = TxCreationKeys::from_channel_static_keys( + &outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(), + directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx, + ); let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(channel_parameters, &bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) { @@ -641,7 +680,20 @@ impl PackageSolvingData { } }, PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); + let directed_parameters = + &onchain_handler.channel_transaction_parameters.as_counterparty_broadcastable(); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().delayed_payment_basepoint, + outp.counterparty_delayed_payment_base_key, + ); + debug_assert_eq!( + directed_parameters.broadcaster_pubkeys().htlc_basepoint, + outp.counterparty_htlc_base_key, + ); + let chan_keys = TxCreationKeys::from_channel_static_keys( + &outp.per_commitment_point, directed_parameters.broadcaster_pubkeys(), + directed_parameters.countersignatory_pubkeys(), &onchain_handler.secp_ctx, + ); let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(channel_parameters, &bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) { diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index c72bd615f4a..da25e4e9aba 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -880,6 +880,17 @@ pub struct ChannelTransactionParameters { pub counterparty_parameters: Option, /// The late-bound funding outpoint pub funding_outpoint: Option, + /// The parent funding txid for a channel that has been spliced. + /// + /// If a channel was funded with transaction A, and later spliced with transaction B, this field + /// tracks the txid of transaction A. + /// + /// See [`compute_funding_key_tweak`] and [`ChannelSigner::pubkeys`] for more context on how + /// this may be used. + /// + /// [`compute_funding_key_tweak`]: crate::sign::compute_funding_key_tweak + /// [`ChannelSigner::pubkeys`]: crate::sign::ChannelSigner::pubkeys + pub splice_parent_funding_txid: Option, /// This channel's type, as negotiated during channel open. For old objects where this field /// wasn't serialized, it will default to static_remote_key at deserialization. pub channel_type_features: ChannelTypeFeatures, @@ -963,6 +974,7 @@ impl ChannelTransactionParameters { funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::from_byte_array([42; 32]), index: 0 }), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::empty(), channel_value_satoshis, } @@ -985,6 +997,7 @@ impl Writeable for ChannelTransactionParameters { (8, self.funding_outpoint, option), (10, legacy_deserialization_prevention_marker, option), (11, self.channel_type_features, required), + (12, self.splice_parent_funding_txid, option), (13, self.channel_value_satoshis, required), }); Ok(()) @@ -998,6 +1011,7 @@ impl ReadableArgs for ChannelTransactionParameters { let mut is_outbound_from_holder = RequiredWrapper(None); let mut counterparty_parameters = None; let mut funding_outpoint = None; + let mut splice_parent_funding_txid = None; let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; let mut channel_value_satoshis = None; @@ -1010,6 +1024,7 @@ impl ReadableArgs for ChannelTransactionParameters { (8, funding_outpoint, option), (10, _legacy_deserialization_prevention_marker, option), (11, channel_type_features, option), + (12, splice_parent_funding_txid, option), (13, channel_value_satoshis, option), }); @@ -1028,6 +1043,7 @@ impl ReadableArgs for ChannelTransactionParameters { is_outbound_from_holder: is_outbound_from_holder.0.unwrap(), counterparty_parameters, funding_outpoint, + splice_parent_funding_txid, channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()), channel_value_satoshis, }) @@ -1154,6 +1170,7 @@ impl HolderCommitmentTransaction { is_outbound_from_holder: false, counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }), funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis, }; @@ -1953,22 +1970,25 @@ mod tests { let keys_provider = test_utils::TestKeysInterface::new(&seed, network); let signer = keys_provider.derive_channel_signer(keys_provider.generate_channel_keys_id(false, 0)); let counterparty_signer = keys_provider.derive_channel_signer(keys_provider.generate_channel_keys_id(true, 1)); - let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint; let per_commitment_secret = SecretKey::from_slice(&>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); - let htlc_basepoint = &signer.pubkeys().htlc_basepoint; - let holder_pubkeys = signer.pubkeys(); - let counterparty_pubkeys = counterparty_signer.pubkeys().clone(); - let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint); + let holder_pubkeys = signer.pubkeys(None, &secp_ctx); + let counterparty_pubkeys = counterparty_signer.pubkeys(None, &secp_ctx).clone(); let channel_parameters = ChannelTransactionParameters { holder_pubkeys: holder_pubkeys.clone(), holder_selected_contest_delay: 0, is_outbound_from_holder: false, counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), + splice_parent_funding_txid: None, channel_type_features: ChannelTypeFeatures::only_static_remote_key(), channel_value_satoshis: 3000, }; + let directed_parameters = channel_parameters.as_holder_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &per_commitment_point, directed_parameters.broadcaster_pubkeys(), + directed_parameters.countersignatory_pubkeys(), &secp_ctx, + ); let htlcs_with_aux = Vec::new(); Self { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index e08a0731513..c88b2ce9650 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -2411,7 +2411,6 @@ impl ChannelContext where SP::Target: SignerProvider { let channel_keys_id = signer_provider.generate_channel_keys_id(true, user_id); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); if config.channel_handshake_config.our_to_self_delay < BREAKDOWN_TIMEOUT { return Err(ChannelError::close(format!("Configured with an unreasonable our_to_self_delay ({}) putting user funds at risks. It must be greater than {}", config.channel_handshake_config.our_to_self_delay, BREAKDOWN_TIMEOUT))); @@ -2571,6 +2570,8 @@ impl ChannelContext where SP::Target: SignerProvider { // TODO(dual_funding): Checks for `funding_feerate_sat_per_1000_weight`? + let pubkeys = holder_signer.pubkeys(None, &secp_ctx); + let funding = FundingScope { value_to_self_msat, counterparty_selected_channel_reserve_satoshis: Some(msg_channel_reserve_satoshis), @@ -2595,6 +2596,7 @@ impl ChannelContext where SP::Target: SignerProvider { pubkeys: counterparty_pubkeys, }), funding_outpoint: None, + splice_parent_funding_txid: None, channel_type_features: channel_type.clone(), channel_value_satoshis, }, @@ -2734,11 +2736,10 @@ impl ChannelContext where SP::Target: SignerProvider { config: &'a UserConfig, current_chain_height: u32, outbound_scid_alias: u64, - temporary_channel_id: Option, + temporary_channel_id_fn: Option ChannelId>, holder_selected_channel_reserve_satoshis: u64, channel_keys_id: [u8; 32], holder_signer: ::EcdsaSigner, - pubkeys: ChannelPublicKeys, _logger: L, ) -> Result<(FundingScope, ChannelContext), APIError> where @@ -2804,7 +2805,9 @@ impl ChannelContext where SP::Target: SignerProvider { Err(_) => return Err(APIError::ChannelUnavailable { err: "Failed to get destination script".to_owned()}), }; - let temporary_channel_id = temporary_channel_id.unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); + let pubkeys = holder_signer.pubkeys(None, &secp_ctx); + let temporary_channel_id = temporary_channel_id_fn.map(|f| f(&pubkeys)) + .unwrap_or_else(|| ChannelId::temporary_from_entropy_source(entropy_source)); let funding = FundingScope { value_to_self_msat, @@ -2829,6 +2832,7 @@ impl ChannelContext where SP::Target: SignerProvider { is_outbound_from_holder: true, counterparty_parameters: None, funding_outpoint: None, + splice_parent_funding_txid: None, channel_type_features: channel_type.clone(), // We'll add our counterparty's `funding_satoshis` when we receive `accept_channel2`. channel_value_satoshis, @@ -8161,7 +8165,9 @@ impl FundedChannel where }; match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { - let our_bitcoin_sig = match ecdsa.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx) { + let our_bitcoin_sig = match ecdsa.sign_channel_announcement_with_funding_key( + &self.funding.channel_transaction_parameters, &announcement, &self.context.secp_ctx, + ) { Err(_) => { log_error!(logger, "Signer rejected channel_announcement signing. Channel will not be announced!"); return None; @@ -8202,7 +8208,9 @@ impl FundedChannel where .map_err(|_| ChannelError::Ignore("Failed to generate node signature for channel_announcement".to_owned()))?; match &self.context.holder_signer { ChannelSignerType::Ecdsa(ecdsa) => { - let our_bitcoin_sig = ecdsa.sign_channel_announcement_with_funding_key(&announcement, &self.context.secp_ctx) + let our_bitcoin_sig = ecdsa.sign_channel_announcement_with_funding_key( + &self.funding.channel_transaction_parameters, &announcement, &self.context.secp_ctx, + ) .map_err(|_| ChannelError::Ignore("Signer rejected channel_announcement".to_owned()))?; Ok(msgs::ChannelAnnouncement { node_signature_1: if were_node_one { our_node_sig } else { their_node_sig }, @@ -9008,7 +9016,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { let channel_keys_id = signer_provider.generate_channel_keys_id(false, user_id); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); + + let temporary_channel_id_fn = temporary_channel_id.map(|id| { + move |_: &ChannelPublicKeys| id + }); let (funding, context) = ChannelContext::new_for_outbound_channel( fee_estimator, @@ -9022,11 +9033,10 @@ impl OutboundV1Channel where SP::Target: SignerProvider { config, current_chain_height, outbound_scid_alias, - temporary_channel_id, + temporary_channel_id_fn, holder_selected_channel_reserve_satoshis, channel_keys_id, holder_signer, - pubkeys, logger, )?; let unfunded_context = UnfundedChannelContext { @@ -9565,9 +9575,10 @@ impl PendingV2Channel where SP::Target: SignerProvider { { let channel_keys_id = signer_provider.generate_channel_keys_id(false, user_id); let holder_signer = signer_provider.derive_channel_signer(channel_keys_id); - let pubkeys = holder_signer.pubkeys().clone(); - let temporary_channel_id = Some(ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint)); + let temporary_channel_id_fn = Some(|pubkeys: &ChannelPublicKeys| { + ChannelId::temporary_v2_from_revocation_basepoint(&pubkeys.revocation_basepoint) + }); let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis( funding_satoshis, MIN_CHAN_DUST_LIMIT_SATOSHIS); @@ -9591,11 +9602,10 @@ impl PendingV2Channel where SP::Target: SignerProvider { config, current_chain_height, outbound_scid_alias, - temporary_channel_id, + temporary_channel_id_fn, holder_selected_channel_reserve_satoshis, channel_keys_id, holder_signer, - pubkeys, logger, )?; let unfunded_context = UnfundedChannelContext { @@ -11597,7 +11607,8 @@ mod tests { [0; 32], ); - assert_eq!(signer.pubkeys().funding_pubkey.serialize()[..], + let holder_pubkeys = signer.pubkeys(None, &secp_ctx); + assert_eq!(holder_pubkeys.funding_pubkey.serialize()[..], >::from_hex("023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb").unwrap()[..]); let keys_provider = Keys { signer: signer.clone() }; @@ -11636,11 +11647,13 @@ mod tests { // We can't just use build_holder_transaction_keys here as the per_commitment_secret is not // derived from a commitment_seed, so instead we copy it here and call // build_commitment_transaction. - let delayed_payment_base = &chan.context.holder_signer.as_ref().pubkeys().delayed_payment_basepoint; let per_commitment_secret = SecretKey::from_slice(&>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); - let htlc_basepoint = &chan.context.holder_signer.as_ref().pubkeys().htlc_basepoint; - let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint); + let directed_params = chan.funding.channel_transaction_parameters.as_holder_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &per_commitment_point, directed_params.broadcaster_pubkeys(), + directed_params.countersignatory_pubkeys(), &secp_ctx, + ); macro_rules! test_commitment { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => { @@ -11691,7 +11704,7 @@ mod tests { commitment_tx.clone(), counterparty_signature, counterparty_htlc_sigs, - &chan.context.holder_signer.as_ref().pubkeys().funding_pubkey, + &holder_pubkeys.funding_pubkey, chan.funding.counterparty_funding_pubkey() ); let holder_sig = signer.sign_holder_commitment(&chan.funding.channel_transaction_parameters, &holder_commitment_tx, &secp_ctx).unwrap(); diff --git a/lightning/src/ln/dual_funding_tests.rs b/lightning/src/ln/dual_funding_tests.rs index 72fa049d6b0..594743ff184 100644 --- a/lightning/src/ln/dual_funding_tests.rs +++ b/lightning/src/ln/dual_funding_tests.rs @@ -169,6 +169,7 @@ fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession) holder_selected_contest_delay: open_channel_v2_msg.common_fields.to_self_delay, is_outbound_from_holder: true, funding_outpoint, + splice_parent_funding_txid: None, channel_type_features, channel_value_satoshis: funding_satoshis, }; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 8cfb7674f7c..0cd481a9346 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -737,7 +737,7 @@ pub fn test_update_fee_that_funder_cannot_afford() { let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap(); let local_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = local_chan.get_signer(); - let pubkeys = chan_signer.as_ref().pubkeys(); + let pubkeys = chan_signer.as_ref().pubkeys(None, &secp_ctx); (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, pubkeys.funding_pubkey) }; @@ -746,7 +746,7 @@ pub fn test_update_fee_that_funder_cannot_afford() { let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); let remote_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = remote_chan.get_signer(); - let pubkeys = chan_signer.as_ref().pubkeys(); + let pubkeys = chan_signer.as_ref().pubkeys(None, &secp_ctx); (pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap(), pubkeys.funding_pubkey) @@ -1468,21 +1468,21 @@ pub fn test_fee_spike_violation_fails_htlc() { // Make the signer believe we validated another commitment, so we can release the secret chan_signer.as_ecdsa().unwrap().get_enforcement_state().last_holder_commitment -= 1; - let pubkeys = chan_signer.as_ref().pubkeys(); + let pubkeys = chan_signer.as_ref().pubkeys(None, &secp_ctx); (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, chan_signer.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER).unwrap(), chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx).unwrap(), - chan_signer.as_ref().pubkeys().funding_pubkey) + chan_signer.as_ref().pubkeys(None, &secp_ctx).funding_pubkey) }; let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = { let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); let remote_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = remote_chan.get_signer(); - let pubkeys = chan_signer.as_ref().pubkeys(); + let pubkeys = chan_signer.as_ref().pubkeys(None, &secp_ctx); (pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap(), - chan_signer.as_ref().pubkeys().funding_pubkey) + chan_signer.as_ref().pubkeys(None, &secp_ctx).funding_pubkey) }; // Assemble the set of keys we can use for signatures for our commitment_signed message. diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index e84cd80a0be..3a0eba23cec 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -238,7 +238,8 @@ pub trait EcdsaChannelSigner: ChannelSigner { /// /// [`NodeSigner::sign_gossip_message`]: crate::sign::NodeSigner::sign_gossip_message fn sign_channel_announcement_with_funding_key( - &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, + &self, channel_parameters: &ChannelTransactionParameters, + msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, ) -> Result; /// Signs the input of a splicing funding transaction with our funding key. diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 9f04968198a..0539a696852 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -774,8 +774,14 @@ pub trait ChannelSigner { /// Returns the holder's channel public keys and basepoints. /// + /// `splice_parent_funding_txid` can be used to compute a tweak to rotate the funding key in the + /// 2-of-2 multisig script during a splice. See [`compute_funding_key_tweak`] for an example + /// tweak and more details. + /// /// This method is *not* asynchronous. Instead, the value must be cached locally. - fn pubkeys(&self) -> &ChannelPublicKeys; + fn pubkeys( + &self, splice_parent_funding_txid: Option, secp_ctx: &Secp256k1, + ) -> ChannelPublicKeys; /// Returns an arbitrary identifier describing the set of keys which are provided back to you in /// some [`SpendableOutputDescriptor`] types. This should be sufficient to identify this @@ -977,6 +983,56 @@ pub trait ChangeDestinationSource { fn get_change_destination_script(&self) -> Result; } +mod sealed { + use bitcoin::secp256k1::{Scalar, SecretKey}; + + #[derive(Clone, PartialEq)] + pub struct MaybeTweakedSecretKey(SecretKey); + + impl From for MaybeTweakedSecretKey { + fn from(value: SecretKey) -> Self { + Self(value) + } + } + + impl MaybeTweakedSecretKey { + pub fn with_tweak(&self, tweak: Option) -> SecretKey { + tweak + .map(|tweak| { + self.0 + .add_tweak(&tweak) + .expect("Addition only fails if the tweak is the inverse of the key") + }) + .unwrap_or(self.0) + } + } +} + +/// Computes the tweak to apply to the base funding key of a channel. +/// +/// The tweak is computed similar to existing tweaks used in +/// [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation): +/// +/// 1. We use the txid of the funding transaction the splice transaction is spending instead of the +/// `per_commitment_point` to guarantee uniqueness. +/// 2. We include the private key instead of the public key to guarantee only those with knowledge +/// of it can re-derive the new funding key. +/// +/// tweak = SHA256(splice_parent_funding_txid || base_funding_secret_key) +/// tweaked_funding_key = base_funding_key + tweak +/// +/// While the use of this tweak is not required (signers may choose to compute a tweak of their +/// choice), signers must ensure their tweak guarantees the two properties mentioned above: +/// uniqueness and derivable only by one or both of the channel participants. +pub fn compute_funding_key_tweak( + base_funding_secret_key: &SecretKey, splice_parent_funding_txid: &Txid, +) -> Scalar { + let mut sha = Sha256::engine(); + sha.input(splice_parent_funding_txid.as_byte_array()); + sha.input(&base_funding_secret_key.secret_bytes()); + Scalar::from_be_bytes(Sha256::from_engine(sha).to_byte_array()).unwrap() +} + /// A simple implementation of [`EcdsaChannelSigner`] that just keeps the private keys in memory. /// /// This implementation performs no policy checks and is insufficient by itself as @@ -984,7 +1040,7 @@ pub trait ChangeDestinationSource { pub struct InMemorySigner { /// Holder secret key in the 2-of-2 multisig script of a channel. This key also backs the /// holder's anchor output in a commitment transaction, if one is present. - pub funding_key: SecretKey, + funding_key: sealed::MaybeTweakedSecretKey, /// Holder secret key for blinded revocation pubkey. pub revocation_base_key: SecretKey, /// Holder secret key used for our balance in counterparty-broadcasted commitment transactions. @@ -1048,7 +1104,7 @@ impl InMemorySigner { &htlc_base_key, ); InMemorySigner { - funding_key, + funding_key: sealed::MaybeTweakedSecretKey::from(funding_key), revocation_base_key, payment_key, delayed_payment_base_key, @@ -1060,6 +1116,14 @@ impl InMemorySigner { } } + /// Holder secret key in the 2-of-2 multisig script of a channel. This key also backs the + /// holder's anchor output in a commitment transaction, if one is present. + pub fn funding_key(&self, splice_parent_funding_txid: Option) -> SecretKey { + let tweak = splice_parent_funding_txid + .map(|txid| compute_funding_key_tweak(&self.funding_key.with_tweak(None), &txid)); + self.funding_key.with_tweak(tweak) + } + fn make_holder_keys( secp_ctx: &Secp256k1, funding_key: &SecretKey, revocation_base_key: &SecretKey, payment_key: &SecretKey, delayed_payment_base_key: &SecretKey, htlc_base_key: &SecretKey, @@ -1103,7 +1167,7 @@ impl InMemorySigner { return Err(()); } - let remotepubkey = bitcoin::PublicKey::new(self.pubkeys().payment_point); + let remotepubkey = bitcoin::PublicKey::new(self.holder_channel_pubkeys.payment_point); let supports_anchors_zero_fee_htlc_tx = descriptor .channel_transaction_parameters .as_ref() @@ -1251,8 +1315,15 @@ impl ChannelSigner for InMemorySigner { Ok(()) } - fn pubkeys(&self) -> &ChannelPublicKeys { - &self.holder_channel_pubkeys + fn pubkeys( + &self, splice_parent_funding_txid: Option, secp_ctx: &Secp256k1, + ) -> ChannelPublicKeys { + let mut pubkeys = self.holder_channel_pubkeys.clone(); + if splice_parent_funding_txid.is_some() { + pubkeys.funding_pubkey = + self.funding_key(splice_parent_funding_txid).public_key(secp_ctx); + } + pubkeys } fn channel_keys_id(&self) -> [u8; 32] { @@ -1274,7 +1345,8 @@ impl EcdsaChannelSigner for InMemorySigner { let trusted_tx = commitment_tx.trust(); let keys = trusted_tx.keys(); - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_keys = channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); let channel_funding_redeemscript = @@ -1282,7 +1354,7 @@ impl EcdsaChannelSigner for InMemorySigner { let built_tx = trusted_tx.built_transaction(); let commitment_sig = built_tx.sign_counterparty_commitment( - &self.funding_key, + &funding_key, &channel_funding_redeemscript, channel_parameters.channel_value_satoshis, secp_ctx, @@ -1335,14 +1407,15 @@ impl EcdsaChannelSigner for InMemorySigner { ) -> Result { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_keys = channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let trusted_tx = commitment_tx.trust(); Ok(trusted_tx.built_transaction().sign_holder_commitment( - &self.funding_key, + &funding_key, &funding_redeemscript, channel_parameters.channel_value_satoshis, &self, @@ -1357,14 +1430,15 @@ impl EcdsaChannelSigner for InMemorySigner { ) -> Result { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_keys = channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); let trusted_tx = commitment_tx.trust(); Ok(trusted_tx.built_transaction().sign_holder_commitment( - &self.funding_key, + &funding_key, &funding_redeemscript, channel_parameters.channel_value_satoshis, &self, @@ -1549,13 +1623,14 @@ impl EcdsaChannelSigner for InMemorySigner { ) -> Result { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_funding_key = &channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR).funding_pubkey; let channel_funding_redeemscript = make_funding_redeemscript(&funding_pubkey, counterparty_funding_key); Ok(closing_tx.trust().sign( - &self.funding_key, + &funding_key, &channel_funding_redeemscript, channel_parameters.channel_value_satoshis, secp_ctx, @@ -1578,14 +1653,17 @@ impl EcdsaChannelSigner for InMemorySigner { EcdsaSighashType::All, ) .unwrap(); - Ok(sign_with_aux_rand(secp_ctx, &hash_to_message!(&sighash[..]), &self.funding_key, &self)) + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + Ok(sign_with_aux_rand(secp_ctx, &hash_to_message!(&sighash[..]), &funding_key, &self)) } fn sign_channel_announcement_with_funding_key( - &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, + &self, channel_parameters: &ChannelTransactionParameters, + msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, ) -> Result { let msghash = hash_to_message!(&Sha256dHash::hash(&msg.encode()[..])[..]); - Ok(secp_ctx.sign_ecdsa(&msghash, &self.funding_key)) + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + Ok(secp_ctx.sign_ecdsa(&msghash, &funding_key)) } fn sign_splicing_funding_input( @@ -1594,7 +1672,8 @@ impl EcdsaChannelSigner for InMemorySigner { ) -> Result { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid); + let funding_pubkey = funding_key.public_key(secp_ctx); let counterparty_funding_key = &channel_parameters.counterparty_pubkeys().expect(MISSING_PARAMS_ERR).funding_pubkey; let funding_redeemscript = @@ -1608,7 +1687,7 @@ impl EcdsaChannelSigner for InMemorySigner { ) .unwrap()[..]; let msg = hash_to_message!(sighash); - Ok(sign(secp_ctx, &msg, &self.funding_key)) + Ok(sign(secp_ctx, &msg, &funding_key)) } } diff --git a/lightning/src/util/dyn_signer.rs b/lightning/src/util/dyn_signer.rs index 74920fa4b0a..e08d7a1f0d0 100644 --- a/lightning/src/util/dyn_signer.rs +++ b/lightning/src/util/dyn_signer.rs @@ -21,7 +21,7 @@ use crate::sign::{NodeSigner, Recipient, SignerProvider, SpendableOutputDescript use bitcoin; use bitcoin::absolute::LockTime; use bitcoin::secp256k1::All; -use bitcoin::{secp256k1, ScriptBuf, Transaction, TxOut}; +use bitcoin::{secp256k1, ScriptBuf, Transaction, TxOut, Txid}; use lightning_invoice::RawBolt11Invoice; #[cfg(taproot)] use musig2::types::{PartialSignature, PublicNonce}; @@ -154,8 +154,10 @@ delegate!(DynSigner, EcdsaChannelSigner, inner, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result, fn sign_closing_transaction(, channel_parameters: &ChannelTransactionParameters, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1) -> Result, - fn sign_channel_announcement_with_funding_key(, msg: &UnsignedChannelAnnouncement, - secp_ctx: &Secp256k1) -> Result, + fn sign_channel_announcement_with_funding_key(, + channel_parameters: &ChannelTransactionParameters, msg: &UnsignedChannelAnnouncement, + secp_ctx: &Secp256k1 + ) -> Result, fn sign_holder_anchor_input(, channel_parameters: &ChannelTransactionParameters, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1) -> Result, @@ -177,7 +179,9 @@ delegate!(DynSigner, ChannelSigner, holder_tx: &HolderCommitmentTransaction, preimages: Vec ) -> Result<(), ()>, - fn pubkeys(,) -> &ChannelPublicKeys, + fn pubkeys(, + splice_parent_funding_txid: Option, secp_ctx: &Secp256k1 + ) -> ChannelPublicKeys, fn channel_keys_id(,) -> [u8; 32], fn validate_counterparty_revocation(, idx: u64, secret: &SecretKey) -> Result<(), ()> ); diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index c439ac66c95..6fb0bc0c890 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -30,6 +30,7 @@ use bitcoin::hashes::Hash; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Transaction; +use bitcoin::Txid; #[cfg(taproot)] use crate::ln::msgs::PartialSignatureWithNonce; @@ -210,8 +211,10 @@ impl ChannelSigner for TestChannelSigner { Ok(()) } - fn pubkeys(&self) -> &ChannelPublicKeys { - self.inner.pubkeys() + fn pubkeys( + &self, splice_parent_funding_txid: Option, secp_ctx: &Secp256k1, + ) -> ChannelPublicKeys { + self.inner.pubkeys(splice_parent_funding_txid, secp_ctx) } fn channel_keys_id(&self) -> [u8; 32] { @@ -467,9 +470,10 @@ impl EcdsaChannelSigner for TestChannelSigner { } fn sign_channel_announcement_with_funding_key( - &self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, + &self, channel_parameters: &ChannelTransactionParameters, + msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, ) -> Result { - self.inner.sign_channel_announcement_with_funding_key(msg, secp_ctx) + self.inner.sign_channel_announcement_with_funding_key(channel_parameters, msg, secp_ctx) } fn sign_splicing_funding_input(