From e48a2e422548d77ad2fe407a6b799fa5f4c93b24 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 21 Mar 2024 21:47:53 +0000 Subject: [PATCH 1/5] script: Add Script::redeem_script inspector --- bitcoin/src/blockdata/script/borrowed.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 253c38f1e..5627d5b3c 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -394,6 +394,25 @@ impl Script { } } + /// Get redeemScript following BIP16 rules regarding P2SH spending. + /// + /// This does not guarantee that this represents a P2SH input [`Script`]. + /// It merely gets the last push of the script. Use + /// [`Script::is_p2sh`](crate::blockdata::script::Script::is_p2sh) on the + /// scriptPubKey to check whether it is actually a P2SH script. + pub fn redeem_script(&self) -> Option<&Script> { + // Script must consist entirely of pushes. + if self.instructions().any(|i| i.is_err() || i.unwrap().push_bytes().is_none()) { + return None; + } + + if let Some(Ok(Instruction::PushBytes(b))) = self.instructions().last() { + Some(Script::from_bytes(b.as_bytes())) + } else { + None + } + } + /// Returns the minimum value an output with this script should have in order to be /// broadcastable on today’s Bitcoin network. #[deprecated(since = "0.32.0", note = "use minimal_non_dust and friends")] From ef336e13871cff00b675dbe5b58495ef8a2bd853 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 12 Oct 2023 13:45:44 +0100 Subject: [PATCH 2/5] witness: Improve Witness::tapscript --- bitcoin/src/blockdata/witness.rs | 43 +++++++++++--------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index d617f980b..e36ffbbb9 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -394,24 +394,19 @@ impl Witness { /// [Script::is_p2tr](crate::blockdata::script::Script::is_p2tr) to /// check whether this is actually a Taproot witness. pub fn tapscript(&self) -> Option<&Script> { - let len = self.len(); - self.last() - .map(|last_elem| { - // From BIP341: - // If there are at least two witness elements, and the first byte of - // the last element is 0x50, this last element is called annex a - // and is removed from the witness stack. - if len >= 2 && last_elem.first() == Some(&TAPROOT_ANNEX_PREFIX) { - // account for the extra item removed from the end - 3 - } else { - // otherwise script is 2nd from last - 2 - } - }) - .filter(|&script_pos_from_last| len >= script_pos_from_last) - .and_then(|script_pos_from_last| self.nth(len - script_pos_from_last)) - .map(Script::from_bytes) + self.last().and_then(|last| { + // From BIP341: + // If there are at least two witness elements, and the first byte of + // the last element is 0x50, this last element is called annex a + // and is removed from the witness stack. + if self.len() >= 3 && last.first() == Some(&TAPROOT_ANNEX_PREFIX) { + self.nth(self.len() - 3).map(Script::from_bytes) + } else if self.len() >= 2 { + self.nth(self.len() - 2).map(Script::from_bytes) + } else { + None + } + }) } } @@ -702,16 +697,8 @@ mod test { let witness_serialized: Vec = serialize(&witness_vec); let witness_serialized_annex: Vec = serialize(&witness_vec_annex); - let witness = Witness { - content: append_u32_vec(witness_serialized[1..].to_vec(), &[0, 5]), - witness_elements: 2, - indices_start: 7, - }; - let witness_annex = Witness { - content: append_u32_vec(witness_serialized_annex[1..].to_vec(), &[0, 5, 7]), - witness_elements: 3, - indices_start: 9, - }; + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); // With or without annex, the tapscript should be returned. assert_eq!(witness.tapscript(), Some(Script::from_bytes(&tapscript[..]))); From b0848022ebec44ec9b8abaff6694e5b7329c85c9 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 12 Oct 2023 13:46:24 +0100 Subject: [PATCH 3/5] witness: Add Witness::taproot_control_block --- bitcoin/src/blockdata/witness.rs | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index e36ffbbb9..5c0044a5f 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -408,6 +408,29 @@ impl Witness { } }) } + + /// Get the taproot control block following BIP341 rules. + /// + /// This does not guarantee that this represents a P2TR [`Witness`]. It + /// merely gets the last or second to last element depending on the first + /// byte of the last element being equal to 0x50. See + /// [Script::is_p2tr](crate::blockdata::script::Script::is_p2tr) to + /// check whether this is actually a Taproot witness. + pub fn taproot_control_block(&self) -> Option<&[u8]> { + self.last().and_then(|last| { + // From BIP341: + // If there are at least two witness elements, and the first byte of + // the last element is 0x50, this last element is called annex a + // and is removed from the witness stack. + if self.len() >= 3 && last.first() == Some(&TAPROOT_ANNEX_PREFIX) { + self.nth(self.len() - 2) + } else if self.len() >= 2 { + Some(last) + } else { + None + } + }) + } } impl Index for Witness { @@ -705,6 +728,27 @@ mod test { assert_eq!(witness_annex.tapscript(), Some(Script::from_bytes(&tapscript[..]))); } + #[test] + fn test_get_control_block() { + let tapscript = hex!("deadbeef"); + let control_block = hex!("02"); + // annex starting with 0x50 causes the branching logic. + let annex = hex!("50"); + + let witness_vec = vec![tapscript.clone(), control_block.clone()]; + let witness_vec_annex = vec![tapscript.clone(), control_block.clone(), annex]; + + let witness_serialized: Vec = serialize(&witness_vec); + let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + + // With or without annex, the tapscript should be returned. + assert_eq!(witness.taproot_control_block(), Some(&control_block[..])); + assert_eq!(witness_annex.taproot_control_block(), Some(&control_block[..])); + } + #[test] fn test_tx() { const S: &str = "02000000000102b44f26b275b8ad7b81146ba3dbecd081f9c1ea0dc05b97516f56045cfcd3df030100000000ffffffff1cb4749ae827c0b75f3d0a31e63efc8c71b47b5e3634a4c698cd53661cab09170100000000ffffffff020b3a0500000000001976a9143ea74de92762212c96f4dd66c4d72a4deb20b75788ac630500000000000016001493a8dfd1f0b6a600ab01df52b138cda0b82bb7080248304502210084622878c94f4c356ce49c8e33a063ec90f6ee9c0208540888cfab056cd1fca9022014e8dbfdfa46d318c6887afd92dcfa54510e057565e091d64d2ee3a66488f82c0121026e181ffb98ebfe5a64c983073398ea4bcd1548e7b971b4c175346a25a1c12e950247304402203ef00489a0d549114977df2820fab02df75bebb374f5eee9e615107121658cfa02204751f2d1784f8e841bff6d3bcf2396af2f1a5537c0e4397224873fbd3bfbe9cf012102ae6aa498ce2dd204e9180e71b4fb1260fe3d1a95c8025b34e56a9adf5f278af200000000"; From 6cc6c8621aff8647547ea09504551909276185a7 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 12 Oct 2023 13:50:44 +0100 Subject: [PATCH 4/5] witness: Add Witness::taproot_annex --- bitcoin/src/blockdata/witness.rs | 58 ++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 5c0044a5f..19bb268a2 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -431,6 +431,25 @@ impl Witness { } }) } + + /// Get the taproot annex following BIP341 rules. + /// + /// This does not guarantee that this represents a P2TR [`Witness`]. See + /// [Script::is_p2tr](crate::blockdata::script::Script::is_p2tr) to + /// check whether this is actually a Taproot witness. + pub fn taproot_annex(&self) -> Option<&[u8]> { + self.last().and_then(|last| { + // From BIP341: + // If there are at least two witness elements, and the first byte of + // the last element is 0x50, this last element is called annex a + // and is removed from the witness stack. + if self.len() >= 2 && last.first() == Some(&TAPROOT_ANNEX_PREFIX) { + Some(last) + } else { + None + } + }) + } } impl Index for Witness { @@ -749,6 +768,45 @@ mod test { assert_eq!(witness_annex.taproot_control_block(), Some(&control_block[..])); } + #[test] + fn test_get_annex() { + let tapscript = hex!("deadbeef"); + let control_block = hex!("02"); + // annex starting with 0x50 causes the branching logic. + let annex = hex!("50"); + + let witness_vec = vec![tapscript.clone(), control_block.clone()]; + let witness_vec_annex = vec![tapscript.clone(), control_block.clone(), annex.clone()]; + + let witness_serialized: Vec = serialize(&witness_vec); + let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + + // With or without annex, the tapscript should be returned. + assert_eq!(witness.taproot_annex(), None); + assert_eq!(witness_annex.taproot_annex(), Some(&annex[..])); + + // Now for keyspend + let signature = hex!("deadbeef"); + // annex starting with 0x50 causes the branching logic. + let annex = hex!("50"); + + let witness_vec = vec![signature.clone()]; + let witness_vec_annex = vec![signature.clone(), annex.clone()]; + + let witness_serialized: Vec = serialize(&witness_vec); + let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + + // With or without annex, the tapscript should be returned. + assert_eq!(witness.taproot_annex(), None); + assert_eq!(witness_annex.taproot_annex(), Some(&annex[..])); + } + #[test] fn test_tx() { const S: &str = "02000000000102b44f26b275b8ad7b81146ba3dbecd081f9c1ea0dc05b97516f56045cfcd3df030100000000ffffffff1cb4749ae827c0b75f3d0a31e63efc8c71b47b5e3634a4c698cd53661cab09170100000000ffffffff020b3a0500000000001976a9143ea74de92762212c96f4dd66c4d72a4deb20b75788ac630500000000000016001493a8dfd1f0b6a600ab01df52b138cda0b82bb7080248304502210084622878c94f4c356ce49c8e33a063ec90f6ee9c0208540888cfab056cd1fca9022014e8dbfdfa46d318c6887afd92dcfa54510e057565e091d64d2ee3a66488f82c0121026e181ffb98ebfe5a64c983073398ea4bcd1548e7b971b4c175346a25a1c12e950247304402203ef00489a0d549114977df2820fab02df75bebb374f5eee9e615107121658cfa02204751f2d1784f8e841bff6d3bcf2396af2f1a5537c0e4397224873fbd3bfbe9cf012102ae6aa498ce2dd204e9180e71b4fb1260fe3d1a95c8025b34e56a9adf5f278af200000000"; From ac4db6369ddd329859a1b36c427fe6b4a5816f6b Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Thu, 21 Mar 2024 21:48:26 +0000 Subject: [PATCH 5/5] witness: Add Witness::witness_script inspector --- bitcoin/src/blockdata/witness.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 19bb268a2..995fe3dac 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -450,6 +450,15 @@ impl Witness { } }) } + + /// Get the p2wsh witness script following BIP141 rules. + /// + /// This does not guarantee that this represents a P2WS [`Witness`]. See + /// [Script::is_p2wsh](crate::blockdata::script::Script::is_p2wsh) to + /// check whether this is actually a P2WSH witness. + pub fn witness_script(&self) -> Option<&Script> { + self.last().map(Script::from_bytes) + } } impl Index for Witness {