Merge rust-bitcoin/rust-bitcoin#2646: Some additional inspectors on Script and Witness

ac4db6369d witness: Add Witness::witness_script inspector (Steven Roose)
6cc6c8621a witness: Add Witness::taproot_annex (Steven Roose)
b0848022eb witness: Add Witness::taproot_control_block (Steven Roose)
ef336e1387 witness: Improve Witness::tapscript (Steven Roose)
e48a2e4225 script: Add Script::redeem_script inspector (Steven Roose)

Pull request description:

  Bundled these because they are very similar. Got a bunch of larger changes coming up based on these. I've been using these for a while for TXHASH work.

ACKs for top commit:
  apoelstra:
    ACK ac4db6369d but will need to wait for next release. I think we should merge these as-is although they will be much clearer after we do script tagging.
  tcharding:
    ACK ac4db6369d

Tree-SHA512: e1590d1bdc8b91aeba137453f0cdaa7e1ae6df3c8e9e1e0f087ed9be1a6beaf2286818379247d26c5dd27d07c12c10433db1c9b9a71667ab4d8d37c7deff1373
This commit is contained in:
merge-script 2024-07-12 18:09:10 +00:00
commit b14625f0d5
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 145 additions and 28 deletions

View File

@ -410,6 +410,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 todays Bitcoin network.
#[deprecated(since = "0.32.0", note = "use minimal_non_dust and friends")]

View File

@ -403,24 +403,70 @@ impl Witness {
/// [Script::is_p2tr](crate::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
}
})
}
/// 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
}
})
}
/// 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
}
})
}
/// 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)
}
}
@ -712,22 +758,74 @@ mod test {
let witness_serialized: Vec<u8> = serialize(&witness_vec);
let witness_serialized_annex: Vec<u8> = 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>(&witness_serialized[..]).unwrap();
let witness_annex = deserialize::<Witness>(&witness_serialized_annex[..]).unwrap();
// With or without annex, the tapscript should be returned.
assert_eq!(witness.tapscript(), Some(Script::from_bytes(&tapscript[..])));
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<u8> = serialize(&witness_vec);
let witness_serialized_annex: Vec<u8> = serialize(&witness_vec_annex);
let witness = deserialize::<Witness>(&witness_serialized[..]).unwrap();
let witness_annex = deserialize::<Witness>(&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_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<u8> = serialize(&witness_vec);
let witness_serialized_annex: Vec<u8> = serialize(&witness_vec_annex);
let witness = deserialize::<Witness>(&witness_serialized[..]).unwrap();
let witness_annex = deserialize::<Witness>(&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<u8> = serialize(&witness_vec);
let witness_serialized_annex: Vec<u8> = serialize(&witness_vec_annex);
let witness = deserialize::<Witness>(&witness_serialized[..]).unwrap();
let witness_annex = deserialize::<Witness>(&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";