Skip to content

Commit fe7d58a

Browse files
committed
Merge rust-bitcoin/rust-bitcoin#1024: Expose SIGHASH_SINGLE bug in encode_signing_data_to
42a91ab Expose SIGHASH_SINGLE bug in `encode_signing_data_to` (Dawid Ciężarkiewicz) Pull request description: Via `Option` return value Fix #1015 ACKs for top commit: tcharding: ACK 42a91ab apoelstra: ACK 42a91ab Tree-SHA512: 8e401ba0ee6ed2bdb95ec838440cfd7a0b6414991ada0d941e8b9526ea4a7d9b6ca1fc84318c4b2a317705650cabc957269c1034dd70c92bdeb854ca413e53be
2 parents 2ec90e9 + 73a0620 commit fe7d58a

File tree

2 files changed

+159
-67
lines changed

2 files changed

+159
-67
lines changed

src/blockdata/transaction.rs

Lines changed: 138 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,11 @@ use crate::consensus::{encode, Decodable, Encodable};
4040
use crate::consensus::encode::MAX_VEC_SIZE;
4141
use crate::hash_types::{Sighash, Txid, Wtxid};
4242
use crate::VarInt;
43+
use crate::util::sighash::UINT256_ONE;
4344

4445
#[cfg(doc)]
4546
use crate::util::sighash::SchnorrSighashType;
4647

47-
/// Used for signature hash for invalid use of SIGHASH_SINGLE.
48-
const UINT256_ONE: [u8; 32] = [
49-
1, 0, 0, 0, 0, 0, 0, 0,
50-
0, 0, 0, 0, 0, 0, 0, 0,
51-
0, 0, 0, 0, 0, 0, 0, 0,
52-
0, 0, 0, 0, 0, 0, 0, 0
53-
];
54-
5548
/// A reference to a transaction output.
5649
///
5750
/// ### Bitcoin Core References
@@ -262,6 +255,67 @@ impl Default for TxOut {
262255
}
263256
}
264257

258+
/// Result of [`Transaction::encode_signing_data_to`].
259+
///
260+
/// This type forces the caller to handle SIGHASH_SINGLE bug case.
261+
///
262+
/// This corner case can't be expressed using standard `Result`,
263+
/// in a way that is both convenient and not-prone to accidental
264+
/// mistakes (like calling `.expect("writer never fails")`).
265+
#[must_use]
266+
pub enum EncodeSigningDataResult<E> {
267+
/// Input data is an instance of `SIGHASH_SINGLE` bug
268+
SighashSingleBug,
269+
/// Operation performed normally.
270+
WriteResult(Result<(), E>),
271+
}
272+
273+
impl<E> EncodeSigningDataResult<E> {
274+
/// Checks for SIGHASH_SINGLE bug returning error if the writer failed.
275+
///
276+
/// This method is provided for easy and correct handling of the result because
277+
/// SIGHASH_SINGLE bug is a special case that must not be ignored nor cause panicking.
278+
/// Since the data is usually written directly into a hasher which never fails,
279+
/// the recommended pattern to handle this is:
280+
///
281+
/// ```rust
282+
/// # use bitcoin::consensus::deserialize;
283+
/// # use bitcoin::{Transaction, Sighash};
284+
/// # use bitcoin_hashes::{Hash, hex::FromHex};
285+
/// # let mut writer = Sighash::engine();
286+
/// # let input_index = 0;
287+
/// # let script_pubkey = bitcoin::Script::new();
288+
/// # let sighash_u32 = 0u32;
289+
/// # const SOME_TX: &'static str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000";
290+
/// # let raw_tx = Vec::from_hex(SOME_TX).unwrap();
291+
/// # let tx: Transaction = deserialize(&raw_tx).unwrap();
292+
/// if tx.encode_signing_data_to(&mut writer, input_index, &script_pubkey, sighash_u32)
293+
/// .is_sighash_single_bug()
294+
/// .expect("writer can't fail") {
295+
/// // use a hash value of "1", instead of computing the actual hash due to SIGHASH_SINGLE bug
296+
/// }
297+
/// ```
298+
pub fn is_sighash_single_bug(self) -> Result<bool, E> {
299+
match self {
300+
EncodeSigningDataResult::SighashSingleBug => Ok(true),
301+
EncodeSigningDataResult::WriteResult(Ok(())) => Ok(false),
302+
EncodeSigningDataResult::WriteResult(Err(e)) => Err(e),
303+
}
304+
}
305+
306+
/// Maps a `Result<T, E>` to `Result<T, F>` by applying a function to a
307+
/// contained [`Err`] value, leaving an [`Ok`] value untouched.
308+
///
309+
/// Like [`Result::map_err`].
310+
pub fn map_err<E2, F>(self, f: F) -> EncodeSigningDataResult<E2> where F: FnOnce(E) -> E2 {
311+
match self {
312+
EncodeSigningDataResult::SighashSingleBug => EncodeSigningDataResult::SighashSingleBug,
313+
EncodeSigningDataResult::WriteResult(Err(e)) => EncodeSigningDataResult::WriteResult(Err(f(e))),
314+
EncodeSigningDataResult::WriteResult(Ok(o)) => EncodeSigningDataResult::WriteResult(Ok(o)),
315+
}
316+
}
317+
}
318+
265319
/// Bitcoin transaction.
266320
///
267321
/// An authenticated movement of coins.
@@ -370,19 +424,23 @@ impl Transaction {
370424
/// - Does NOT attempt to support OP_CODESEPARATOR. In general this would require evaluating
371425
/// `script_pubkey` to determine which separators get evaluated and which don't, which we don't
372426
/// have the information to determine.
373-
/// - Does NOT handle the sighash single bug, you should either handle that manually or use
374-
/// [`Self::signature_hash()`] instead.
427+
/// - Does NOT handle the sighash single bug (see "Return type" section)
428+
///
429+
/// # Return type
430+
///
431+
/// This function can't handle the SIGHASH_SINGLE bug internally, so it returns [`EncodeSigningDataResult`]
432+
/// that must be handled by the caller (see [`EncodeSigningDataResult::is_sighash_single_bug`]).
375433
///
376434
/// # Panics
377435
///
378436
/// If `input_index` is out of bounds (greater than or equal to `self.input.len()`).
379437
pub fn encode_signing_data_to<Write: io::Write, U: Into<u32>>(
380438
&self,
381-
mut writer: Write,
439+
writer: Write,
382440
input_index: usize,
383441
script_pubkey: &Script,
384442
sighash_type: U,
385-
) -> Result<(), encode::Error> {
443+
) -> EncodeSigningDataResult<io::Error> {
386444
let sighash_type: u32 = sighash_type.into();
387445
assert!(input_index < self.input.len()); // Panic on OOB
388446

@@ -391,56 +449,73 @@ impl Transaction {
391449
// will result in the data written to the writer being hashed, however the correct
392450
// handling of the SIGHASH_SINGLE bug is to return the 'one array' - either implement
393451
// this behaviour manually or use `signature_hash()`.
394-
writer.write_all(b"[not a transaction] SIGHASH_SINGLE bug")?;
395-
return Ok(())
452+
return EncodeSigningDataResult::SighashSingleBug;
396453
}
397454

398-
let (sighash, anyone_can_pay) = EcdsaSighashType::from_consensus(sighash_type).split_anyonecanpay_flag();
399-
400-
// Build tx to sign
401-
let mut tx = Transaction {
402-
version: self.version,
403-
lock_time: self.lock_time,
404-
input: vec![],
405-
output: vec![],
406-
};
407-
// Add all inputs necessary..
408-
if anyone_can_pay {
409-
tx.input = vec![TxIn {
410-
previous_output: self.input[input_index].previous_output,
411-
script_sig: script_pubkey.clone(),
412-
sequence: self.input[input_index].sequence,
413-
witness: Witness::default(),
414-
}];
415-
} else {
416-
tx.input = Vec::with_capacity(self.input.len());
417-
for (n, input) in self.input.iter().enumerate() {
418-
tx.input.push(TxIn {
419-
previous_output: input.previous_output,
420-
script_sig: if n == input_index { script_pubkey.clone() } else { Script::new() },
421-
sequence: if n != input_index && (sighash == EcdsaSighashType::Single || sighash == EcdsaSighashType::None) { 0 } else { input.sequence },
455+
fn encode_signing_data_to_inner<Write: io::Write>(
456+
self_: &Transaction,
457+
mut writer: Write,
458+
input_index: usize,
459+
script_pubkey: &Script,
460+
sighash_type: u32,
461+
) -> Result<(), io::Error> {
462+
let (sighash, anyone_can_pay) = EcdsaSighashType::from_consensus(sighash_type).split_anyonecanpay_flag();
463+
464+
// Build tx to sign
465+
let mut tx = Transaction {
466+
version: self_.version,
467+
lock_time: self_.lock_time,
468+
input: vec![],
469+
output: vec![],
470+
};
471+
// Add all inputs necessary..
472+
if anyone_can_pay {
473+
tx.input = vec![TxIn {
474+
previous_output: self_.input[input_index].previous_output,
475+
script_sig: script_pubkey.clone(),
476+
sequence: self_.input[input_index].sequence,
422477
witness: Witness::default(),
423-
});
478+
}];
479+
} else {
480+
tx.input = Vec::with_capacity(self_.input.len());
481+
for (n, input) in self_.input.iter().enumerate() {
482+
tx.input.push(TxIn {
483+
previous_output: input.previous_output,
484+
script_sig: if n == input_index { script_pubkey.clone() } else { Script::new() },
485+
sequence: if n != input_index && (sighash == EcdsaSighashType::Single || sighash == EcdsaSighashType::None) { 0 } else { input.sequence },
486+
witness: Witness::default(),
487+
});
488+
}
424489
}
490+
// ..then all outputs
491+
tx.output = match sighash {
492+
EcdsaSighashType::All => self_.output.clone(),
493+
EcdsaSighashType::Single => {
494+
let output_iter = self_.output.iter()
495+
.take(input_index + 1) // sign all outputs up to and including this one, but erase
496+
.enumerate() // all of them except for this one
497+
.map(|(n, out)| if n == input_index { out.clone() } else { TxOut::default() });
498+
output_iter.collect()
499+
}
500+
EcdsaSighashType::None => vec![],
501+
_ => unreachable!()
502+
};
503+
// hash the result
504+
tx.consensus_encode(&mut writer)?;
505+
let sighash_arr = endian::u32_to_array_le(sighash_type);
506+
sighash_arr.consensus_encode(&mut writer)?;
507+
Ok(())
425508
}
426-
// ..then all outputs
427-
tx.output = match sighash {
428-
EcdsaSighashType::All => self.output.clone(),
429-
EcdsaSighashType::Single => {
430-
let output_iter = self.output.iter()
431-
.take(input_index + 1) // sign all outputs up to and including this one, but erase
432-
.enumerate() // all of them except for this one
433-
.map(|(n, out)| if n == input_index { out.clone() } else { TxOut::default() });
434-
output_iter.collect()
435-
}
436-
EcdsaSighashType::None => vec![],
437-
_ => unreachable!()
438-
};
439-
// hash the result
440-
tx.consensus_encode(&mut writer)?;
441-
let sighash_arr = endian::u32_to_array_le(sighash_type);
442-
sighash_arr.consensus_encode(&mut writer)?;
443-
Ok(())
509+
510+
EncodeSigningDataResult::WriteResult(
511+
encode_signing_data_to_inner(
512+
self,
513+
writer,
514+
input_index,
515+
script_pubkey,
516+
sighash_type
517+
)
518+
)
444519
}
445520

446521
/// Computes a signature hash for a given input index with a given sighash flag.
@@ -473,12 +548,15 @@ impl Transaction {
473548
sighash_u32: u32
474549
) -> Sighash {
475550
if self.is_invalid_use_of_sighash_single(sighash_u32, input_index) {
476-
return Sighash::from_slice(&UINT256_ONE).expect("const-size array");
551+
return Sighash::from_inner(UINT256_ONE);
477552
}
478553

479554
let mut engine = Sighash::engine();
480-
self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32)
481-
.expect("engines don't error");
555+
if self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32)
556+
.is_sighash_single_bug()
557+
.expect("engines don't error") {
558+
return Sighash::from_slice(&UINT256_ONE).expect("const-size array");
559+
}
482560
Sighash::from_engine(engine)
483561
}
484562

src/util/sighash.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//! and legacy (before Bip143).
2121
//!
2222
23+
use crate::blockdata::transaction::EncodeSigningDataResult;
2324
use crate::prelude::*;
2425

2526
pub use crate::blockdata::transaction::{EcdsaSighashType, SighashTypeParseError};
@@ -36,6 +37,14 @@ use crate::{Script, Transaction, TxOut};
3637

3738
use super::taproot::LeafVersion;
3839

40+
/// Used for signature hash for invalid use of SIGHASH_SINGLE.
41+
pub(crate) const UINT256_ONE: [u8; 32] = [
42+
1, 0, 0, 0, 0, 0, 0, 0,
43+
0, 0, 0, 0, 0, 0, 0, 0,
44+
0, 0, 0, 0, 0, 0, 0, 0,
45+
0, 0, 0, 0, 0, 0, 0, 0
46+
];
47+
3948
/// Efficiently calculates signature hash message for legacy, segwit and taproot inputs.
4049
#[derive(Debug)]
4150
pub struct SighashCache<T: Deref<Target=Transaction>> {
@@ -638,23 +647,24 @@ impl<R: Deref<Target = Transaction>> SighashCache<R> {
638647

639648
/// Encodes the legacy signing data for any flag type into a given object implementing a
640649
/// [`std::io::Write`] trait. Internally calls [`Transaction::encode_signing_data_to`].
650+
#[must_use]
641651
pub fn legacy_encode_signing_data_to<Write: io::Write, U: Into<u32>>(
642652
&self,
643653
mut writer: Write,
644654
input_index: usize,
645655
script_pubkey: &Script,
646656
sighash_type: U,
647-
) -> Result<(), Error> {
657+
) -> EncodeSigningDataResult<Error> {
648658
if input_index >= self.tx.input.len() {
649-
return Err(Error::IndexOutOfInputsBounds {
659+
return EncodeSigningDataResult::WriteResult(Err(Error::IndexOutOfInputsBounds {
650660
index: input_index,
651661
inputs_size: self.tx.input.len(),
652-
});
662+
}));
653663
}
664+
654665
self.tx
655666
.encode_signing_data_to(&mut writer, input_index, script_pubkey, sighash_type.into())
656-
.expect("writers don't error");
657-
Ok(())
667+
.map_err(|e| e.into())
658668
}
659669

660670
/// Computes the legacy sighash for any `sighash_type`.
@@ -665,8 +675,12 @@ impl<R: Deref<Target = Transaction>> SighashCache<R> {
665675
sighash_type: u32,
666676
) -> Result<Sighash, Error> {
667677
let mut enc = Sighash::engine();
668-
self.legacy_encode_signing_data_to(&mut enc, input_index, script_pubkey, sighash_type)?;
669-
Ok(Sighash::from_engine(enc))
678+
if self.legacy_encode_signing_data_to(&mut enc, input_index, script_pubkey, sighash_type)
679+
.is_sighash_single_bug()? {
680+
Ok(Sighash::from_inner(UINT256_ONE))
681+
} else {
682+
Ok(Sighash::from_engine(enc))
683+
}
670684
}
671685

672686
#[inline]

0 commit comments

Comments
 (0)