Skip to content

Commit

Permalink
Support holder funding key rotation during splicing
Browse files Browse the repository at this point in the history
We introduce a scalar tweak that can be applied to the base funding key
to obtain the channel's funding key used in the 2-of-2 multisig. This is
used to derive additional keys from the same secret backing the base
funding_pubkey, as we have to rotate keys for each successful splice
attempt.

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.
  • Loading branch information
wpaulino committed Mar 5, 2025
1 parent 7188d5b commit bbd89a2
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 63 deletions.
7 changes: 5 additions & 2 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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) = {
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
};
Expand Down
1 change: 1 addition & 0 deletions lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
60 changes: 56 additions & 4 deletions lightning/src/chain/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
30 changes: 25 additions & 5 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
Expand All @@ -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(())
Expand All @@ -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;
Expand All @@ -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),
});

Expand All @@ -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,
})
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit bbd89a2

Please sign in to comment.