Skip to content

Commit 3ec11de

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 982fd2b commit 3ec11de

File tree

3 files changed

+173
-16
lines changed

3 files changed

+173
-16
lines changed

lightning/src/ln/channel.rs

Lines changed: 138 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::{
@@ -7586,11 +7586,141 @@ where
75867586
}
75877587
}
75887588

7589-
fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec<Witness>) {
7590-
if let Some(ref mut _signing_session) = self.interactive_tx_signing_session {
7591-
// Check that sighash_all was used:
7592-
// TODO(dual_funding): Check sig for sighash
7589+
fn verify_interactive_tx_signatures(
7590+
&mut self, witnesses: &Vec<Witness>,
7591+
) -> Result<(), APIError> {
7592+
if let Some(session) = &self.interactive_tx_signing_session {
7593+
let unsigned_tx = session.unsigned_tx();
7594+
let built_tx = unsigned_tx.build_unsigned_tx();
7595+
let prev_outputs: Vec<&TxOut> =
7596+
unsigned_tx.inputs().map(|input| input.prev_output()).collect::<Vec<_>>();
7597+
let all_prevouts = sighash::Prevouts::All(&prev_outputs[..]);
7598+
7599+
let mut cache = SighashCache::new(&built_tx);
7600+
let secp = Secp256k1::verification_only();
7601+
7602+
let script_pubkeys = unsigned_tx
7603+
.inputs()
7604+
.enumerate()
7605+
.filter(|(_, input)| input.is_local(unsigned_tx.holder_is_initiator()));
7606+
7607+
'inputs: for ((input_idx, input), witness) in script_pubkeys.zip(witnesses) {
7608+
let prev_output = input.prev_output();
7609+
let script_pubkey = &prev_output.script_pubkey;
7610+
7611+
// P2WPKH
7612+
if script_pubkey.is_p2wpkh() {
7613+
if witness.len() != 2 {
7614+
return Err(APIError::APIMisuseError {
7615+
err: format!(
7616+
"The witness for input at index {input_idx} does not have the correct number of elements for a P2WPKH spend. Expected 2 got {}",
7617+
witness.len()
7618+
),
7619+
});
7620+
}
7621+
let sighash = cache
7622+
.p2wpkh_signature_hash(
7623+
input_idx,
7624+
script_pubkey,
7625+
prev_output.value,
7626+
EcdsaSighashType::All,
7627+
)
7628+
.expect("Transaction is ready for signing");
7629+
let msg = Message::from_digest_slice(&sighash[..]).unwrap();
7630+
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") })?;
7631+
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") })?;
7632+
secp.verify_ecdsa(&msg, &sig.signature, &pubkey).map_err(|_| {
7633+
APIError::APIMisuseError { err:
7634+
format!("Failed signature verification for input at index {input_idx} for P2WPKH spend.") }
7635+
})?;
7636+
continue 'inputs;
7637+
}
7638+
7639+
// P2TR key path spend witness includes signature and optional annex
7640+
if script_pubkey.is_p2tr() && witness.len() <= 2 {
7641+
if witness.len() == 0 {
7642+
return Err(APIError::APIMisuseError {
7643+
err: format!(
7644+
"The witness for input at index {input_idx} for a P2TR spend has 0 elements, which is invalid",
7645+
),
7646+
});
7647+
}
7648+
let sighash = cache
7649+
.taproot_key_spend_signature_hash(
7650+
input_idx,
7651+
&all_prevouts,
7652+
TapSighashType::All,
7653+
)
7654+
.expect("Transaction is ready for signing");
7655+
let msg = Message::from_digest_slice(&sighash[..]).unwrap();
7656+
let pubkey = match script_pubkey.instructions().collect::<Vec<Result<bitcoin::script::Instruction, _>>>()[1] {
7657+
Ok(bitcoin::script::Instruction::PushBytes(push_bytes)) => {
7658+
XOnlyPublicKey::from_slice(push_bytes.as_bytes())
7659+
},
7660+
_ => return 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") }),
7661+
}.map_err(|_| APIError::APIMisuseError { err: format!("") })?;
7662+
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") })?;
7663+
secp.verify_schnorr(&sig.signature, &msg, &pubkey).map_err(|_| {
7664+
APIError::APIMisuseError { err: format!("Failed signature verification for input at index {input_idx} for P2WPKH spend.") }
7665+
})?;
7666+
continue 'inputs;
7667+
}
7668+
7669+
// P2WSH - No validation just sighash checks
7670+
if script_pubkey.is_p2wsh() {
7671+
'elements: for element in witness {
7672+
match element.len() {
7673+
// Possibly a DER-encoded ECDSA signature with a sighash type byte assuming low-S
7674+
70..72 => {
7675+
if bitcoin::ecdsa::Signature::from_slice(element)
7676+
.map(|sig| matches!(sig.sighash_type, EcdsaSighashType::All))
7677+
.unwrap_or(true)
7678+
{
7679+
continue 'elements;
7680+
}
7681+
7682+
return Err(APIError::APIMisuseError {
7683+
err: format!(
7684+
"An ECDSA signature in the witness for input {input_idx} does not use SIGHASH_ALL"
7685+
),
7686+
});
7687+
},
7688+
_ => continue 'elements,
7689+
}
7690+
}
7691+
continue 'inputs;
7692+
}
7693+
7694+
// P2TR script path - No validation, just sighash checks
7695+
if script_pubkey.is_p2tr() {
7696+
'elements: for element in witness {
7697+
match element.len() {
7698+
// Schnorr sig + sighash type byte.
7699+
// If this were just 64 bytes, it would implicitly be SIGHASH_DEFAULT (= SIGHASH_ALL)
7700+
65 => {
7701+
if bitcoin::taproot::Signature::from_slice(element)
7702+
.map(|sig| matches!(sig.sighash_type, TapSighashType::All))
7703+
.unwrap_or(true)
7704+
{
7705+
continue 'elements;
7706+
}
7707+
7708+
return Err(APIError::APIMisuseError {
7709+
err:
7710+
format!("A (likely) Schnorr signature in the witness for input {input_idx} does not use SIGHASH_DEFAULT or SIGHASH_ALL")
7711+
});
7712+
},
7713+
_ => continue 'elements,
7714+
}
7715+
}
7716+
continue 'inputs;
7717+
}
7718+
7719+
debug_assert!(false, "We don't allow contributing inputs that are not spending P2WPKH, P2WSH, or P2TR");
7720+
return Err(APIError::APIMisuseError { err: format!("Input at index {input_idx} does not spend from one of P2WPKH, P2WSH, or P2TR") });
7721+
}
75937722
}
7723+
Ok(())
75947724
}
75957725

75967726
pub fn funding_transaction_signed<L: Deref>(
@@ -7599,7 +7729,7 @@ where
75997729
where
76007730
L::Target: Logger,
76017731
{
7602-
self.verify_interactive_tx_signatures(&witnesses);
7732+
self.verify_interactive_tx_signatures(&witnesses)?;
76037733
if let Some(ref mut signing_session) = self.interactive_tx_signing_session {
76047734
let logger = WithChannelContext::from(logger, &self.context, None);
76057735
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
@@ -5897,19 +5897,32 @@ where
58975897
/// counterparty's signature(s) the funding transaction will automatically be broadcast via the
58985898
/// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
58995899
///
5900-
/// `SIGHASH_ALL` MUST be used for all signatures when providing signatures.
5901-
///
5902-
/// <div class="warning">
5903-
/// WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which
5904-
/// will prevent the funding transaction from being relayed on the bitcoin network and hence being
5905-
/// confirmed.
5906-
/// </div>
5900+
/// `SIGHASH_ALL` MUST be used for all signatures when providing signatures, otherwise your
5901+
/// funds can be held hostage!
5902+
///
5903+
/// LDK checks the following:
5904+
/// * Each input spends an output that is one of P2WPKH, P2WSH, or P2TR.
5905+
/// These were already checked by LDK when the inputs to be contributed were provided.
5906+
/// * All signatures use the `SIGHASH_ALL` sighash type.
5907+
/// * P2WPKH and P2TR key path spends are valid (verifies signatures)
5908+
///
5909+
/// NOTE:
5910+
/// * When checking P2WSH spends, LDK tries to decode 70-72 byte witness elements as ECDSA
5911+
/// signatures with a sighash flag. If the internal DER-decoding fails, then LDK just
5912+
/// assumes it wasn't a signature and carries with checks. If the element can be decoded
5913+
/// as an ECDSA signature, the the sighash flag must be `SIGHASH_ALL`.
5914+
/// * When checking P2TR script-path spends, LDK assumes all elements of exactly 65 bytes
5915+
/// with the last byte matching any valid sighash flag byte are schnorr signatures and checks
5916+
/// that the sighash type is `SIGHASH_ALL`. If the last byte is not any valid sighash flag, the
5917+
/// element is assumed not to be a signature and is ignored. Elements of 64 bytes are not
5918+
/// checked because if they were schnorr signatures then they would implicitly be `SIGHASH_DEFAULT`
5919+
/// which is an alias of `SIGHASH_ALL`.
59075920
///
59085921
/// Returns [`ChannelUnavailable`] when a channel is not found or an incorrect
59095922
/// `counterparty_node_id` is provided.
59105923
///
59115924
/// Returns [`APIMisuseError`] when a channel is not in a state where it is expecting funding
5912-
/// signatures.
5925+
/// signatures or if any of the checks described above fail.
59135926
///
59145927
/// [`ChannelUnavailable`]: APIError::ChannelUnavailable
59155928
/// [`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)