Skip to content

Commit def03b5

Browse files
committed
Support scalar tweak to rotate holder funding key during splicing
A scalar tweak 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), but rather than using the `per_commitment_point`, we use the txid of the funding transaction the splice transaction is spending to guarantee uniqueness, and the `revocation_basepoint` to guarantee only the channel participants can re-derive the new funding key. tweak = SHA256(splice_parent_funding_txid || revocation_basepoint || base_funding_pubkey) tweaked_funding_key = base_funding_key + tweak
1 parent 0d127cf commit def03b5

File tree

7 files changed

+237
-45
lines changed

7 files changed

+237
-45
lines changed

lightning/src/chain/channelmonitor.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -3461,7 +3461,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34613461
let broadcaster_keys = &self.onchain_tx_handler.channel_transaction_parameters
34623462
.counterparty_parameters.as_ref().unwrap().pubkeys;
34633463
let countersignatory_keys =
3464-
&self.onchain_tx_handler.channel_transaction_parameters.holder_pubkeys;
3464+
self.onchain_tx_handler.channel_transaction_parameters.holder_pubkeys.inner();
34653465

34663466
let broadcaster_funding_key = broadcaster_keys.funding_pubkey;
34673467
let countersignatory_funding_key = countersignatory_keys.funding_pubkey;
@@ -5136,7 +5136,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
51365136
if onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() &&
51375137
counterparty_payment_script.is_p2wpkh()
51385138
{
5139-
let payment_point = onchain_tx_handler.channel_transaction_parameters.holder_pubkeys.payment_point;
5139+
let payment_point = onchain_tx_handler.channel_transaction_parameters.holder_pubkeys.inner().payment_point;
51405140
counterparty_payment_script =
51415141
chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point).to_p2wsh();
51425142
}
@@ -5235,7 +5235,7 @@ mod tests {
52355235
use crate::ln::types::ChannelId;
52365236
use crate::types::payment::{PaymentPreimage, PaymentHash};
52375237
use crate::ln::channel_keys::{DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, RevocationBasepoint, RevocationKey};
5238-
use crate::ln::chan_utils::{self,HTLCOutputInCommitment, ChannelPublicKeys, ChannelTransactionParameters, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
5238+
use crate::ln::chan_utils::{self,HTLCOutputInCommitment, ChannelPublicKeys, ChannelTransactionParameters, HolderChannelPublicKeys, HolderCommitmentTransaction, CounterpartyChannelTransactionParameters};
52395239
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
52405240
use crate::ln::functional_test_utils::*;
52415241
use crate::ln::script::ShutdownScript;
@@ -5415,7 +5415,13 @@ mod tests {
54155415
let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::MAX };
54165416
let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint);
54175417
let channel_parameters = ChannelTransactionParameters {
5418-
holder_pubkeys: keys.holder_channel_pubkeys.clone(),
5418+
holder_pubkeys: HolderChannelPublicKeys::new(
5419+
keys.holder_channel_pubkeys.funding_pubkey,
5420+
keys.holder_channel_pubkeys.revocation_basepoint,
5421+
keys.holder_channel_pubkeys.payment_point,
5422+
keys.holder_channel_pubkeys.delayed_payment_basepoint,
5423+
keys.holder_channel_pubkeys.htlc_basepoint, None, &secp_ctx,
5424+
),
54195425
holder_selected_contest_delay: 66,
54205426
is_outbound_from_holder: true,
54215427
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
@@ -5667,7 +5673,13 @@ mod tests {
56675673
let funding_outpoint = OutPoint { txid: Txid::all_zeros(), index: u16::MAX };
56685674
let channel_id = ChannelId::v1_from_funding_outpoint(funding_outpoint);
56695675
let channel_parameters = ChannelTransactionParameters {
5670-
holder_pubkeys: keys.holder_channel_pubkeys.clone(),
5676+
holder_pubkeys: HolderChannelPublicKeys::new(
5677+
keys.holder_channel_pubkeys.funding_pubkey,
5678+
keys.holder_channel_pubkeys.revocation_basepoint,
5679+
keys.holder_channel_pubkeys.payment_point,
5680+
keys.holder_channel_pubkeys.delayed_payment_basepoint,
5681+
keys.holder_channel_pubkeys.htlc_basepoint, None, &secp_ctx,
5682+
),
56715683
holder_selected_contest_delay: 66,
56725684
is_outbound_from_holder: true,
56735685
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {

lightning/src/chain/onchaintx.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
665665
}
666666

667667
// We'll locate an anchor output we can spend within the commitment transaction.
668-
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
668+
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.inner().funding_pubkey;
669669
match chan_utils::get_anchor_output(&tx.0, funding_pubkey) {
670670
// An anchor output was found, so we should yield a funding event externally.
671671
Some((idx, _)) => {
@@ -1290,7 +1290,7 @@ mod tests {
12901290
use crate::chain::transaction::OutPoint;
12911291
use crate::ln::chan_utils::{
12921292
ChannelPublicKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters,
1293-
HTLCOutputInCommitment, HolderCommitmentTransaction,
1293+
HTLCOutputInCommitment, HolderChannelPublicKeys, HolderCommitmentTransaction,
12941294
};
12951295
use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint};
12961296
use crate::ln::functional_test_utils::create_dummy_block;
@@ -1344,7 +1344,13 @@ mod tests {
13441344
// Use non-anchor channels so that HTLC-Timeouts are broadcast immediately instead of sent
13451345
// to the user for external funding.
13461346
let chan_params = ChannelTransactionParameters {
1347-
holder_pubkeys: signer.holder_channel_pubkeys.clone(),
1347+
holder_pubkeys: HolderChannelPublicKeys::new(
1348+
signer.holder_channel_pubkeys.funding_pubkey,
1349+
signer.holder_channel_pubkeys.revocation_basepoint,
1350+
signer.holder_channel_pubkeys.payment_point,
1351+
signer.holder_channel_pubkeys.delayed_payment_basepoint,
1352+
signer.holder_channel_pubkeys.htlc_basepoint, None, &secp_ctx,
1353+
),
13481354
holder_selected_contest_delay: 66,
13491355
is_outbound_from_holder: true,
13501356
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {

lightning/src/ln/chan_utils.rs

+138-9
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use crate::util::transaction_utils;
3535

3636
use bitcoin::locktime::absolute::LockTime;
3737
use bitcoin::ecdsa::Signature as BitcoinSignature;
38-
use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar};
38+
use bitcoin::secp256k1::{SecretKey, PublicKey, Scalar, Verification};
3939
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Message};
4040
use bitcoin::{secp256k1, Sequence, Witness};
4141

@@ -430,6 +430,26 @@ pub fn derive_private_revocation_key<T: secp256k1::Signing>(secp_ctx: &Secp256k1
430430
.expect("Addition only fails if the tweak is the inverse of the key. This is not possible when the tweak commits to the key.")
431431
}
432432

433+
/// Computes the tweak to apply to the base funding key of a channel.
434+
///
435+
/// The tweak is computed similar to existing tweaks used in
436+
/// [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md#key-derivation), but
437+
/// rather than using the `per_commitment_point`, we use the txid of the funding transaction the
438+
/// splice transaction is spending to guarantee uniqueness, and the `revocation_basepoint` to
439+
/// guarantee only the channel participants can re-derive the new funding key.
440+
///
441+
/// tweak = SHA256(splice_parent_funding_txid || revocation_basepoint || base_funding_pubkey)
442+
/// tweaked_funding_key = base_funding_key + tweak
443+
//
444+
// TODO: Expose a helper on `FundingScope` that calls this.
445+
pub fn compute_funding_key_tweak(base_funding_pubkey: &PublicKey, revocation_basepoint: &PublicKey, splice_parent_funding_txid: &Txid) -> Scalar {
446+
let mut sha = Sha256::engine();
447+
sha.input(splice_parent_funding_txid.as_byte_array());
448+
sha.input(&revocation_basepoint.serialize());
449+
sha.input(&base_funding_pubkey.serialize());
450+
Scalar::from_be_bytes(Sha256::from_engine(sha).to_byte_array()).unwrap()
451+
}
452+
433453
/// The set of public keys which are used in the creation of one commitment transaction.
434454
/// These are derived from the channel base keys and per-commitment data.
435455
///
@@ -470,6 +490,9 @@ impl_writeable_tlv_based!(TxCreationKeys, {
470490
pub struct ChannelPublicKeys {
471491
/// The public key which is used to sign all commitment transactions, as it appears in the
472492
/// on-chain channel lock-in 2-of-2 multisig output.
493+
///
494+
/// NOTE: This key will already have the [`HolderChannelPublicKeys::funding_key_tweak`] applied
495+
/// if one existed.
473496
pub funding_pubkey: PublicKey,
474497
/// The base point which is used (with [`RevocationKey::from_basepoint`]) to derive per-commitment
475498
/// revocation keys. This is combined with the per-commitment-secret generated by the
@@ -497,6 +520,102 @@ impl_writeable_tlv_based!(ChannelPublicKeys, {
497520
(8, htlc_basepoint, required),
498521
});
499522

523+
/// The holder's public keys which do not change over the life of a channel, except for the
524+
/// `funding_pubkey`, which may rotate after each successful splice attempt via the
525+
/// `funding_key_tweak`.
526+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
527+
pub struct HolderChannelPublicKeys {
528+
keys: ChannelPublicKeys,
529+
/// A optional scalar tweak applied to the base funding key to obtain the channel's funding key
530+
/// used in the 2-of-2 multisig. This is used to derive additional keys from the same secret
531+
/// backing the base `funding_pubkey`, as we have to rotate keys for each successful splice
532+
/// attempt. The tweak is computed as described in [`compute_funding_key_tweak`].
533+
//
534+
// TODO: Expose `splice_parent_funding_txid` instead so the signer can re-derive the tweak?
535+
// There's no harm in the signer trusting the tweak as long as its funding secret has not
536+
// been leaked.
537+
pub funding_key_tweak: Option<Scalar>,
538+
}
539+
540+
// `HolderChannelPublicKeys` may have been previously written as `ChannelPublicKeys` so we have to
541+
// mimic its serialization.
542+
impl Writeable for HolderChannelPublicKeys {
543+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
544+
write_tlv_fields!(writer, {
545+
(0, self.keys.funding_pubkey, required),
546+
(2, self.keys.revocation_basepoint, required),
547+
(4, self.keys.payment_point, required),
548+
(6, self.keys.delayed_payment_basepoint, required),
549+
(8, self.keys.htlc_basepoint, required),
550+
(10, self.funding_key_tweak, option),
551+
});
552+
Ok(())
553+
}
554+
}
555+
556+
impl Readable for HolderChannelPublicKeys {
557+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
558+
let mut funding_pubkey = RequiredWrapper(None);
559+
let mut revocation_basepoint = RequiredWrapper(None);
560+
let mut payment_point = RequiredWrapper(None);
561+
let mut delayed_payment_basepoint = RequiredWrapper(None);
562+
let mut htlc_basepoint = RequiredWrapper(None);
563+
let mut funding_key_tweak: Option<Scalar> = None;
564+
565+
read_tlv_fields!(reader, {
566+
(0, funding_pubkey, required),
567+
(2, revocation_basepoint, required),
568+
(4, payment_point, required),
569+
(6, delayed_payment_basepoint, required),
570+
(8, htlc_basepoint, required),
571+
(10, funding_key_tweak, option),
572+
});
573+
574+
Ok(Self {
575+
keys: ChannelPublicKeys {
576+
funding_pubkey: funding_pubkey.0.unwrap(),
577+
revocation_basepoint: revocation_basepoint.0.unwrap(),
578+
payment_point: payment_point.0.unwrap(),
579+
delayed_payment_basepoint: delayed_payment_basepoint.0.unwrap(),
580+
htlc_basepoint: htlc_basepoint.0.unwrap(),
581+
},
582+
funding_key_tweak,
583+
})
584+
}
585+
}
586+
587+
impl HolderChannelPublicKeys {
588+
/// Constructs a new instance of [`HolderChannelPublicKeys`].
589+
pub fn new<C: Verification>(
590+
funding_pubkey: PublicKey, revocation_basepoint: RevocationBasepoint,
591+
payment_point: PublicKey, delayed_payment_basepoint: DelayedPaymentBasepoint,
592+
htlc_basepoint: HtlcBasepoint, funding_key_tweak: Option<Scalar>, secp: &Secp256k1<C>,
593+
) -> Self {
594+
let funding_pubkey = funding_key_tweak
595+
.map(|tweak| {
596+
funding_pubkey
597+
.add_exp_tweak(secp, &tweak)
598+
.expect("Addition only fails if the tweak is the inverse of the key")
599+
})
600+
.unwrap_or(funding_pubkey);
601+
Self {
602+
keys: ChannelPublicKeys {
603+
funding_pubkey,
604+
revocation_basepoint,
605+
payment_point,
606+
delayed_payment_basepoint,
607+
htlc_basepoint,
608+
},
609+
funding_key_tweak,
610+
}
611+
}
612+
613+
/// Returns the holder's channel public keys as a [`ChannelPublicKeys`].
614+
pub fn inner(&self) -> &ChannelPublicKeys {
615+
&self.keys
616+
}
617+
}
618+
500619
impl TxCreationKeys {
501620
/// Create per-state keys from channel base points and the per-commitment point.
502621
/// Key set is asymmetric and can't be used as part of counter-signatory set of transactions.
@@ -869,7 +988,7 @@ pub fn build_anchor_input_witness(funding_key: &PublicKey, funding_sig: &Signatu
869988
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
870989
pub struct ChannelTransactionParameters {
871990
/// Holder public keys
872-
pub holder_pubkeys: ChannelPublicKeys,
991+
pub holder_pubkeys: HolderChannelPublicKeys,
873992
/// The contest delay selected by the holder, which applies to counterparty-broadcast transactions
874993
pub holder_selected_contest_delay: u16,
875994
/// Whether the holder is the initiator of this channel.
@@ -933,7 +1052,7 @@ impl ChannelTransactionParameters {
9331052

9341053
pub(crate) fn make_funding_redeemscript(&self) -> ScriptBuf {
9351054
make_funding_redeemscript(
936-
&self.holder_pubkeys.funding_pubkey,
1055+
&self.holder_pubkeys.inner().funding_pubkey,
9371056
&self.counterparty_parameters.as_ref().unwrap().pubkeys.funding_pubkey
9381057
)
9391058
}
@@ -953,7 +1072,10 @@ impl ChannelTransactionParameters {
9531072
htlc_basepoint: PublicKey::from_slice(&[2; 33]).unwrap().into(),
9541073
};
9551074
Self {
956-
holder_pubkeys: dummy_keys.clone(),
1075+
holder_pubkeys: HolderChannelPublicKeys {
1076+
keys: dummy_keys.clone(),
1077+
funding_key_tweak: None,
1078+
},
9571079
holder_selected_contest_delay: 42,
9581080
is_outbound_from_holder: true,
9591081
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
@@ -1050,7 +1172,7 @@ impl<'a> DirectedChannelTransactionParameters<'a> {
10501172
/// Get the channel pubkeys for the broadcaster
10511173
pub fn broadcaster_pubkeys(&self) -> &'a ChannelPublicKeys {
10521174
if self.holder_is_broadcaster {
1053-
&self.inner.holder_pubkeys
1175+
self.inner.holder_pubkeys.inner()
10541176
} else {
10551177
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
10561178
}
@@ -1061,7 +1183,7 @@ impl<'a> DirectedChannelTransactionParameters<'a> {
10611183
if self.holder_is_broadcaster {
10621184
&self.inner.counterparty_parameters.as_ref().unwrap().pubkeys
10631185
} else {
1064-
&self.inner.holder_pubkeys
1186+
self.inner.holder_pubkeys.inner()
10651187
}
10661188
}
10671189

@@ -1149,7 +1271,10 @@ impl HolderCommitmentTransaction {
11491271
htlc_basepoint: HtlcBasepoint::from(dummy_key.clone())
11501272
};
11511273
let channel_parameters = ChannelTransactionParameters {
1152-
holder_pubkeys: channel_pubkeys.clone(),
1274+
holder_pubkeys: HolderChannelPublicKeys {
1275+
keys: channel_pubkeys.clone(),
1276+
funding_key_tweak: None,
1277+
},
11531278
holder_selected_contest_delay: 0,
11541279
is_outbound_from_holder: false,
11551280
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }),
@@ -1918,7 +2043,7 @@ pub fn get_commitment_transaction_number_obscure_factor(
19182043

19192044
#[cfg(test)]
19202045
mod tests {
1921-
use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
2046+
use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys, HolderChannelPublicKeys};
19222047
use crate::chain;
19232048
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
19242049
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
@@ -1961,7 +2086,11 @@ mod tests {
19612086
let counterparty_pubkeys = counterparty_signer.pubkeys().clone();
19622087
let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint);
19632088
let channel_parameters = ChannelTransactionParameters {
1964-
holder_pubkeys: holder_pubkeys.clone(),
2089+
holder_pubkeys: HolderChannelPublicKeys::new(
2090+
holder_pubkeys.funding_pubkey, holder_pubkeys.revocation_basepoint,
2091+
holder_pubkeys.payment_point, holder_pubkeys.delayed_payment_basepoint,
2092+
holder_pubkeys.htlc_basepoint, None, &secp_ctx,
2093+
),
19652094
holder_selected_contest_delay: 0,
19662095
is_outbound_from_holder: false,
19672096
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),

lightning/src/ln/channel.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use crate::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo,
4242
use crate::ln::channelmanager::{self, OpenChannelMessage, PendingHTLCStatus, HTLCSource, SentHTLCId, HTLCFailureMsg, PendingHTLCInfo, RAACommitmentOrder, PaymentClaimDetails, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA, MAX_LOCAL_BREAKDOWN_TIMEOUT};
4343
use crate::ln::chan_utils::{
4444
CounterpartyCommitmentSecrets, TxCreationKeys, HTLCOutputInCommitment, htlc_success_tx_weight,
45-
htlc_timeout_tx_weight, ChannelPublicKeys, CommitmentTransaction,
45+
htlc_timeout_tx_weight, ChannelPublicKeys, HolderChannelPublicKeys, CommitmentTransaction,
4646
HolderCommitmentTransaction, ChannelTransactionParameters,
4747
CounterpartyChannelTransactionParameters, MAX_HTLCS,
4848
get_commitment_transaction_number_obscure_factor,
@@ -1693,7 +1693,7 @@ impl FundingScope {
16931693
}
16941694

16951695
fn get_holder_pubkeys(&self) -> &ChannelPublicKeys {
1696-
&self.channel_transaction_parameters.holder_pubkeys
1696+
self.channel_transaction_parameters.holder_pubkeys.inner()
16971697
}
16981698

16991699
pub fn get_counterparty_selected_contest_delay(&self) -> Option<u16> {
@@ -2585,7 +2585,10 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
25852585
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
25862586

25872587
channel_transaction_parameters: ChannelTransactionParameters {
2588-
holder_pubkeys: pubkeys,
2588+
holder_pubkeys: HolderChannelPublicKeys::new(
2589+
pubkeys.funding_pubkey, pubkeys.revocation_basepoint, pubkeys.payment_point,
2590+
pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, None, &secp_ctx,
2591+
),
25892592
holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay,
25902593
is_outbound_from_holder: false,
25912594
counterparty_parameters: Some(CounterpartyChannelTransactionParameters {
@@ -2822,7 +2825,10 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
28222825
next_remote_commitment_tx_fee_info_cached: Mutex::new(None),
28232826

28242827
channel_transaction_parameters: ChannelTransactionParameters {
2825-
holder_pubkeys: pubkeys,
2828+
holder_pubkeys: HolderChannelPublicKeys::new(
2829+
pubkeys.funding_pubkey, pubkeys.revocation_basepoint, pubkeys.payment_point,
2830+
pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, None, &secp_ctx,
2831+
),
28262832
holder_selected_contest_delay: config.channel_handshake_config.our_to_self_delay,
28272833
is_outbound_from_holder: true,
28282834
counterparty_parameters: None,

0 commit comments

Comments
 (0)