Skip to content

Commit 23cc06c

Browse files
committed
Verify the holder provided valid witnesses and uses SIGHASH_ALL
LDK checks the following: * Each input spends an output that is one of P2WPKH, P2WSH, or P2TR. These were already checked by LDK when the inputs to be contributed were provided. * All signatures use the `SIGHASH_ALL` sighash type. * P2WPKH and P2TR key path spends are valid (verifies signatures) NOTE: * When checking P2WSH spends, LDK tries to decode 70-72 byte witness elements as ECDSA signatures with a sighash flag. If the internal DER-decoding fails, then LDK just assumes it wasn't a signature and carries with checks. If the element can be decoded as an ECDSA signature, the the sighash flag must be `SIGHASH_ALL`. * When checking P2TR script-path spends, LDK assumes all elements of exactly 65 bytes with the last byte matching any valid sighash flag byte are schnorr signatures and checks that the sighash type is `SIGHASH_ALL`. If the last byte is not any valid sighash flag, the element is assumed not to be a signature and is ignored. Elements of 64 bytes are not checked because if they were schnorr signatures then they would implicitly be `SIGHASH_DEFAULT` which is an alias of `SIGHASH_ALL`.
1 parent 1892549 commit 23cc06c

File tree

3 files changed

+191
-16
lines changed

3 files changed

+191
-16
lines changed

lightning/src/ln/channel.rs

Lines changed: 156 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use bitcoin::amount::Amount;
1212
use bitcoin::consensus::encode;
1313
use bitcoin::constants::ChainHash;
1414
use bitcoin::script::{Builder, Script, ScriptBuf, WScriptHash};
15-
use bitcoin::sighash::EcdsaSighashType;
15+
use bitcoin::sighash::{EcdsaSighashType, SighashCache};
1616
use bitcoin::transaction::{Transaction, TxIn, TxOut};
17-
use bitcoin::{Weight, Witness};
17+
use bitcoin::{TapSighashType, Weight, Witness, XOnlyPublicKey};
1818

1919
use bitcoin::hash_types::{BlockHash, Txid};
2020
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -23,7 +23,7 @@ use bitcoin::hashes::Hash;
2323

2424
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
2525
use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1};
26-
use bitcoin::secp256k1::{PublicKey, SecretKey};
26+
use bitcoin::secp256k1::{Message, PublicKey, SecretKey};
2727
use bitcoin::{secp256k1, sighash};
2828

2929
use crate::chain::chaininterface::{
@@ -7625,11 +7625,159 @@ where
76257625
}
76267626
}
76277627

7628-
fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec<Witness>) {
7629-
if let Some(ref mut _signing_session) = self.interactive_tx_signing_session {
7630-
// Check that sighash_all was used:
7631-
// TODO(dual_funding): Check sig for sighash
7628+
fn verify_interactive_tx_signatures(
7629+
&mut self, witnesses: &Vec<Witness>,
7630+
) -> Result<(), APIError> {
7631+
if let Some(session) = &self.interactive_tx_signing_session {
7632+
let unsigned_tx = session.unsigned_tx();
7633+
let built_tx = unsigned_tx.build_unsigned_tx();
7634+
let prev_outputs: Vec<&TxOut> =
7635+
unsigned_tx.inputs().map(|input| input.prev_output()).collect::<Vec<_>>();
7636+
let all_prevouts = sighash::Prevouts::All(&prev_outputs[..]);
7637+
7638+
let mut cache = SighashCache::new(&built_tx);
7639+
let secp = Secp256k1::verification_only();
7640+
7641+
let script_pubkeys = unsigned_tx
7642+
.inputs()
7643+
.enumerate()
7644+
.filter(|(_, input)| input.is_local(unsigned_tx.holder_is_initiator()));
7645+
7646+
'inputs: for ((input_idx, input), witness) in script_pubkeys.zip(witnesses) {
7647+
let prev_output = input.prev_output();
7648+
let script_pubkey = &prev_output.script_pubkey;
7649+
7650+
// P2WPKH
7651+
if script_pubkey.is_p2wpkh() {
7652+
if witness.len() != 2 {
7653+
return Err(APIError::APIMisuseError {
7654+
err: format!(
7655+
"The witness for input at index {input_idx} does not have the correct number of elements for a P2WPKH spend. Expected 2 got {}",
7656+
witness.len()
7657+
),
7658+
});
7659+
}
7660+
let sighash = cache
7661+
.p2wpkh_signature_hash(
7662+
input_idx,
7663+
script_pubkey,
7664+
prev_output.value,
7665+
EcdsaSighashType::All,
7666+
)
7667+
.map_err(|_| {
7668+
debug_assert!(
7669+
false,
7670+
"Funding transaction sighash should be calculable"
7671+
);
7672+
APIError::APIMisuseError {
7673+
err: "The transaction sighash could not be calculated".to_string(),
7674+
}
7675+
})?;
7676+
let msg = Message::from_digest_slice(&sighash[..])
7677+
.expect("Sighash is a SHA256 which is 32 bytes long");
7678+
let pubkey = PublicKey::from_slice(&witness[1]).map_err(|_| APIError::APIMisuseError { err: format!("The witness for input at index {input_idx} contains an invalid ECDSA public key") })?;
7679+
let sig = bitcoin::ecdsa::Signature::from_slice(&witness[0]).map_err(|_| APIError::APIMisuseError { err: format!("The witness for input at index {input_idx} contains an invalid signature") })?;
7680+
secp.verify_ecdsa(&msg, &sig.signature, &pubkey).map_err(|_| {
7681+
APIError::APIMisuseError { err:
7682+
format!("Failed signature verification for input at index {input_idx} for P2WPKH spend.") }
7683+
})?;
7684+
continue 'inputs;
7685+
}
7686+
7687+
// P2TR key path spend witness includes signature and optional annex
7688+
if script_pubkey.is_p2tr() && witness.len() <= 2 {
7689+
if witness.len() == 0 {
7690+
return Err(APIError::APIMisuseError {
7691+
err: format!(
7692+
"The witness for input at index {input_idx} for a P2TR spend has 0 elements, which is invalid",
7693+
),
7694+
});
7695+
}
7696+
let sighash = cache
7697+
.taproot_key_spend_signature_hash(
7698+
input_idx,
7699+
&all_prevouts,
7700+
TapSighashType::All,
7701+
)
7702+
.map_err(|_| {
7703+
debug_assert!(
7704+
false,
7705+
"Funding transaction sighash should be calculable"
7706+
);
7707+
APIError::APIMisuseError {
7708+
err: "The transaction sighash could not be calculated".to_string(),
7709+
}
7710+
})?;
7711+
let msg = Message::from_digest_slice(&sighash[..])
7712+
.expect("Sighash is a SHA256 which is 32 bytes long");
7713+
let pubkey = match script_pubkey.instructions().collect::<Vec<Result<bitcoin::script::Instruction, _>>>()[1] {
7714+
Ok(bitcoin::script::Instruction::PushBytes(push_bytes)) => {
7715+
XOnlyPublicKey::from_slice(push_bytes.as_bytes())
7716+
},
7717+
_ => return Err(APIError::APIMisuseError { err: format!("The scriptPubKey of the previous output for input at index {input_idx} for a P2TR key path spend is invalid") }),
7718+
}.map_err(|_| APIError::APIMisuseError { err: format!("The scriptPubKey of the previous output for input at index {input_idx} for a P2TR key path spend has an invalid public key") })?;
7719+
let sig = bitcoin::taproot::Signature::from_slice(&witness[0]).map_err(|_| APIError::APIMisuseError { err: format!("The witness for input at index {input_idx} for a P2TR key path spend has an invalid signature") })?;
7720+
secp.verify_schnorr(&sig.signature, &msg, &pubkey).map_err(|_| {
7721+
APIError::APIMisuseError { err: format!("Failed signature verification for input at index {input_idx} for P2WPKH spend.") }
7722+
})?;
7723+
continue 'inputs;
7724+
}
7725+
7726+
// P2WSH - No validation just sighash checks
7727+
if script_pubkey.is_p2wsh() {
7728+
'elements: for element in witness {
7729+
match element.len() {
7730+
// Possibly a DER-encoded ECDSA signature with a sighash type byte assuming low-S
7731+
70..=72 => {
7732+
if bitcoin::ecdsa::Signature::from_slice(element)
7733+
.map(|sig| matches!(sig.sighash_type, EcdsaSighashType::All))
7734+
.unwrap_or(true)
7735+
{
7736+
continue 'elements;
7737+
}
7738+
7739+
return Err(APIError::APIMisuseError {
7740+
err: format!(
7741+
"An ECDSA signature in the witness for input {input_idx} does not use SIGHASH_ALL"
7742+
),
7743+
});
7744+
},
7745+
_ => continue 'elements,
7746+
}
7747+
}
7748+
continue 'inputs;
7749+
}
7750+
7751+
// P2TR script path - No validation, just sighash checks
7752+
if script_pubkey.is_p2tr() {
7753+
'elements: for element in witness {
7754+
match element.len() {
7755+
// Schnorr sig + sighash type byte.
7756+
// If this were just 64 bytes, it would implicitly be SIGHASH_DEFAULT (= SIGHASH_ALL)
7757+
65 => {
7758+
if bitcoin::taproot::Signature::from_slice(element)
7759+
.map(|sig| matches!(sig.sighash_type, TapSighashType::All))
7760+
.unwrap_or(true)
7761+
{
7762+
continue 'elements;
7763+
}
7764+
7765+
return Err(APIError::APIMisuseError {
7766+
err:
7767+
format!("A (likely) Schnorr signature in the witness for input {input_idx} does not use SIGHASH_DEFAULT or SIGHASH_ALL")
7768+
});
7769+
},
7770+
_ => continue 'elements,
7771+
}
7772+
}
7773+
continue 'inputs;
7774+
}
7775+
7776+
debug_assert!(false, "We don't allow contributing inputs that are not spending P2WPKH, P2WSH, or P2TR");
7777+
return Err(APIError::APIMisuseError { err: format!("Input at index {input_idx} does not spend from one of P2WPKH, P2WSH, or P2TR") });
7778+
}
76327779
}
7780+
Ok(())
76337781
}
76347782

76357783
pub fn funding_transaction_signed<L: Deref>(
@@ -7638,7 +7786,7 @@ where
76387786
where
76397787
L::Target: Logger,
76407788
{
7641-
self.verify_interactive_tx_signatures(&witnesses);
7789+
self.verify_interactive_tx_signatures(&witnesses)?;
76427790
if let Some(ref mut signing_session) = self.interactive_tx_signing_session {
76437791
let logger = WithChannelContext::from(logger, &self.context, None);
76447792
if let Some(holder_tx_signatures) = signing_session

lightning/src/ln/channelmanager.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5899,19 +5899,32 @@ where
58995899
/// counterparty's signature(s) the funding transaction will automatically be broadcast via the
59005900
/// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
59015901
///
5902-
/// `SIGHASH_ALL` MUST be used for all signatures when providing signatures.
5903-
///
5904-
/// <div class="warning">
5905-
/// WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which
5906-
/// will prevent the funding transaction from being relayed on the bitcoin network and hence being
5907-
/// confirmed.
5908-
/// </div>
5902+
/// `SIGHASH_ALL` MUST be used for all signatures when providing signatures, otherwise your
5903+
/// funds can be held hostage!
5904+
///
5905+
/// LDK checks the following:
5906+
/// * Each input spends an output that is one of P2WPKH, P2WSH, or P2TR.
5907+
/// These were already checked by LDK when the inputs to be contributed were provided.
5908+
/// * All signatures use the `SIGHASH_ALL` sighash type.
5909+
/// * P2WPKH and P2TR key path spends are valid (verifies signatures)
5910+
///
5911+
/// NOTE:
5912+
/// * When checking P2WSH spends, LDK tries to decode 70-72 byte witness elements as ECDSA
5913+
/// signatures with a sighash flag. If the internal DER-decoding fails, then LDK just
5914+
/// assumes it wasn't a signature and carries with checks. If the element can be decoded
5915+
/// as an ECDSA signature, the the sighash flag must be `SIGHASH_ALL`.
5916+
/// * When checking P2TR script-path spends, LDK assumes all elements of exactly 65 bytes
5917+
/// with the last byte matching any valid sighash flag byte are schnorr signatures and checks
5918+
/// that the sighash type is `SIGHASH_ALL`. If the last byte is not any valid sighash flag, the
5919+
/// element is assumed not to be a signature and is ignored. Elements of 64 bytes are not
5920+
/// checked because if they were schnorr signatures then they would implicitly be `SIGHASH_DEFAULT`
5921+
/// which is an alias of `SIGHASH_ALL`.
59095922
///
59105923
/// Returns [`ChannelUnavailable`] when a channel is not found or an incorrect
59115924
/// `counterparty_node_id` is provided.
59125925
///
59135926
/// Returns [`APIMisuseError`] when a channel is not in a state where it is expecting funding
5914-
/// signatures.
5927+
/// signatures or if any of the checks described above fail.
59155928
///
59165929
/// [`ChannelUnavailable`]: APIError::ChannelUnavailable
59175930
/// [`APIMisuseError`]: APIError::APIMisuseError

lightning/src/ln/interactivetxs.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ pub(crate) struct NegotiatedTxInput {
213213
prev_output: TxOut,
214214
}
215215

216+
impl NegotiatedTxInput {
217+
pub(super) fn is_local(&self, holder_is_initiator: bool) -> bool {
218+
!is_serial_id_valid_for_counterparty(holder_is_initiator, self.serial_id)
219+
}
220+
221+
pub(super) fn prev_output(&self) -> &TxOut {
222+
&self.prev_output
223+
}
224+
}
225+
216226
impl_writeable_tlv_based!(NegotiatedTxInput, {
217227
(1, serial_id, required),
218228
(3, txin, required),
@@ -363,6 +373,10 @@ impl ConstructedTransaction {
363373
.zip(witnesses)
364374
.for_each(|(input, witness)| input.witness = witness);
365375
}
376+
377+
pub fn holder_is_initiator(&self) -> bool {
378+
self.holder_is_initiator
379+
}
366380
}
367381

368382
/// The InteractiveTxSigningSession coordinates the signing flow of interactively constructed

0 commit comments

Comments
 (0)