From dac552b4365e9feaffc5614979c7c57d1f41ff5b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 22 Mar 2024 07:03:30 +1100 Subject: [PATCH 1/2] Add unit tests for shortest/longest witness program Add two unit tests that verify we can correctly determine if a shortest allowed and longest allowed script is a witness program. Done in preparation for patching the `witness_version` function. --- bitcoin/src/blockdata/script/borrowed.rs | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index a4bb14fb..6f817d5d 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -660,3 +660,31 @@ delegate_index!( RangeToInclusive, (Bound, Bound) ); + +#[cfg(test)] +mod tests { + use super::*; + use crate::blockdata::script::witness_program::WitnessProgram; + + #[test] + fn shortest_witness_program() { + let bytes = [0x00; 2]; // Arbitrary bytes, witprog must be between 2 and 40. + let version = WitnessVersion::V15; // Arbitrary version number, intentionally not 0 or 1. + + let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); + let script = ScriptBuf::new_witness_program(&p); + + assert_eq!(script.witness_version(), Some(version)); + } + + #[test] + fn longest_witness_program() { + let bytes = [0x00; 40]; // Arbitrary bytes, witprog must be between 2 and 40. + let version = WitnessVersion::V16; // Arbitrary version number, intentionally not 0 or 1. + + let p = WitnessProgram::new(version, &bytes).expect("failed to create witness program"); + let script = ScriptBuf::new_witness_program(&p); + + assert_eq!(script.witness_version(), Some(version)); + } +} From dec05b63e9337fe93ab22e68ce544cc5d7db5dcf Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Fri, 22 Mar 2024 07:07:07 +1100 Subject: [PATCH 2/2] Refactor witness_version and is_witness_program These two functions are related. We cannot, by definition, get the witness version from a script that is not a witness program but currently the code is not linking these two things. Refactor by doing: - Move the check of the witness program bip rules to `witness_version` - Call `witness_version().is_some()` in the predicate Improve the docs while we are at it to include the bip text in the rustdoc. Note I didn't bother referencing the segwit bip number, this bip text is pretty well known. --- bitcoin/src/blockdata/script/borrowed.rs | 46 ++++++++++++++---------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 6f817d5d..8e1db39b 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -165,9 +165,35 @@ impl Script { } /// Returns witness version of the script, if any, assuming the script is a `scriptPubkey`. + /// + /// # Returns + /// + /// The witness version if this script is found to conform to the SegWit rules: + /// + /// > A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte + /// > push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new + /// > special meaning. The value of the first push is called the "version byte". The following + /// > byte vector pushed is called the "witness program". #[inline] pub fn witness_version(&self) -> Option { - self.0.first().and_then(|opcode| WitnessVersion::try_from(Opcode::from(*opcode)).ok()) + let script_len = self.0.len(); + if !(4..=42).contains(&script_len) { + return None; + } + + let ver_opcode = Opcode::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16 + let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes + + if push_opbyte < OP_PUSHBYTES_2.to_u8() || push_opbyte > OP_PUSHBYTES_40.to_u8() + { + return None; + } + // Check that the rest of the script has the correct size + if script_len - 2 != push_opbyte as usize { + return None; + } + + WitnessVersion::try_from(ver_opcode).ok() } /// Checks whether a script pubkey is a P2SH output. @@ -293,23 +319,7 @@ impl Script { /// Checks whether a script pubkey is a Segregated Witness (segwit) program. #[inline] - pub fn is_witness_program(&self) -> bool { - // A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte - // push opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new - // special meaning. The value of the first push is called the "version byte". The following - // byte vector pushed is called the "witness program". - let script_len = self.0.len(); - if !(4..=42).contains(&script_len) { - return false; - } - let ver_opcode = Opcode::from(self.0[0]); // Version 0 or PUSHNUM_1-PUSHNUM_16 - let push_opbyte = self.0[1]; // Second byte push opcode 2-40 bytes - WitnessVersion::try_from(ver_opcode).is_ok() - && push_opbyte >= OP_PUSHBYTES_2.to_u8() - && push_opbyte <= OP_PUSHBYTES_40.to_u8() - // Check that the rest of the script has the correct size - && script_len - 2 == push_opbyte as usize - } + pub fn is_witness_program(&self) -> bool { self.witness_version().is_some() } /// Checks whether a script pubkey is a P2WSH output. #[inline]