Skip to content

Commit 537495d

Browse files
committed
Merge rust-bitcoin/rust-bitcoin#860: Fix signature hash returned for sighash single bug
d1abfd9 Add unit test for sighash single bug (Tobin Harding) 82f29b4 Use 1 signature hash for invalid SIGHASH_SINGLE (Tobin Harding) 3831816 Move test helper function (Tobin Harding) 3e21295 Remove unnecessary whitespace character (Tobin Harding) Pull request description: Fix up the logic that handles correctly returning the special array 1,0,0,...,0 for signature hash when the sighash single bug is exploitable i.e., when signing a transaction with SIGHASH_SINGLE for an input index that does not have a corresponding transaction output of the same index. - Patch 1 and 2: Clean up - Patch 3: Implements the fix - Patch 4: Adds a passing test that fails if moved to before patch 3 Resolves: #817 ACKs for top commit: apoelstra: ACK d1abfd9 dr-orlovsky: ACK d1abfd9 Tree-SHA512: f2d09e929d2f91348ae0b0758b3d4be6c6ce0cb38c4988e0bebb29f5918ca8491b9e7b31fe745f7c20d9348612fe2166f0a12b782f256aad5f6b6c027c2218b7
2 parents 5d31627 + e03a0e2 commit 537495d

File tree

1 file changed

+64
-30
lines changed

1 file changed

+64
-30
lines changed

src/blockdata/transaction.rs

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ use VarInt;
4545
#[cfg(doc)]
4646
use util::sighash::SchnorrSigHashType;
4747

48+
/// Used for signature hash for invalid use of SIGHASH_SINGLE.
49+
const UINT256_ONE: [u8; 32] = [
50+
1, 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+
0, 0, 0, 0, 0, 0, 0, 0
54+
];
55+
4856
/// A reference to a transaction output.
4957
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
5058
pub struct OutPoint {
@@ -332,20 +340,11 @@ impl Transaction {
332340
script_pubkey: &Script,
333341
sighash_type: U,
334342
) -> Result<(), encode::Error> {
335-
let sighash_type : u32 = sighash_type.into();
343+
let sighash_type: u32 = sighash_type.into();
336344
assert!(input_index < self.input.len()); // Panic on OOB
337345

338346
let (sighash, anyone_can_pay) = EcdsaSigHashType::from_u32_consensus(sighash_type).split_anyonecanpay_flag();
339347

340-
// Special-case sighash_single bug because this is easy enough.
341-
if sighash == EcdsaSigHashType::Single && input_index >= self.output.len() {
342-
writer.write_all(&[1, 0, 0, 0, 0, 0, 0, 0,
343-
0, 0, 0, 0, 0, 0, 0, 0,
344-
0, 0, 0, 0, 0, 0, 0, 0,
345-
0, 0, 0, 0, 0, 0, 0, 0])?;
346-
return Ok(());
347-
}
348-
349348
// Build tx to sign
350349
let mut tx = Transaction {
351350
version: self.version,
@@ -401,12 +400,24 @@ impl Transaction {
401400
script_pubkey: &Script,
402401
sighash_u32: u32
403402
) -> SigHash {
403+
if self.is_invalid_use_of_sighash_single(sighash_u32, input_index) {
404+
return SigHash::from_slice(&UINT256_ONE).expect("const-size array");
405+
}
406+
404407
let mut engine = SigHash::engine();
405408
self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32)
406409
.expect("engines don't error");
407410
SigHash::from_engine(engine)
408411
}
409412

413+
/// The SIGHASH_SINGLE bug becomes exploitable when one tries to sign a transaction with
414+
/// SIGHASH_SINGLE and there is not a corresponding output transaction with the same index as
415+
/// the input transaction.
416+
fn is_invalid_use_of_sighash_single(&self, sighash: u32, input_index: usize) -> bool {
417+
let ty = EcdsaSigHashType::from_u32_consensus(sighash);
418+
ty == EcdsaSigHashType::Single && input_index >= self.output.len()
419+
}
420+
410421
/// Gets the "weight" of this transaction, as defined by BIP141. For transactions with an empty
411422
/// witness, this is simply the consensus-serialized size times 4. For transactions with a
412423
/// witness, this is the non-witness consensus-serialized size multiplied by 3 plus the
@@ -815,7 +826,7 @@ impl ::std::error::Error for SigHashTypeParseError {}
815826

816827
#[cfg(test)]
817828
mod tests {
818-
use super::{OutPoint, ParseOutPointError, Transaction, TxIn, NonStandardSigHashType};
829+
use super::*;
819830

820831
use core::str::FromStr;
821832
use blockdata::constants::WITNESS_SCALE_FACTOR;
@@ -1073,25 +1084,6 @@ mod tests {
10731084
serde_round_trip!(tx);
10741085
}
10751086

1076-
fn run_test_sighash(tx: &str, script: &str, input_index: usize, hash_type: i32, expected_result: &str) {
1077-
let tx: Transaction = deserialize(&Vec::from_hex(tx).unwrap()[..]).unwrap();
1078-
let script = Script::from(Vec::from_hex(script).unwrap());
1079-
let mut raw_expected = Vec::from_hex(expected_result).unwrap();
1080-
raw_expected.reverse();
1081-
let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap();
1082-
1083-
let actual_result = if raw_expected[0] % 2 == 0 {
1084-
// tx.signature_hash and cache.legacy_signature_hash are the same, this if helps to test
1085-
// both the codepaths without repeating the test code
1086-
tx.signature_hash(input_index, &script, hash_type as u32)
1087-
} else {
1088-
let cache = SigHashCache::new(&tx);
1089-
cache.legacy_signature_hash(input_index, &script, hash_type as u32).unwrap()
1090-
};
1091-
1092-
assert_eq!(actual_result, expected_result);
1093-
}
1094-
10951087
// Test decoding transaction `4be105f158ea44aec57bf12c5817d073a712ab131df6f37786872cfc70734188`
10961088
// from testnet, which is the first BIP144-encoded transaction I encountered.
10971089
#[test]
@@ -1148,6 +1140,48 @@ mod tests {
11481140
assert_eq!(EcdsaSigHashType::from_u32_standard(nonstandard_hashtype), Err(NonStandardSigHashType(0x04)));
11491141
}
11501142

1143+
#[test]
1144+
fn sighash_single_bug() {
1145+
const SIGHASH_SINGLE: u32 = 3;
1146+
// We need a tx with more inputs than outputs.
1147+
let mut input = Vec::new();
1148+
input.push(TxIn::default());
1149+
input.push(TxIn::default());
1150+
let mut output = Vec::new();
1151+
output.push(TxOut::default());
1152+
1153+
let tx = Transaction {
1154+
version: 1,
1155+
lock_time: 0,
1156+
input: input,
1157+
output: output, // TODO: Use Vec::from([TxOut]) once we bump MSRV.
1158+
};
1159+
let script = Script::new();
1160+
let got = tx.signature_hash(1, &script, SIGHASH_SINGLE);
1161+
let want = SigHash::from_slice(&UINT256_ONE).unwrap();
1162+
1163+
assert_eq!(got, want)
1164+
}
1165+
1166+
fn run_test_sighash(tx: &str, script: &str, input_index: usize, hash_type: i32, expected_result: &str) {
1167+
let tx: Transaction = deserialize(&Vec::from_hex(tx).unwrap()[..]).unwrap();
1168+
let script = Script::from(Vec::from_hex(script).unwrap());
1169+
let mut raw_expected = Vec::from_hex(expected_result).unwrap();
1170+
raw_expected.reverse();
1171+
let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap();
1172+
1173+
let actual_result = if raw_expected[0] % 2 == 0 {
1174+
// tx.signature_hash and cache.legacy_signature_hash are the same, this if helps to test
1175+
// both the codepaths without repeating the test code
1176+
tx.signature_hash(input_index, &script, hash_type as u32)
1177+
} else {
1178+
let cache = SigHashCache::new(&tx);
1179+
cache.legacy_signature_hash(input_index, &script, hash_type as u32).unwrap()
1180+
};
1181+
1182+
assert_eq!(actual_result, expected_result);
1183+
}
1184+
11511185
// These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT
11521186
// They were transformed by replacing {...} with run_test_sighash(...), then the ones containing
11531187
// OP_CODESEPARATOR in their pubkeys were removed

0 commit comments

Comments
 (0)