Merge rust-bitcoin/rust-bitcoin#4279: `Witness` api improvements and test cleanups

84bee2f7b0 Simplify `Witness` construction in tests (Martin Habovstiak)
3551ec2c69 Don't access internalls of `Witness` in tests (Martin Habovstiak)
c8078360d2 Impl `PartialEq` between `Witness` and containers (Martin Habovstiak)
587a66da47 Add a bunch of missing conversions for `Witness` (Martin Habovstiak)

Pull request description:

  This is supposed to go in front of #4250

  `Witness` lacked a bunch of APIs that were making it harder to use and test, so this also adds them in addition to cleaning up tests. (I only realized they are missing when I tried to clean up tests and got a bunch of errors.)

ACKs for top commit:
  tcharding:
    ACK 84bee2f7b0
  apoelstra:
    ACK 84bee2f7b06a7bd1f435aaad18fa76a15188326e; successfully ran local tests

Tree-SHA512: 7973f2a56b070babba7b4c632f45858154ccd00f8e77956ad2d28cb66e1fd18ff60d92c031ba3b76d0958e4acd34adfca10607fa26ec569dfd52ba1c1e2c79eb
This commit is contained in:
merge-script 2025-03-26 03:38:39 +00:00
commit 143531de7c
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
2 changed files with 182 additions and 79 deletions

View File

@ -387,14 +387,8 @@ mod test {
// 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, 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();
let witness = Witness::from([&*tapscript, &control_block]);
let witness_annex = Witness::from([&*tapscript, &control_block, &annex]);
// With or without annex, the tapscript should be returned.
assert_eq!(witness.tapscript(), Some(Script::from_bytes(&tapscript[..])));
@ -409,14 +403,8 @@ mod test {
// 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, 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();
let witness = Witness::from([&*tapscript, &control_block]);
let witness_annex = Witness::from([&*tapscript, &control_block, &annex]);
let expected_leaf_script =
LeafScript { version: LeafVersion::TapScript, script: Script::from_bytes(&tapscript) };
@ -432,14 +420,8 @@ mod test {
// 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];
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();
let witness = Witness::from([&*signature]);
let witness_annex = Witness::from([&*signature, &annex]);
// With or without annex, no tapscript should be returned.
assert_eq!(witness.tapscript(), None);
@ -454,18 +436,9 @@ mod test {
let annex = hex!("50");
let signature = vec![0xff; 64];
let witness_vec = vec![tapscript.clone(), control_block.clone()];
let witness_vec_annex = vec![tapscript.clone(), control_block.clone(), annex.clone()];
let witness_vec_key_spend_annex = vec![signature, annex];
let witness_serialized: Vec<u8> = serialize(&witness_vec);
let witness_serialized_annex: Vec<u8> = serialize(&witness_vec_annex);
let witness_serialized_key_spend_annex: Vec<u8> = serialize(&witness_vec_key_spend_annex);
let witness = deserialize::<Witness>(&witness_serialized[..]).unwrap();
let witness_annex = deserialize::<Witness>(&witness_serialized_annex[..]).unwrap();
let witness_key_spend_annex =
deserialize::<Witness>(&witness_serialized_key_spend_annex[..]).unwrap();
let witness = Witness::from([&*tapscript, &control_block]);
let witness_annex = Witness::from([&*tapscript, &control_block, &annex]);
let witness_key_spend_annex = Witness::from([&*signature, &annex]);
// With or without annex, the tapscript should be returned.
assert_eq!(witness.taproot_control_block(), Some(&control_block[..]));
@ -480,14 +453,8 @@ mod test {
// 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();
let witness = Witness::from([&*tapscript, &control_block]);
let witness_annex = Witness::from([&*tapscript, &control_block, &annex]);
// With or without annex, the tapscript should be returned.
assert_eq!(witness.taproot_annex(), None);
@ -498,14 +465,8 @@ mod test {
// 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();
let witness = Witness::from([&*signature]);
let witness_annex = Witness::from([&*signature, &annex]);
// With or without annex, the tapscript should be returned.
assert_eq!(witness.taproot_annex(), None);

View File

@ -14,7 +14,7 @@ use internals::compact_size;
use internals::wrap_debug::WrapDebug;
use internals::slice::SliceExt;
use crate::prelude::Vec;
use crate::prelude::{Box, Vec};
/// The Witness is the data used to unlock bitcoin since the [SegWit upgrade].
///
@ -238,6 +238,109 @@ fn decode_cursor(bytes: &[u8], start_of_indices: usize, index: usize) -> Option<
bytes.get_array::<4>(start).map(|index_bytes| u32::from_ne_bytes(*index_bytes) as usize)
}
// Note: we use `Borrow` in the following `PartialEq` impls specifically because of its additional
// constraints on equality semantics.
impl<T: core::borrow::Borrow<[u8]>> PartialEq<[T]> for Witness {
fn eq(&self, rhs: &[T]) -> bool {
if self.len() != rhs.len() {
return false;
}
self.iter().zip(rhs).all(|(left, right)| left == right.borrow())
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<&[T]> for Witness {
fn eq(&self, rhs: &&[T]) -> bool {
*self == **rhs
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for [T] {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for &[T] {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == **self
}
}
impl<const N: usize, T: core::borrow::Borrow<[u8]>> PartialEq<[T; N]> for Witness {
fn eq(&self, rhs: &[T; N]) -> bool {
*self == *rhs.as_slice()
}
}
impl<const N: usize, T: core::borrow::Borrow<[u8]>> PartialEq<&[T; N]> for Witness {
fn eq(&self, rhs: &&[T; N]) -> bool {
*self == *rhs.as_slice()
}
}
impl<const N: usize, T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for [T; N] {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
impl<const N: usize, T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for &[T; N] {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == **self
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Vec<T>> for Witness {
fn eq(&self, rhs: &Vec<T>) -> bool {
*self == **rhs
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for Vec<T> {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Box<[T]>> for Witness {
fn eq(&self, rhs: &Box<[T]>) -> bool {
*self == **rhs
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for Box<[T]> {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<alloc::rc::Rc<[T]>> for Witness {
fn eq(&self, rhs: &alloc::rc::Rc<[T]>) -> bool {
*self == **rhs
}
}
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for alloc::rc::Rc<[T]> {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
#[cfg(target_has_atomic = "ptr")]
impl<T: core::borrow::Borrow<[u8]>> PartialEq<alloc::sync::Arc<[T]>> for Witness {
fn eq(&self, rhs: &alloc::sync::Arc<[T]>) -> bool {
*self == **rhs
}
}
#[cfg(target_has_atomic = "ptr")]
impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for alloc::sync::Arc<[T]> {
fn eq(&self, rhs: &Witness) -> bool {
*rhs == *self
}
}
/// Debug implementation that displays the witness as a structured output containing:
/// - Number of witness elements
/// - Total bytes across all elements
@ -410,6 +513,46 @@ impl From<Vec<&[u8]>> for Witness {
fn from(vec: Vec<&[u8]>) -> Self { Witness::from_slice(&vec) }
}
impl<const N: usize> From<[&[u8]; N]> for Witness {
#[inline]
fn from(arr: [&[u8]; N]) -> Self { Witness::from_slice(&arr) }
}
impl<const N: usize> From<&[&[u8]; N]> for Witness {
#[inline]
fn from(arr: &[&[u8]; N]) -> Self { Witness::from_slice(arr) }
}
impl<const N: usize> From<&[[u8; N]]> for Witness {
#[inline]
fn from(slice: &[[u8; N]]) -> Self { Witness::from_slice(slice) }
}
impl<const N: usize> From<&[&[u8; N]]> for Witness {
#[inline]
fn from(slice: &[&[u8; N]]) -> Self { Witness::from_slice(slice) }
}
impl<const N: usize, const M: usize> From<[[u8; M]; N]> for Witness {
#[inline]
fn from(slice: [[u8; M]; N]) -> Self { Witness::from_slice(&slice) }
}
impl<const N: usize, const M: usize> From<&[[u8; M]; N]> for Witness {
#[inline]
fn from(slice: &[[u8; M]; N]) -> Self { Witness::from_slice(slice) }
}
impl<const N: usize, const M: usize> From<[&[u8; M]; N]> for Witness {
#[inline]
fn from(slice: [&[u8; M]; N]) -> Self { Witness::from_slice(&slice) }
}
impl<const N: usize, const M: usize> From<&[&[u8; M]; N]> for Witness {
#[inline]
fn from(slice: &[&[u8; M]; N]) -> Self { Witness::from_slice(slice) }
}
impl Default for Witness {
#[inline]
fn default() -> Self { Self::new() }
@ -438,11 +581,7 @@ mod test {
// A witness with a single element that is empty (zero length).
fn single_empty_element() -> Witness {
// The first is 0 serialized as a compact size integer.
// The last four bytes represent start at index 0.
let content = [0_u8; 5];
Witness { witness_elements: 1, content: content.to_vec(), indices_start: 1 }
Witness::from([[0u8; 0]])
}
#[test]
@ -477,13 +616,7 @@ mod test {
witness.push(push);
assert!(!witness.is_empty());
let elements = [1u8, 11];
let expected = Witness {
witness_elements: 1,
content: append_u32_vec(&elements, &[0]), // Start at index 0.
indices_start: elements.len(),
};
assert_eq!(witness, expected);
assert_eq!(witness, [[11_u8]]);
let element_0 = push.as_slice();
assert_eq!(element_0, &witness[0]);
@ -500,13 +633,7 @@ mod test {
let push = [21u8, 22u8];
witness.push(push);
let elements = [1u8, 11, 2, 21, 22];
let expected = Witness {
witness_elements: 2,
content: append_u32_vec(&elements, &[0, 2]),
indices_start: elements.len(),
};
assert_eq!(witness, expected);
assert_eq!(witness, [&[11_u8] as &[_], &[21, 22]]);
let element_1 = push.as_slice();
assert_eq!(element_1, &witness[1]);
@ -523,13 +650,7 @@ mod test {
let push = [31u8, 32u8];
witness.push(push);
let elements = [1u8, 11, 2, 21, 22, 2, 31, 32];
let expected = Witness {
witness_elements: 3,
content: append_u32_vec(&elements, &[0, 2, 5]),
indices_start: elements.len(),
};
assert_eq!(witness, expected);
assert_eq!(witness, [&[11_u8] as &[_], &[21, 22], &[31, 32]]);
let element_2 = push.as_slice();
assert_eq!(element_2, &witness[2]);
@ -603,6 +724,27 @@ mod test {
assert!(expected.is_empty());
}
#[test]
fn partial_eq() {
const EMPTY_BYTES: &[u8] = &[];
assert_eq!(Vec::<&[u8]>::new(), Witness::new());
macro_rules! ck {
($container:expr) => {
{
let container = $container;
let witness = Witness::from(Clone::clone(&container));
assert_eq!(witness, container, stringify!($container));
}
}
}
ck!([EMPTY_BYTES]);
ck!([EMPTY_BYTES, EMPTY_BYTES]);
ck!([[42]]);
ck!([[42, 21]]);
ck!([&[42], EMPTY_BYTES]);
ck!([[42u8], [21]]);
}
#[test]
#[cfg(feature = "serde")]
fn serde_bincode_backward_compatibility() {