Skip to content

Commit

Permalink
transaction: add a method to err on non-standard types to SigHashType
Browse files Browse the repository at this point in the history
Right now, any sighash type could be parsed without error, which matches
consensus rules. However most of them would be invalid by standardness,
so it's a bit footgun-y (even more so for pre-signed transactions
protocols for which standardness is critical).

This adds `from_u32_standard()`, which takes care to error if we are
passed an invalid-by-current-policy-rules SIGHASH type.

Signed-off-by: Antoine Poinsot <darosior@protonmail.com>
  • Loading branch information
darosior committed Feb 19, 2021
1 parent 466f161 commit bf98d9f
Showing 1 changed file with 42 additions and 2 deletions.
44 changes: 42 additions & 2 deletions src/blockdata/transaction.rs
Expand Up @@ -608,6 +608,19 @@ impl Decodable for Transaction {
}
}

/// This type is consensus valid but an input including it would prevent the transaction from
/// being relayed on today's Bitcoin network.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NonStandardSigHashType;

impl fmt::Display for NonStandardSigHashType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Non standard sighash type")
}
}

impl error::Error for NonStandardSigHashType {}

/// Hashtype of an input's signature, encoded in the last byte of the signature
/// Fixed values so they can be casted as integer types for encoding
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
Expand Down Expand Up @@ -673,7 +686,10 @@ impl SigHashType {
}
}

/// Reads a 4-byte uint32 as a sighash type
/// Reads a 4-byte uint32 as a sighash type.
///
/// **Note**: this replicates consensus behaviour, for current standardness rules correctness
/// you probably want [from_u32_standard].
pub fn from_u32(n: u32) -> SigHashType {
// In Bitcoin Core, the SignatureHash function will mask the (int32) value with
// 0x1f to (apparently) deactivate ACP when checking for SINGLE and NONE bits.
Expand All @@ -694,6 +710,21 @@ impl SigHashType {
}
}

/// Read a 4-byte uint32 as a standard sighash type, returning an error if the type
/// is non standard.
pub fn from_u32_standard(n: u32) -> Result<SigHashType, NonStandardSigHashType> {
match n {
// Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198
0x01 => Ok(SigHashType::All),
0x02 => Ok(SigHashType::None),
0x03 => Ok(SigHashType::Single),
0x81 => Ok(SigHashType::AllPlusAnyoneCanPay),
0x82 => Ok(SigHashType::NonePlusAnyoneCanPay),
0x83 => Ok(SigHashType::SinglePlusAnyoneCanPay),
_ => Err(NonStandardSigHashType)
}
}

/// Converts to a u32
pub fn as_u32(self) -> u32 { self as u32 }
}
Expand All @@ -706,7 +737,7 @@ impl From<SigHashType> for u32 {

#[cfg(test)]
mod tests {
use super::{OutPoint, ParseOutPointError, Transaction, TxIn};
use super::{OutPoint, ParseOutPointError, Transaction, TxIn, NonStandardSigHashType};

use std::str::FromStr;
use blockdata::constants::WITNESS_SCALE_FACTOR;
Expand Down Expand Up @@ -999,6 +1030,15 @@ mod tests {
}
}

#[test]
fn test_sighashtype_standard() {
let nonstandard_hashtype = 0x04;
// This type is not well defined, by consensus it becomes ALL
assert_eq!(SigHashType::from_u32(nonstandard_hashtype), SigHashType::All);
// But it's policy-invalid to use it!
assert_eq!(SigHashType::from_u32_standard(nonstandard_hashtype), Err(NonStandardSigHashType));
}

// These test vectors were stolen from libbtc, which is Copyright 2014 Jonas Schnelli MIT
// They were transformed by replacing {...} with run_test_sighash(...), then the ones containing
// OP_CODESEPARATOR in their pubkeys were removed
Expand Down

0 comments on commit bf98d9f

Please sign in to comment.