Merge rust-bitcoin/rust-bitcoin#808: Refactor logical operators

df7bb03a67 Simplify read_scriptbool (Tobin Harding)
4b6e86658d Refactor is_provably_unspendable (Tobin Harding)
e54a2d653b Put && operator at front of line (Tobin Harding)
f5512c4931 Refactor is_p2pkh (Tobin Harding)
373ea89a9a Simplify read_scriptbool (Tobin Harding)
654b2772b8 Add passing unit tests for read_scriptbool (Tobin Harding)

Pull request description:

  In an effort to make the code clearer and more explicit, do various refactorings around logical operators. Each done as a separate patch to ease review and limit scope of discussion.

  Based on review of https://github.com/rust-bitcoin/rust-bitcoin/pull/806

ACKs for top commit:
  Kixunil:
    ACK df7bb03a67
  apoelstra:
    ACK df7bb03a67

Tree-SHA512: 06460979d492eb38cefc147397338b7fd95320c66ce8e8b4f8e2b454bb35721ce308413690a0618bd19d695df56175646d4d0c619388c0268f7fd35d5a7b6a3d
This commit is contained in:
Andrew Poelstra 2022-01-26 13:13:58 +00:00
commit cb35766979
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 69 additions and 34 deletions

View File

@ -195,8 +195,8 @@ impl Block {
// commitment is in the last output that starts with below magic // commitment is in the last output that starts with below magic
if let Some(pos) = coinbase.output.iter() if let Some(pos) = coinbase.output.iter()
.rposition(|o| { .rposition(|o| {
o.script_pubkey.len () >= 38 && o.script_pubkey.len () >= 38
o.script_pubkey[0..6] == [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed] }) { && o.script_pubkey[0..6] == [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed] }) {
let commitment = WitnessCommitment::from_slice(&coinbase.output[pos].script_pubkey.as_bytes()[6..38]).unwrap(); let commitment = WitnessCommitment::from_slice(&coinbase.output[pos].script_pubkey.as_bytes()[6..38]).unwrap();
// witness reserved value is in coinbase input witness // witness reserved value is in coinbase input witness
let witness_vec: Vec<_> = coinbase.input[0].witness.iter().collect(); let witness_vec: Vec<_> = coinbase.input[0].witness.iter().collect();

View File

@ -239,9 +239,10 @@ pub fn read_scriptint(v: &[u8]) -> Result<i64, Error> {
/// else as true", except that the overflow rules don't apply. /// else as true", except that the overflow rules don't apply.
#[inline] #[inline]
pub fn read_scriptbool(v: &[u8]) -> bool { pub fn read_scriptbool(v: &[u8]) -> bool {
!(v.is_empty() || match v.split_last() {
((v[v.len() - 1] == 0 || v[v.len() - 1] == 0x80) && Some((last, rest)) => !((last & !0x80 == 0x00) && rest.iter().all(|&b| b == 0)),
v.iter().rev().skip(1).all(|&w| w == 0))) None => false,
}
} }
/// Read a script-encoded unsigned integer /// Read a script-encoded unsigned integer
@ -417,32 +418,37 @@ impl Script {
/// Checks whether a script pubkey is a p2sh output /// Checks whether a script pubkey is a p2sh output
#[inline] #[inline]
pub fn is_p2sh(&self) -> bool { pub fn is_p2sh(&self) -> bool {
self.0.len() == 23 && self.0.len() == 23
self.0[0] == opcodes::all::OP_HASH160.into_u8() && && self.0[0] == opcodes::all::OP_HASH160.into_u8()
self.0[1] == opcodes::all::OP_PUSHBYTES_20.into_u8() && && self.0[1] == opcodes::all::OP_PUSHBYTES_20.into_u8()
self.0[22] == opcodes::all::OP_EQUAL.into_u8() && self.0[22] == opcodes::all::OP_EQUAL.into_u8()
} }
/// Checks whether a script pubkey is a p2pkh output /// Checks whether a script pubkey is a p2pkh output
#[inline] #[inline]
pub fn is_p2pkh(&self) -> bool { pub fn is_p2pkh(&self) -> bool {
self.0.len() == 25 && self.0.len() == 25
self.0[0] == opcodes::all::OP_DUP.into_u8() && && self.0[0] == opcodes::all::OP_DUP.into_u8()
self.0[1] == opcodes::all::OP_HASH160.into_u8() && && self.0[1] == opcodes::all::OP_HASH160.into_u8()
self.0[2] == opcodes::all::OP_PUSHBYTES_20.into_u8() && && self.0[2] == opcodes::all::OP_PUSHBYTES_20.into_u8()
self.0[23] == opcodes::all::OP_EQUALVERIFY.into_u8() && && self.0[23] == opcodes::all::OP_EQUALVERIFY.into_u8()
self.0[24] == opcodes::all::OP_CHECKSIG.into_u8() && self.0[24] == opcodes::all::OP_CHECKSIG.into_u8()
} }
/// Checks whether a script pubkey is a p2pk output /// Checks whether a script pubkey is a p2pk output
#[inline] #[inline]
pub fn is_p2pk(&self) -> bool { pub fn is_p2pk(&self) -> bool {
(self.0.len() == 67 && match self.len() {
self.0[0] == opcodes::all::OP_PUSHBYTES_65.into_u8() && 67 => {
self.0[66] == opcodes::all::OP_CHECKSIG.into_u8()) self.0[0] == opcodes::all::OP_PUSHBYTES_65.into_u8()
|| (self.0.len() == 35 && && self.0[66] == opcodes::all::OP_CHECKSIG.into_u8()
self.0[0] == opcodes::all::OP_PUSHBYTES_33.into_u8() && }
self.0[34] == opcodes::all::OP_CHECKSIG.into_u8()) 35 => {
self.0[0] == opcodes::all::OP_PUSHBYTES_33.into_u8()
&& self.0[34] == opcodes::all::OP_CHECKSIG.into_u8()
}
_ => false
}
} }
/// Checks whether a script pubkey is a Segregated Witness (segwit) program. /// Checks whether a script pubkey is a Segregated Witness (segwit) program.
@ -468,37 +474,48 @@ impl Script {
/// Checks whether a script pubkey is a p2wsh output /// Checks whether a script pubkey is a p2wsh output
#[inline] #[inline]
pub fn is_v0_p2wsh(&self) -> bool { pub fn is_v0_p2wsh(&self) -> bool {
self.0.len() == 34 && self.0.len() == 34
self.witness_version() == Some(WitnessVersion::V0) && && self.witness_version() == Some(WitnessVersion::V0)
self.0[1] == opcodes::all::OP_PUSHBYTES_32.into_u8() && self.0[1] == opcodes::all::OP_PUSHBYTES_32.into_u8()
} }
/// Checks whether a script pubkey is a p2wpkh output /// Checks whether a script pubkey is a p2wpkh output
#[inline] #[inline]
pub fn is_v0_p2wpkh(&self) -> bool { pub fn is_v0_p2wpkh(&self) -> bool {
self.0.len() == 22 && self.0.len() == 22
self.witness_version() == Some(WitnessVersion::V0) && && self.witness_version() == Some(WitnessVersion::V0)
self.0[1] == opcodes::all::OP_PUSHBYTES_20.into_u8() && self.0[1] == opcodes::all::OP_PUSHBYTES_20.into_u8()
} }
/// Checks whether a script pubkey is a P2TR output /// Checks whether a script pubkey is a P2TR output
#[inline] #[inline]
pub fn is_v1_p2tr(&self) -> bool { pub fn is_v1_p2tr(&self) -> bool {
self.0.len() == 34 && self.0.len() == 34
self.witness_version() == Some(WitnessVersion::V1) && && self.witness_version() == Some(WitnessVersion::V1)
self.0[1] == opcodes::all::OP_PUSHBYTES_32.into_u8() && self.0[1] == opcodes::all::OP_PUSHBYTES_32.into_u8()
} }
/// Check if this is an OP_RETURN output /// Check if this is an OP_RETURN output
pub fn is_op_return (&self) -> bool { pub fn is_op_return (&self) -> bool {
!self.0.is_empty() && (opcodes::All::from(self.0[0]) == opcodes::all::OP_RETURN) match self.0.first() {
Some(b) => *b == opcodes::all::OP_RETURN.into_u8(),
None => false
}
} }
/// Whether a script can be proven to have no satisfying input /// Whether a script can be proven to have no satisfying input
pub fn is_provably_unspendable(&self) -> bool { pub fn is_provably_unspendable(&self) -> bool {
!self.0.is_empty() && use blockdata::opcodes::Class::{ReturnOp, IllegalOp};
(opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::ReturnOp ||
opcodes::All::from(self.0[0]).classify(opcodes::ClassifyContext::Legacy) == opcodes::Class::IllegalOp) match self.0.first() {
Some(b) => {
let first = opcodes::All::from(*b);
let class = first.classify(opcodes::ClassifyContext::Legacy);
class == ReturnOp || class == IllegalOp
},
None => false,
}
} }
/// Gets the minimum value an output with this script should have in order to be /// Gets the minimum value an output with this script should have in order to be
@ -1448,5 +1465,23 @@ mod test {
assert!(instructions.next().is_none()); assert!(instructions.next().is_none());
assert!(instructions.next().is_none()); assert!(instructions.next().is_none());
} }
#[test]
fn read_scriptbool_zero_is_false() {
let v: Vec<u8> = vec![0x00, 0x00, 0x00, 0x00];
assert!(!read_scriptbool(&v));
let v: Vec<u8> = vec![0x00, 0x00, 0x00, 0x80]; // With sign bit set.
assert!(!read_scriptbool(&v));
}
#[test]
fn read_scriptbool_non_zero_is_true() {
let v: Vec<u8> = vec![0x01, 0x00, 0x00, 0x00];
assert!(read_scriptbool(&v));
let v: Vec<u8> = vec![0x01, 0x00, 0x00, 0x80]; // With sign bit set.
assert!(read_scriptbool(&v));
}
} }