Skip to content

Commit b3773a7

Browse files
committed
Make ChannelSigner solely responsible for validating commitment sigs
As part of the custom transactions project, we want to make channel generic over any funding, htlc, and revokeable scripts used on-chain. Hence, this commit removes the validation of holder commitment signatures from channel, as it requires building the funding, htlc, and revokeable scripts to create the expected sighash. This signature validation is now the sole responsibility of `ChannelSigner::validate_holder_commitment`. This commit updates `InMemorySigner` to honor this new API contract.
1 parent bed20cf commit b3773a7

File tree

5 files changed

+127
-109
lines changed

5 files changed

+127
-109
lines changed

lightning/src/ln/channel.rs

Lines changed: 50 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ use bitcoin::hashes::sha256::Hash as Sha256;
2121
use bitcoin::hashes::sha256d::Hash as Sha256d;
2222
use bitcoin::hashes::Hash;
2323

24-
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
25-
use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1};
26-
use bitcoin::secp256k1::{PublicKey, SecretKey};
27-
use bitcoin::{secp256k1, sighash};
24+
use bitcoin::secp256k1::{
25+
self, constants::PUBLIC_KEY_SIZE, ecdsa::Signature, PublicKey, Secp256k1, SecretKey,
26+
};
2827

2928
use crate::chain::chaininterface::{
3029
fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator,
@@ -2512,29 +2511,6 @@ where
25122511

25132512
fn received_msg(&self) -> &'static str;
25142513

2515-
#[rustfmt::skip]
2516-
fn check_counterparty_commitment_signature<L: Deref>(
2517-
&self, sig: &Signature, holder_commitment_point: &HolderCommitmentPoint, logger: &L
2518-
) -> Result<CommitmentTransaction, ChannelError> where L::Target: Logger {
2519-
let funding_script = self.funding().get_funding_redeemscript();
2520-
2521-
let commitment_data = self.context().build_commitment_transaction(self.funding(),
2522-
holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(),
2523-
true, false, logger);
2524-
let initial_commitment_tx = commitment_data.tx;
2525-
let trusted_tx = initial_commitment_tx.trust();
2526-
let initial_commitment_bitcoin_tx = trusted_tx.built_transaction();
2527-
let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.funding().get_value_satoshis());
2528-
// They sign the holder commitment transaction...
2529-
log_trace!(logger, "Checking {} tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.",
2530-
self.received_msg(), log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.funding().counterparty_funding_pubkey().serialize()),
2531-
encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]),
2532-
encode::serialize_hex(&funding_script), &self.context().channel_id());
2533-
secp_check!(self.context().secp_ctx.verify_ecdsa(&sighash, sig, self.funding().counterparty_funding_pubkey()), format!("Invalid {} signature from peer", self.received_msg()));
2534-
2535-
Ok(initial_commitment_tx)
2536-
}
2537-
25382514
#[rustfmt::skip]
25392515
fn initial_commitment_signed<L: Deref>(
25402516
&mut self, channel_id: ChannelId, counterparty_signature: Signature, holder_commitment_point: &mut HolderCommitmentPoint,
@@ -2543,32 +2519,11 @@ where
25432519
where
25442520
L::Target: Logger
25452521
{
2546-
let initial_commitment_tx = match self.check_counterparty_commitment_signature(&counterparty_signature, holder_commitment_point, logger) {
2547-
Ok(res) => res,
2548-
Err(ChannelError::Close(e)) => {
2549-
// TODO(dual_funding): Update for V2 established channels.
2550-
if !self.funding().is_outbound() {
2551-
self.funding_mut().channel_transaction_parameters.funding_outpoint = None;
2552-
}
2553-
return Err(ChannelError::Close(e));
2554-
},
2555-
Err(e) => {
2556-
// The only error we know how to handle is ChannelError::Close, so we fall over here
2557-
// to make sure we don't continue with an inconsistent state.
2558-
panic!("unexpected error type from check_counterparty_commitment_signature {:?}", e);
2559-
}
2560-
};
25612522
let context = self.context();
2562-
let commitment_data = context.build_commitment_transaction(self.funding(),
2563-
context.cur_counterparty_commitment_transaction_number,
2564-
&context.counterparty_cur_commitment_point.unwrap(), false, false, logger);
2565-
let counterparty_initial_commitment_tx = commitment_data.tx;
2566-
let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust();
2567-
let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction();
2568-
2569-
log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}",
2570-
&context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
25712523

2524+
let initial_commitment_tx = context.build_commitment_transaction(self.funding(),
2525+
holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(),
2526+
true, false, logger).tx;
25722527
let holder_commitment_tx = HolderCommitmentTransaction::new(
25732528
initial_commitment_tx,
25742529
counterparty_signature,
@@ -2577,10 +2532,29 @@ where
25772532
&self.funding().counterparty_funding_pubkey()
25782533
);
25792534

2580-
if context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()).is_err() {
2535+
log_trace!(logger, "Validating {} commitment in channel {}", self.received_msg(), context.channel_id());
2536+
if context.holder_signer.as_ref().validate_holder_commitment(
2537+
&self.funding().channel_transaction_parameters,
2538+
&holder_commitment_tx,
2539+
Vec::new(),
2540+
&context.secp_ctx,
2541+
).is_err() {
2542+
if !self.funding().is_outbound() {
2543+
self.funding_mut().channel_transaction_parameters.funding_outpoint = None;
2544+
}
25812545
return Err(ChannelError::close("Failed to validate our commitment".to_owned()));
25822546
}
25832547

2548+
let commitment_data = context.build_commitment_transaction(self.funding(),
2549+
context.cur_counterparty_commitment_transaction_number,
2550+
&context.counterparty_cur_commitment_point.unwrap(), false, false, logger);
2551+
let counterparty_initial_commitment_tx = commitment_data.tx;
2552+
let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust();
2553+
let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction();
2554+
2555+
log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}",
2556+
&context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction));
2557+
25842558
// Now that we're past error-generating stuff, update our local state:
25852559

25862560
let is_v2_established = self.is_v2_established();
@@ -4207,25 +4181,30 @@ where
42074181
where
42084182
L::Target: Logger,
42094183
{
4210-
let funding_script = funding.get_funding_redeemscript();
4211-
42124184
let commitment_data = self.build_commitment_transaction(funding,
42134185
holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(),
42144186
true, false, logger);
4215-
let commitment_txid = {
4216-
let trusted_tx = commitment_data.tx.trust();
4217-
let bitcoin_tx = trusted_tx.built_transaction();
4218-
let sighash = bitcoin_tx.get_sighash_all(&funding_script, funding.get_value_satoshis());
4219-
4220-
log_trace!(logger, "Checking commitment tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}",
4221-
log_bytes!(msg.signature.serialize_compact()[..]),
4222-
log_bytes!(funding.counterparty_funding_pubkey().serialize()), encode::serialize_hex(&bitcoin_tx.transaction),
4223-
log_bytes!(sighash[..]), encode::serialize_hex(&funding_script), &self.channel_id());
4224-
if let Err(_) = self.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &funding.counterparty_funding_pubkey()) {
4225-
return Err(ChannelError::close("Invalid commitment tx signature from peer".to_owned()));
4226-
}
4227-
bitcoin_tx.txid
4228-
};
4187+
4188+
if msg.htlc_signatures.len() != commitment_data.tx.nondust_htlcs().len() {
4189+
return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_data.tx.nondust_htlcs().len())));
4190+
}
4191+
4192+
let holder_commitment_tx = HolderCommitmentTransaction::new(
4193+
commitment_data.tx,
4194+
msg.signature,
4195+
msg.htlc_signatures.clone(),
4196+
&funding.get_holder_pubkeys().funding_pubkey,
4197+
funding.counterparty_funding_pubkey()
4198+
);
4199+
4200+
log_trace!(logger, "Validating commitment_signed commitment in channel {}", self.channel_id());
4201+
self.holder_signer.as_ref().validate_holder_commitment(
4202+
&funding.channel_transaction_parameters,
4203+
&holder_commitment_tx,
4204+
commitment_data.outbound_htlc_preimages,
4205+
&self.secp_ctx,
4206+
)
4207+
.map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?;
42294208

42304209
// If our counterparty updated the channel fee in this commitment transaction, check that
42314210
// they can actually afford the new fee now.
@@ -4257,28 +4236,10 @@ where
42574236
}
42584237
}
42594238

4260-
if msg.htlc_signatures.len() != commitment_data.tx.nondust_htlcs().len() {
4261-
return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_data.tx.nondust_htlcs().len())));
4262-
}
4263-
4264-
let holder_keys = commitment_data.tx.trust().keys();
4265-
let mut nondust_htlc_sources = Vec::with_capacity(commitment_data.tx.nondust_htlcs().len());
4266-
let mut dust_htlcs = Vec::with_capacity(commitment_data.htlcs_included.len() - commitment_data.tx.nondust_htlcs().len());
4267-
for (idx, (htlc, mut source_opt)) in commitment_data.htlcs_included.into_iter().enumerate() {
4239+
let mut nondust_htlc_sources = Vec::with_capacity(holder_commitment_tx.nondust_htlcs().len());
4240+
let mut dust_htlcs = Vec::with_capacity(commitment_data.htlcs_included.len() - holder_commitment_tx.nondust_htlcs().len());
4241+
for (htlc, mut source_opt) in commitment_data.htlcs_included.into_iter() {
42684242
if let Some(_) = htlc.transaction_output_index {
4269-
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_data.tx.feerate_per_kw(),
4270-
funding.get_counterparty_selected_contest_delay().unwrap(), &htlc, funding.get_channel_type(),
4271-
&holder_keys.broadcaster_delayed_payment_key, &holder_keys.revocation_key);
4272-
4273-
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, funding.get_channel_type(), &holder_keys);
4274-
let htlc_sighashtype = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
4275-
let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).p2wsh_signature_hash(0, &htlc_redeemscript, htlc.to_bitcoin_amount(), htlc_sighashtype).unwrap()[..]);
4276-
log_trace!(logger, "Checking HTLC tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}.",
4277-
log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(holder_keys.countersignatory_htlc_key.to_public_key().serialize()),
4278-
encode::serialize_hex(&htlc_tx), log_bytes!(htlc_sighash[..]), encode::serialize_hex(&htlc_redeemscript), &self.channel_id());
4279-
if let Err(_) = self.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &holder_keys.countersignatory_htlc_key.to_public_key()) {
4280-
return Err(ChannelError::close("Invalid HTLC tx signature from peer".to_owned()));
4281-
}
42824243
if htlc.offered {
42834244
if let Some(source) = source_opt.take() {
42844245
nondust_htlc_sources.push(source.clone());
@@ -4292,17 +4253,6 @@ where
42924253
debug_assert!(source_opt.is_none(), "HTLCSource should have been put somewhere");
42934254
}
42944255

4295-
let holder_commitment_tx = HolderCommitmentTransaction::new(
4296-
commitment_data.tx,
4297-
msg.signature,
4298-
msg.htlc_signatures.clone(),
4299-
&funding.get_holder_pubkeys().funding_pubkey,
4300-
funding.counterparty_funding_pubkey()
4301-
);
4302-
4303-
self.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_data.outbound_htlc_preimages)
4304-
.map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?;
4305-
43064256
Ok(LatestHolderCommitmentTXInfo {
43074257
commitment_tx: holder_commitment_tx,
43084258
htlc_outputs: dust_htlcs,

lightning/src/ln/functional_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8892,7 +8892,7 @@ pub fn test_duplicate_funding_err_in_funding() {
88928892
funding_created_msg.funding_output_index += 10;
88938893
nodes[1].node.handle_funding_created(node_c_id, &funding_created_msg);
88948894
get_err_msg(&nodes[1], &node_c_id);
8895-
let err = "Invalid funding_created signature from peer".to_owned();
8895+
let err = "Failed to validate our commitment".to_owned();
88968896
let reason = ClosureReason::ProcessingError { err };
88978897
let expected_closing = ExpectedCloseEvent::from_id_reason(real_channel_id, false, reason);
88988898
check_closed_events(&nodes[1], &[expected_closing]);

lightning/src/sign/mod.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,8 +763,9 @@ pub trait ChannelSigner {
763763
/// closed. If you wish to make this operation asynchronous, you should instead return `Ok(())`
764764
/// and pause future signing operations until this validation completes.
765765
fn validate_holder_commitment(
766-
&self, holder_tx: &HolderCommitmentTransaction,
767-
outbound_htlc_preimages: Vec<PaymentPreimage>,
766+
&self, channel_parameters: &ChannelTransactionParameters,
767+
holder_tx: &HolderCommitmentTransaction, outbound_htlc_preimages: Vec<PaymentPreimage>,
768+
secp_ctx: &Secp256k1<secp256k1::All>,
768769
) -> Result<(), ()>;
769770

770771
/// Validate the counterparty's revocation.
@@ -1362,9 +1363,68 @@ impl ChannelSigner for InMemorySigner {
13621363
}
13631364

13641365
fn validate_holder_commitment(
1365-
&self, _holder_tx: &HolderCommitmentTransaction,
1366-
_outbound_htlc_preimages: Vec<PaymentPreimage>,
1366+
&self, channel_parameters: &ChannelTransactionParameters,
1367+
holder_tx: &HolderCommitmentTransaction, _outbound_htlc_preimages: Vec<PaymentPreimage>,
1368+
secp_ctx: &secp256k1::Secp256k1<secp256k1::All>,
13671369
) -> Result<(), ()> {
1370+
let counterparty_funding_pubkey =
1371+
&channel_parameters.counterparty_pubkeys().as_ref().unwrap().funding_pubkey;
1372+
let funding_script = channel_parameters.make_funding_redeemscript();
1373+
let channel_value_satoshis = channel_parameters.channel_value_satoshis;
1374+
let trusted_tx = holder_tx.trust();
1375+
let bitcoin_tx = trusted_tx.built_transaction();
1376+
let sighash = bitcoin_tx.get_sighash_all(&funding_script, channel_value_satoshis);
1377+
1378+
secp_ctx
1379+
.verify_ecdsa(&sighash, &holder_tx.counterparty_sig, counterparty_funding_pubkey)
1380+
.map_err(|_| ())?;
1381+
1382+
let holder_keys = trusted_tx.keys();
1383+
1384+
if holder_tx.nondust_htlcs().len() != holder_tx.counterparty_htlc_sigs.len() {
1385+
return Err(());
1386+
}
1387+
for (htlc, sig) in
1388+
holder_tx.nondust_htlcs().iter().zip(holder_tx.counterparty_htlc_sigs.iter())
1389+
{
1390+
let htlc_tx = chan_utils::build_htlc_transaction(
1391+
&bitcoin_tx.txid,
1392+
holder_tx.feerate_per_kw(),
1393+
channel_parameters.as_holder_broadcastable().contest_delay(),
1394+
&htlc,
1395+
&channel_parameters.channel_type_features,
1396+
&holder_keys.broadcaster_delayed_payment_key,
1397+
&holder_keys.revocation_key,
1398+
);
1399+
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(
1400+
&htlc,
1401+
&channel_parameters.channel_type_features,
1402+
&holder_keys,
1403+
);
1404+
let htlc_sighashtype =
1405+
if channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() {
1406+
EcdsaSighashType::SinglePlusAnyoneCanPay
1407+
} else {
1408+
EcdsaSighashType::All
1409+
};
1410+
let htlc_sighash = hash_to_message!(
1411+
&sighash::SighashCache::new(&htlc_tx)
1412+
.p2wsh_signature_hash(
1413+
0,
1414+
&htlc_redeemscript,
1415+
htlc.to_bitcoin_amount(),
1416+
htlc_sighashtype
1417+
)
1418+
.unwrap()[..]
1419+
);
1420+
secp_ctx
1421+
.verify_ecdsa(
1422+
&htlc_sighash,
1423+
&sig,
1424+
&holder_keys.countersignatory_htlc_key.to_public_key(),
1425+
)
1426+
.map_err(|_| ())?;
1427+
}
13681428
Ok(())
13691429
}
13701430

lightning/src/util/dyn_signer.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,10 @@ delegate!(DynSigner, ChannelSigner,
172172
) -> Result<PublicKey, ()>,
173173
fn release_commitment_secret(, idx: u64) -> Result<[u8; 32], ()>,
174174
fn validate_holder_commitment(,
175+
channel_parameters: &ChannelTransactionParameters,
175176
holder_tx: &HolderCommitmentTransaction,
176-
preimages: Vec<PaymentPreimage>
177+
preimages: Vec<PaymentPreimage>,
178+
secp_ctx: &Secp256k1<All>
177179
) -> Result<(), ()>,
178180
fn pubkeys(,
179181
splice_parent_funding_txid: Option<Txid>, secp_ctx: &Secp256k1<secp256k1::All>

lightning/src/util/test_channel_signer.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,9 @@ impl ChannelSigner for TestChannelSigner {
191191
}
192192

193193
fn validate_holder_commitment(
194-
&self, holder_tx: &HolderCommitmentTransaction,
195-
_outbound_htlc_preimages: Vec<PaymentPreimage>,
194+
&self, channel_parameters: &ChannelTransactionParameters,
195+
holder_tx: &HolderCommitmentTransaction, outbound_htlc_preimages: Vec<PaymentPreimage>,
196+
secp_ctx: &Secp256k1<secp256k1::All>,
196197
) -> Result<(), ()> {
197198
let mut state = self.state.lock().unwrap();
198199
let idx = holder_tx.commitment_number();
@@ -205,7 +206,12 @@ impl ChannelSigner for TestChannelSigner {
205206
);
206207
}
207208
state.last_holder_commitment = idx;
208-
Ok(())
209+
self.inner.validate_holder_commitment(
210+
channel_parameters,
211+
holder_tx,
212+
outbound_htlc_preimages,
213+
secp_ctx,
214+
)
209215
}
210216

211217
fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> {

0 commit comments

Comments
 (0)