Merge pull request #3624 from wpaulino/funding-key-tweak

Support scalar tweak to rotate holder funding key during splicing
This commit is contained in:
Matt Corallo 2025-03-06 00:19:03 +00:00 committed by GitHub
commit f25709cc79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 241 additions and 63 deletions

View file

@ -1359,8 +1359,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
) -> ChannelMonitor<Signer> {
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<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
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,
};

View file

@ -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,
};

View file

@ -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) {

View file

@ -880,6 +880,17 @@ pub struct ChannelTransactionParameters {
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
/// The late-bound funding outpoint
pub funding_outpoint: Option<chain::transaction::OutPoint>,
/// 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<Txid>,
/// 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<u64> 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<u64> 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<u64> 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(&<Vec<u8>>::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 {

View file

@ -2411,7 +2411,6 @@ impl<SP: Deref> ChannelContext<SP> 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<SP: Deref> ChannelContext<SP> 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<SP: Deref> ChannelContext<SP> 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<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
config: &'a UserConfig,
current_chain_height: u32,
outbound_scid_alias: u64,
temporary_channel_id: Option<ChannelId>,
temporary_channel_id_fn: Option<impl Fn(&ChannelPublicKeys) -> ChannelId>,
holder_selected_channel_reserve_satoshis: u64,
channel_keys_id: [u8; 32],
holder_signer: <SP::Target as SignerProvider>::EcdsaSigner,
pubkeys: ChannelPublicKeys,
_logger: L,
) -> Result<(FundingScope, ChannelContext<SP>), APIError>
where
@ -2804,7 +2805,9 @@ impl<SP: Deref> ChannelContext<SP> 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<SP: Deref> ChannelContext<SP> 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<SP: Deref> FundedChannel<SP> 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<SP: Deref> FundedChannel<SP> 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<SP: Deref> OutboundV1Channel<SP> 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<SP: Deref> OutboundV1Channel<SP> 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<SP: Deref> PendingV2Channel<SP> 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<SP: Deref> PendingV2Channel<SP> 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()[..],
<Vec<u8>>::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(&<Vec<u8>>::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();

View file

@ -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,
};

View file

@ -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.

View file

@ -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<secp256k1::All>,
&self, channel_parameters: &ChannelTransactionParameters,
msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()>;
/// Signs the input of a splicing funding transaction with our funding key.

View file

@ -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<Txid>, secp_ctx: &Secp256k1<secp256k1::All>,
) -> 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<ScriptBuf, ()>;
}
mod sealed {
use bitcoin::secp256k1::{Scalar, SecretKey};
#[derive(Clone, PartialEq)]
pub struct MaybeTweakedSecretKey(SecretKey);
impl From<SecretKey> for MaybeTweakedSecretKey {
fn from(value: SecretKey) -> Self {
Self(value)
}
}
impl MaybeTweakedSecretKey {
pub fn with_tweak(&self, tweak: Option<Scalar>) -> 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<Txid>) -> 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<C: Signing>(
secp_ctx: &Secp256k1<C>, 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<Txid>, secp_ctx: &Secp256k1<secp256k1::All>,
) -> 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<Signature, ()> {
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<Signature, ()> {
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<Signature, ()> {
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<secp256k1::All>,
&self, channel_parameters: &ChannelTransactionParameters,
msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
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<Signature, ()> {
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))
}
}

View file

@ -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<secp256k1::All>) -> Result<Signature, ()>,
fn sign_closing_transaction(, channel_parameters: &ChannelTransactionParameters,
closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>,
fn sign_channel_announcement_with_funding_key(, msg: &UnsignedChannelAnnouncement,
secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>,
fn sign_channel_announcement_with_funding_key(,
channel_parameters: &ChannelTransactionParameters, msg: &UnsignedChannelAnnouncement,
secp_ctx: &Secp256k1<secp256k1::All>
) -> Result<Signature, ()>,
fn sign_holder_anchor_input(, channel_parameters: &ChannelTransactionParameters,
anchor_tx: &Transaction, input: usize,
secp_ctx: &Secp256k1<secp256k1::All>) -> Result<Signature, ()>,
@ -177,7 +179,9 @@ delegate!(DynSigner, ChannelSigner,
holder_tx: &HolderCommitmentTransaction,
preimages: Vec<PaymentPreimage>
) -> Result<(), ()>,
fn pubkeys(,) -> &ChannelPublicKeys,
fn pubkeys(,
splice_parent_funding_txid: Option<Txid>, secp_ctx: &Secp256k1<secp256k1::All>
) -> ChannelPublicKeys,
fn channel_keys_id(,) -> [u8; 32],
fn validate_counterparty_revocation(, idx: u64, secret: &SecretKey) -> Result<(), ()>
);

View file

@ -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<Txid>, secp_ctx: &Secp256k1<secp256k1::All>,
) -> 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<secp256k1::All>,
&self, channel_parameters: &ChannelTransactionParameters,
msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
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(