Merge rust-bitcoin/rust-bitcoin#4186: Clean up Witness API

Pull request description:

  Enhance Witness struct element access methods:

  - Rename `nth()` to `get()` for clearer slice-like element retrieval
  - Introduce `get_back()` method for flexible reverse indexing
  - Remove redundant `second_to_last()` and `third_to_last()` methods
  - Add `#[track_caller]` to index implementation for better error tracking
  - Update all references to use new method names
  - Improve documentation with usage examples

  The changes provide a more intuitive and consistent approach to accessing witness elements.

  Close #4098

ACKs for top commit:
  Kixunil:
    ACK 3ca3218c23
  tcharding:
    ACK 3ca3218c23
  apoelstra:
    ACK 3ca3218c236c63a9b006047524e2b47e310f07d9; successfully ran local tests

Tree-SHA512: 163e7457f3fe5141373e27a6df5fe1da6f2f05f02e877ef96243510d030d832c0fa86ade781e015a3c392f004651170b60438a83d330f1059457e5ade6478af7
This commit is contained in:
merge-script 2025-03-06 18:43:27 +00:00
commit febce17eff
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 65 additions and 63 deletions

View File

@ -29,8 +29,8 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, amount: Amount) {
// BIP-141: The witness must consist of exactly 2 items (≤ 520 bytes each). The first one a
// signature, and the second one a public key.
assert_eq!(witness.len(), 2);
let sig_bytes = witness.nth(0).unwrap();
let pk_bytes = witness.nth(1).unwrap();
let sig_bytes = witness.get(0).unwrap();
let pk_bytes = witness.get(1).unwrap();
let sig = ecdsa::Signature::from_slice(sig_bytes).expect("failed to parse sig");
@ -118,7 +118,7 @@ fn compute_sighash_p2wsh(raw_tx: &[u8], inp_idx: usize, amount: Amount) {
//in an M of N multisig, the witness elements from 1 (0-based) to M-2 are signatures (with sighash flags as the last byte)
for n in 1..=witness.len() - 2 {
let sig_bytes = witness.nth(n).expect("out of bounds");
let sig_bytes = witness.get(n).expect("out of bounds");
let sig = ecdsa::Signature::from_slice(sig_bytes).expect("failed to parse sig");
let sig_len = sig_bytes.len() - 1; //last byte is EcdsaSighashType sighash flag
//ECDSA signature in DER format lengths are between 70 and 72 bytes

View File

@ -257,7 +257,7 @@ impl<'a> P2TrSpend<'a> {
}),
2 if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => {
let spend = P2TrSpend::Key {
// signature: witness.second_to_last().expect("len > 1"),
// signature: witness.get_back(1).expect("len > 1"),
annex: witness.last(),
};
Some(spend)
@ -267,15 +267,15 @@ impl<'a> P2TrSpend<'a> {
// arm.
3.. if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => {
let spend = P2TrSpend::Script {
leaf_script: Script::from_bytes(witness.third_to_last().expect("len > 2")),
control_block: witness.second_to_last().expect("len > 1"),
leaf_script: Script::from_bytes(witness.get_back(2).expect("len > 2")),
control_block: witness.get_back(1).expect("len > 1"),
annex: witness.last(),
};
Some(spend)
}
_ => {
let spend = P2TrSpend::Script {
leaf_script: Script::from_bytes(witness.second_to_last().expect("len > 1")),
leaf_script: Script::from_bytes(witness.get_back(1).expect("len > 1")),
control_block: witness.last().expect("len > 0"),
annex: None,
};
@ -514,13 +514,10 @@ mod test {
assert_eq!(expected_wit[i], wit_el.to_lower_hex_string());
}
assert_eq!(expected_wit[1], tx.input[0].witness.last().unwrap().to_lower_hex_string());
assert_eq!(
expected_wit[0],
tx.input[0].witness.second_to_last().unwrap().to_lower_hex_string()
);
assert_eq!(expected_wit[0], tx.input[0].witness.nth(0).unwrap().to_lower_hex_string());
assert_eq!(expected_wit[1], tx.input[0].witness.nth(1).unwrap().to_lower_hex_string());
assert_eq!(None, tx.input[0].witness.nth(2));
assert_eq!(expected_wit[0], tx.input[0].witness.get_back(1).unwrap().to_lower_hex_string());
assert_eq!(expected_wit[0], tx.input[0].witness.get(0).unwrap().to_lower_hex_string());
assert_eq!(expected_wit[1], tx.input[0].witness.get(1).unwrap().to_lower_hex_string());
assert_eq!(None, tx.input[0].witness.get(2));
assert_eq!(expected_wit[0], tx.input[0].witness[0].to_lower_hex_string());
assert_eq!(expected_wit[1], tx.input[0].witness[1].to_lower_hex_string());

View File

@ -185,37 +185,41 @@ impl Witness {
/// Returns the last element in the witness, if any.
#[inline]
pub fn last(&self) -> Option<&[u8]> {
if self.witness_elements == 0 {
pub fn last(&self) -> Option<&[u8]> { self.get_back(0) }
/// Retrieves an element from the end of the witness by its reverse index.
///
/// `index` is 0-based from the end, where 0 is the last element, 1 is the second-to-last, etc.
///
/// Returns `None` if the requested index is beyond the witness's elements.
///
/// # Examples
/// ```
/// use bitcoin_primitives::witness::Witness;
///
/// let mut witness = Witness::new();
/// witness.push(b"A");
/// witness.push(b"B");
/// witness.push(b"C");
/// witness.push(b"D");
///
/// assert_eq!(witness.get_back(0), Some(b"D".as_slice()));
/// assert_eq!(witness.get_back(1), Some(b"C".as_slice()));
/// assert_eq!(witness.get_back(2), Some(b"B".as_slice()));
/// assert_eq!(witness.get_back(3), Some(b"A".as_slice()));
/// assert_eq!(witness.get_back(4), None);
/// ```
pub fn get_back(&self, index: usize) -> Option<&[u8]> {
if self.witness_elements <= index {
None
} else {
self.nth(self.witness_elements - 1)
self.get(self.witness_elements - 1 - index)
}
}
/// Returns the second-to-last element in the witness, if any.
/// Returns a specific element from the witness by its index, if any.
#[inline]
pub fn second_to_last(&self) -> Option<&[u8]> {
if self.witness_elements <= 1 {
None
} else {
self.nth(self.witness_elements - 2)
}
}
/// Returns the third-to-last element in the witness, if any.
#[inline]
pub fn third_to_last(&self) -> Option<&[u8]> {
if self.witness_elements <= 2 {
None
} else {
self.nth(self.witness_elements - 3)
}
}
/// Return the nth element in the witness, if any
#[inline]
pub fn nth(&self, index: usize) -> Option<&[u8]> {
pub fn get(&self, index: usize) -> Option<&[u8]> {
let pos = decode_cursor(&self.content, self.indices_start, index)?;
self.element_at(pos)
}
@ -274,8 +278,9 @@ pub struct Iter<'a> {
impl Index<usize> for Witness {
type Output = [u8];
#[track_caller]
#[inline]
fn index(&self, index: usize) -> &Self::Output { self.nth(index).expect("out of bounds") }
fn index(&self, index: usize) -> &Self::Output { self.get(index).expect("out of bounds") }
}
impl<'a> Iterator for Iter<'a> {
@ -468,12 +473,12 @@ mod test {
let mut witness = Witness::default();
assert!(witness.is_empty());
assert_eq!(witness.last(), None);
assert_eq!(witness.second_to_last(), None);
assert_eq!(witness.get_back(1), None);
assert_eq!(witness.nth(0), None);
assert_eq!(witness.nth(1), None);
assert_eq!(witness.nth(2), None);
assert_eq!(witness.nth(3), None);
assert_eq!(witness.get(0), None);
assert_eq!(witness.get(1), None);
assert_eq!(witness.get(2), None);
assert_eq!(witness.get(3), None);
// Push a single byte element onto the witness stack.
let push = [11_u8];
@ -491,13 +496,13 @@ mod test {
let element_0 = push.as_slice();
assert_eq!(element_0, &witness[0]);
assert_eq!(witness.second_to_last(), None);
assert_eq!(witness.get_back(1), None);
assert_eq!(witness.last(), Some(element_0));
assert_eq!(witness.nth(0), Some(element_0));
assert_eq!(witness.nth(1), None);
assert_eq!(witness.nth(2), None);
assert_eq!(witness.nth(3), None);
assert_eq!(witness.get(0), Some(element_0));
assert_eq!(witness.get(1), None);
assert_eq!(witness.get(2), None);
assert_eq!(witness.get(3), None);
// Now push 2 byte element onto the witness stack.
let push = [21u8, 22u8];
@ -514,12 +519,12 @@ mod test {
let element_1 = push.as_slice();
assert_eq!(element_1, &witness[1]);
assert_eq!(witness.nth(0), Some(element_0));
assert_eq!(witness.nth(1), Some(element_1));
assert_eq!(witness.nth(2), None);
assert_eq!(witness.nth(3), None);
assert_eq!(witness.get(0), Some(element_0));
assert_eq!(witness.get(1), Some(element_1));
assert_eq!(witness.get(2), None);
assert_eq!(witness.get(3), None);
assert_eq!(witness.second_to_last(), Some(element_0));
assert_eq!(witness.get_back(1), Some(element_0));
assert_eq!(witness.last(), Some(element_1));
// Now push another 2 byte element onto the witness stack.
@ -537,13 +542,13 @@ mod test {
let element_2 = push.as_slice();
assert_eq!(element_2, &witness[2]);
assert_eq!(witness.nth(0), Some(element_0));
assert_eq!(witness.nth(1), Some(element_1));
assert_eq!(witness.nth(2), Some(element_2));
assert_eq!(witness.nth(3), None);
assert_eq!(witness.get(0), Some(element_0));
assert_eq!(witness.get(1), Some(element_1));
assert_eq!(witness.get(2), Some(element_2));
assert_eq!(witness.get(3), None);
assert_eq!(witness.third_to_last(), Some(element_0));
assert_eq!(witness.second_to_last(), Some(element_1));
assert_eq!(witness.get_back(2), Some(element_0));
assert_eq!(witness.get_back(1), Some(element_1));
assert_eq!(witness.last(), Some(element_2));
}
@ -574,8 +579,8 @@ mod test {
let indices_start = elements.len();
let witness =
Witness::from_parts__unstable(content.clone(), witness_elements, indices_start);
assert_eq!(witness.nth(0).unwrap(), [11_u8]);
assert_eq!(witness.nth(1).unwrap(), [21_u8, 22]);
assert_eq!(witness.get(0).unwrap(), [11_u8]);
assert_eq!(witness.get(1).unwrap(), [21_u8, 22]);
assert_eq!(witness.size(), 6);
}