Skip to content

Commit

Permalink
Let ChannelSigner set to_local script pubkey
Browse files Browse the repository at this point in the history
This allows the `to_local` output to easily be changed according to the
features of the channel, or the evolution of the LN specification.

`to_local` could even be set to completely arbitrary scripts if
compatibility with the formal LN spec is not required.

Builders of `CommitmentTransaction` now ask a `ChannelSigner` for the
appropriate `to_local` script pubkey to use, and then pass it to the
`CommitmentTransaction` constructor.

External signers now provide the expected `to_local` script pubkey to
the `verify` call of `CommitmentTransaction`.
  • Loading branch information
tankyleo committed Dec 13, 2024
1 parent e12c3e8 commit cbac0e7
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 39 deletions.
15 changes: 8 additions & 7 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3398,13 +3398,18 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
&broadcaster_keys, &countersignatory_keys, &self.onchain_tx_handler.secp_ctx);
let channel_parameters =
&self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable();
let to_broadcaster_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, their_per_commitment_point, &self.onchain_tx_handler.secp_ctx);
let to_broadcaster_txout = TxOut {
script_pubkey: to_broadcaster_spk,
value: Amount::from_sat(to_broadcaster_value),
};
let counterparty_txout = TxOut {
script_pubkey: self.counterparty_payment_script.clone(),
value: Amount::from_sat(to_countersignatory_value),
};

CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
to_broadcaster_value, counterparty_txout, broadcaster_funding_key,
to_broadcaster_txout, counterparty_txout, broadcaster_funding_key,
countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs,
channel_parameters)
}
Expand Down Expand Up @@ -3507,15 +3512,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
let secret = self.get_secret(commitment_number).unwrap();
let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key);
let revocation_pubkey = RevocationKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, &self.holder_revocation_basepoint, &per_commitment_point,);
let delayed_key = DelayedPaymentKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, &self.counterparty_commitment_params.counterparty_delayed_payment_base_key, &PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key));

let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key);
let revokeable_p2wsh = revokeable_redeemscript.to_p2wsh();
let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, &per_commitment_point, &self.onchain_tx_handler.secp_ctx);

// First, process non-htlc outputs (to_holder & to_counterparty)
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == revokeable_p2wsh {
if outp.script_pubkey == revokeable_spk {
let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx());
let justice_package = PackageTemplate::build_package(
commitment_txid, idx as u32,
Expand Down
54 changes: 30 additions & 24 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,12 +1137,17 @@ impl HolderCommitmentTransaction {
for _ in 0..htlcs.len() {
counterparty_htlc_sigs.push(dummy_sig);
}
let broadcaster_payment_script = signer.get_revokeable_spk(false, 0, &keys.per_commitment_point, &secp_ctx);
let broadcaster_txout = TxOut {
script_pubkey: broadcaster_payment_script,
value: Amount::ZERO,
};
let counterparty_payment_script = signer.get_counterparty_payment_script(true);
let counterparty_txout = TxOut {
script_pubkey: counterparty_payment_script,
value: Amount::ZERO,
};
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, counterparty_txout, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, broadcaster_txout, counterparty_txout, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
HolderCommitmentTransaction {
inner,
Expand Down Expand Up @@ -1452,12 +1457,12 @@ impl CommitmentTransaction {
/// Only include HTLCs that are above the dust limit for the channel.
///
/// This is not exported to bindings users due to the generic though we likely should expose a version without
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_txout: TxOut, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat);
pub fn new_with_auxiliary_htlc_data<T>(commitment_number: u64, to_broadcaster_txout: TxOut, to_countersignatory_txout: TxOut, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction {
let to_broadcaster_value_sat = to_broadcaster_txout.value;
let to_countersignatory_value_sat = to_countersignatory_txout.value;

// Sort outputs and populate output indices while keeping track of the auxiliary data
let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_txout, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();
let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_txout, to_countersignatory_txout, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap();

let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters);
let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
Expand Down Expand Up @@ -1486,15 +1491,19 @@ impl CommitmentTransaction {
self
}

fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, to_countersignatory_spk: ScriptBuf) -> Result<BuiltCommitmentTransaction, ()> {
fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, to_broadcaster_spk: ScriptBuf, to_countersignatory_spk: ScriptBuf) -> Result<BuiltCommitmentTransaction, ()> {
let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters);

let to_broadcaster_txout = TxOut {
script_pubkey: to_broadcaster_spk,
value: self.to_broadcaster_value_sat,
};
let to_countersignatory_txout = TxOut {
script_pubkey: to_countersignatory_spk,
value: self.to_countersignatory_value_sat,
};
let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect();
let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, to_countersignatory_txout, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;
let (outputs, _) = Self::internal_build_outputs(keys, to_broadcaster_txout, to_countersignatory_txout, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?;

let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs);
let txid = transaction.compute_txid();
Expand All @@ -1518,9 +1527,7 @@ impl CommitmentTransaction {
// - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the
// caller needs to have sorted together with the HTLCs so it can keep track of the output index
// - building of a bitcoin transaction during a verify() call, in which case T is just ()
fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_txout: TxOut, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
let contest_delay = channel_parameters.contest_delay();

fn internal_build_outputs<T>(keys: &TxCreationKeys, to_broadcaster_txout: TxOut, to_countersignatory_txout: TxOut, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec<TxOut>, Vec<HTLCOutputInCommitment>), ()> {
let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new();

if to_countersignatory_txout.value > Amount::ZERO {
Expand All @@ -1530,23 +1537,15 @@ impl CommitmentTransaction {
))
}

if to_broadcaster_value_sat > Amount::ZERO {
let redeem_script = get_revokeable_redeemscript(
&keys.revocation_key,
contest_delay,
&keys.broadcaster_delayed_payment_key,
);
if to_broadcaster_txout.value > Amount::ZERO {
txouts.push((
TxOut {
script_pubkey: redeem_script.to_p2wsh(),
value: to_broadcaster_value_sat,
},
to_broadcaster_txout.clone(),
None,
));
}

if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
if to_broadcaster_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() {
if to_broadcaster_txout.value > Amount::ZERO || !htlcs_with_aux.is_empty() {
let anchor_script = get_anchor_redeemscript(broadcaster_funding_key);
txouts.push((
TxOut {
Expand Down Expand Up @@ -1682,14 +1681,14 @@ impl CommitmentTransaction {
///
/// An external validating signer must call this method before signing
/// or using the built transaction.
pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>, to_countersignatory_spk: ScriptBuf) -> Result<TrustedCommitmentTransaction, ()> {
pub fn verify<T: secp256k1::Signing + secp256k1::Verification>(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1<T>, to_broadcaster_spk: ScriptBuf, to_countersignatory_spk: ScriptBuf) -> Result<TrustedCommitmentTransaction, ()> {
// This is the only field of the key cache that we trust
let per_commitment_point = self.keys.per_commitment_point;
let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx);
if keys != self.keys {
return Err(());
}
let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, to_countersignatory_spk)?;
let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, to_broadcaster_spk, to_countersignatory_spk)?;
if self.built.transaction != tx.transaction || self.built.txid != tx.txid {
return Err(());
}
Expand Down Expand Up @@ -1897,7 +1896,7 @@ mod tests {
use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys};
use crate::chain;
use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment};
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use bitcoin::secp256k1::{self, PublicKey, SecretKey, Secp256k1};
use crate::util::test_utils;
use crate::sign::{ChannelSigner, SignerProvider};
use bitcoin::{Amount, TxOut, Network, Txid, ScriptBuf, CompressedPublicKey};
Expand All @@ -1921,6 +1920,7 @@ mod tests {
channel_parameters: ChannelTransactionParameters,
counterparty_pubkeys: ChannelPublicKeys,
signer: TestChannelSigner,
secp_ctx: Secp256k1::<secp256k1::All>,
}

impl TestCommitmentTxBuilder {
Expand Down Expand Up @@ -1957,18 +1957,24 @@ mod tests {
channel_parameters,
counterparty_pubkeys,
signer,
secp_ctx,
}
}

fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction {
let broadcaster_payment_script = self.signer.get_revokeable_spk(true, self.commitment_number, &self.keys.per_commitment_point, &self.secp_ctx);
let broadcaster_txout = TxOut {
script_pubkey: broadcaster_payment_script,
value: Amount::from_sat(to_broadcaster_sats),
};
let counterparty_payment_script = self.signer.get_counterparty_payment_script(false);
let counterparty_txout = TxOut {
script_pubkey: counterparty_payment_script,
value: Amount::from_sat(to_countersignatory_sats),
};
CommitmentTransaction::new_with_auxiliary_htlc_data(
self.commitment_number,
to_broadcaster_sats,
broadcaster_txout,
counterparty_txout,
self.holder_funding_pubkey.clone(),
self.counterparty_funding_pubkey.clone(),
Expand Down
7 changes: 6 additions & 1 deletion lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3164,13 +3164,18 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
let channel_parameters =
if local { self.channel_transaction_parameters.as_holder_broadcastable() }
else { self.channel_transaction_parameters.as_counterparty_broadcastable() };
let broadcaster_payment_script = self.holder_signer.as_ref().get_revokeable_spk(local, commitment_number, &keys.per_commitment_point, &self.secp_ctx);
let broadcaster_txout = TxOut {
script_pubkey: broadcaster_payment_script,
value: Amount::from_sat(value_to_a as u64),
};
let counterparty_payment_script = self.holder_signer.as_ref().get_counterparty_payment_script(!local);
let counterparty_txout = TxOut {
script_pubkey: counterparty_payment_script,
value: Amount::from_sat(value_to_b as u64),
};
let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number,
value_to_a as u64,
broadcaster_txout,
counterparty_txout,
funding_pubkey_a,
funding_pubkey_b,
Expand Down
14 changes: 12 additions & 2 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,11 @@ fn test_update_fee_that_funder_cannot_afford() {
|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
).flatten().unwrap();
let local_chan_signer = local_chan.get_signer();
let broadcaster_payment_script = local_chan_signer.as_ref().get_revokeable_spk(false, INITIAL_COMMITMENT_NUMBER - 1, &commit_tx_keys.per_commitment_point, &secp_ctx);
let broadcaster_txout = TxOut {
script_pubkey: broadcaster_payment_script,
value: Amount::from_sat(push_sats),
};
let counterparty_payment_script = local_chan_signer.as_ref().get_counterparty_payment_script(true);
let counterparty_txout = TxOut {
script_pubkey: counterparty_payment_script,
Expand All @@ -774,7 +779,7 @@ fn test_update_fee_that_funder_cannot_afford() {
let mut htlcs: Vec<(HTLCOutputInCommitment, ())> = vec![];
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
INITIAL_COMMITMENT_NUMBER - 1,
push_sats,
broadcaster_txout,
counterparty_txout,
local_funding, remote_funding,
commit_tx_keys.clone(),
Expand Down Expand Up @@ -1522,14 +1527,19 @@ fn test_fee_spike_violation_fails_htlc() {
|phase| if let ChannelPhase::Funded(chan) = phase { Some(chan) } else { None }
).flatten().unwrap();
let local_chan_signer = local_chan.get_signer();
let broadcaster_payment_script = local_chan_signer.as_ref().get_revokeable_spk(false, commitment_number, &commit_tx_keys.per_commitment_point, &secp_ctx);
let broadcaster_txout = TxOut {
script_pubkey: broadcaster_payment_script,
value: Amount::from_sat(95000),
};
let counterparty_payment_script = local_chan_signer.as_ref().get_counterparty_payment_script(true);
let counterparty_txout = TxOut {
script_pubkey: counterparty_payment_script,
value: Amount::from_sat(local_chan_balance),
};
let commitment_tx = CommitmentTransaction::new_with_auxiliary_htlc_data(
commitment_number,
95000,
broadcaster_txout,
counterparty_txout,
local_funding, remote_funding,
commit_tx_keys.clone(),
Expand Down
46 changes: 43 additions & 3 deletions lightning/src/sign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use crate::crypto::chacha20::ChaCha20;
use crate::io::{self, Error};
use crate::ln::msgs::DecodeError;
use crate::prelude::*;
use crate::sign::chan_utils::TxCreationKeys;
use crate::sign::ecdsa::EcdsaChannelSigner;
#[cfg(taproot)]
use crate::sign::taproot::TaprootChannelSigner;
Expand Down Expand Up @@ -796,13 +797,28 @@ pub trait ChannelSigner {
/// channel_parameters.is_populated() MUST be true.
fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters);

/// Returns the scriptpubkey that should be placed in the `to_remote` output of commitment
/// transactions. Assumes the signer has already been given the channel parameters via
/// Returns the script pubkey that should be placed in the `to_remote` output of commitment
/// transactions.
///
/// Assumes the signer has already been given the channel parameters via
/// `provide_channel_parameters`.
///
/// If `to_self` is set, return the `to_remote` script pubkey for the counterparty's commitment
/// If `to_self` is set, return the `to_remote` script pubkey for the remote party's commitment
/// transaction, otherwise, for the local party's.
fn get_counterparty_payment_script(&self, to_self: bool) -> ScriptBuf;

/// Returns the script pubkey that should be placed in the `to_local` output of commitment
/// transactions, and in the output of second level HTLC transactions.
///
/// Assumes the signer has already been given the channel parameters via
/// `provide_channel_parameters`.
///
/// If `to_self` is set, return the revokeable script pubkey for local party's
/// commitment / htlc transaction, otherwise, for the remote party's.
fn get_revokeable_spk(
&self, to_self: bool, commitment_number: u64, per_commitment_point: &PublicKey,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> ScriptBuf;
}

/// Specifies the recipient of an invoice.
Expand Down Expand Up @@ -1405,6 +1421,30 @@ impl ChannelSigner for InMemorySigner {
let payment_point = &params.countersignatory_pubkeys().payment_point;
get_counterparty_payment_script(params.channel_type_features(), payment_point)
}

fn get_revokeable_spk(
&self, to_self: bool, _commitment_number: u64, per_commitment_point: &PublicKey,
secp_ctx: &Secp256k1<secp256k1::All>,
) -> ScriptBuf {
let params = if to_self {
self.channel_parameters.as_ref().unwrap().as_holder_broadcastable()
} else {
self.channel_parameters.as_ref().unwrap().as_counterparty_broadcastable()
};
let contest_delay = params.contest_delay();
let keys = TxCreationKeys::from_channel_static_keys(
per_commitment_point,
params.broadcaster_pubkeys(),
params.countersignatory_pubkeys(),
secp_ctx,
);
get_revokeable_redeemscript(
&keys.revocation_key,
contest_delay,
&keys.broadcaster_delayed_payment_key,
)
.to_p2wsh()
}
}

const MISSING_PARAMS_ERR: &'static str =
Expand Down
Loading

0 comments on commit cbac0e7

Please sign in to comment.