extern crate alloc;
use alloc::vec::Vec;
const MARKER_OFFSET: usize = 4;
const FLAG_OFFSET: usize = 5;
const BIP141_FLAG: u8 = 0x01;
const MARKER_FLAG_LEN: usize = 2;
const WITNESS_COUNT_LEN: usize = 1;
const WITNESS_LEN_LEN: usize = 1;
const WITNESS_DATA_LEN: usize = 32;
const LOCKTIME_LEN: usize = 4;
const BIP141_WITNESS_LEN: usize = WITNESS_COUNT_LEN + WITNESS_LEN_LEN + WITNESS_DATA_LEN;
#[cfg(test)]
const MIN_COINBASE_TX_SUFFIX_LEN: usize = BIP141_WITNESS_LEN + LOCKTIME_LEN;
#[derive(Debug)]
pub enum StripBip141Error {
FailedToDeserializeCoinbaseVersion,
FailedToDeserializeCoinbaseInputs,
FailedToDeserializeCoinbaseOutputs,
FailedToDeserializeCoinbaseLockTime,
FailedToDeserializeCoinbaseWitness,
UnsupportedBip141Flag(u8),
UnexpectedWitnessFraming,
}
#[allow(clippy::type_complexity)]
pub fn try_strip_bip141(
coinbase_tx_prefix: &[u8],
coinbase_tx_suffix: &[u8],
) -> Result<Option<(Vec<u8>, Vec<u8>)>, StripBip141Error> {
let (Some(&marker), Some(&flag)) = (
coinbase_tx_prefix.get(MARKER_OFFSET),
coinbase_tx_prefix.get(FLAG_OFFSET),
) else {
return Err(StripBip141Error::FailedToDeserializeCoinbaseInputs);
};
let has_bip141_marker_and_flag = marker == 0x00 && flag != 0x00;
if !has_bip141_marker_and_flag {
return Ok(None);
}
if flag != BIP141_FLAG {
return Err(StripBip141Error::UnsupportedBip141Flag(flag));
}
let mut coinbase_tx_prefix_stripped_bip141 = coinbase_tx_prefix[0..MARKER_OFFSET].to_vec();
coinbase_tx_prefix_stripped_bip141
.extend_from_slice(&coinbase_tx_prefix[MARKER_OFFSET + MARKER_FLAG_LEN..]);
let locktime_position = coinbase_tx_suffix
.len()
.checked_sub(LOCKTIME_LEN)
.ok_or(StripBip141Error::FailedToDeserializeCoinbaseLockTime)?;
let witness_position = locktime_position
.checked_sub(BIP141_WITNESS_LEN)
.ok_or(StripBip141Error::FailedToDeserializeCoinbaseWitness)?;
if coinbase_tx_suffix[witness_position] != 1
|| coinbase_tx_suffix[witness_position + WITNESS_COUNT_LEN] != WITNESS_DATA_LEN as u8
{
return Err(StripBip141Error::UnexpectedWitnessFraming);
}
let mut coinbase_tx_suffix_stripped_bip141 = coinbase_tx_suffix[..witness_position].to_vec();
coinbase_tx_suffix_stripped_bip141.extend_from_slice(&coinbase_tx_suffix[locktime_position..]);
Ok(Some((
coinbase_tx_prefix_stripped_bip141,
coinbase_tx_suffix_stripped_bip141,
)))
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
#[test]
fn test_try_strip_bip141_sri_stripped() {
let coinbase_tx_prefix = [
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 60, 2, 69, 8, 0, 22, 47, 83, 116, 114, 97,
116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47, 47, 32,
]
.to_vec();
let coinbase_tx_suffix = [
254, 255, 255, 255, 2, 0, 242, 5, 42, 1, 0, 0, 0, 22, 0, 20, 235, 225, 183, 220, 194,
147, 204, 170, 14, 231, 67, 168, 111, 137, 223, 130, 88, 194, 8, 252, 0, 0, 0, 0, 0, 0,
0, 0, 38, 106, 36, 170, 33, 169, 237, 226, 246, 28, 63, 113, 209, 222, 253, 63, 169,
153, 223, 163, 105, 83, 117, 92, 105, 6, 137, 121, 153, 98, 180, 139, 235, 216, 54,
151, 78, 140, 249, 68, 8, 0, 0,
]
.to_vec();
let result = try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix)
.expect("failed trying to strip bip141");
assert!(result.is_none());
}
#[test]
fn test_try_strip_bip141_braiins() {
let coinbase_tx_prefix = [
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 76, 3, 206, 226, 13, 15, 47, 115, 108, 117,
115, 104, 47, 183, 0, 23, 4, 15, 174, 96, 163, 250, 190, 109, 109, 207, 170, 255, 170,
165, 167, 227, 112, 68, 27, 6, 55, 218, 219, 107, 176, 134, 175, 81, 46, 136, 141, 22,
52, 201, 113, 254, 3, 148, 98, 86, 129, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146, 162, 70, 0,
]
.to_vec();
let coinbase_tx_suffix = [
255, 255, 255, 255, 3, 194, 35, 192, 18, 0, 0, 0, 0, 23, 169, 20, 31, 12, 187, 236,
139, 196, 201, 69, 228, 225, 98, 73, 177, 30, 238, 145, 30, 222, 213, 95, 135, 0, 0, 0,
0, 0, 0, 0, 0, 38, 106, 36, 170, 33, 169, 237, 255, 27, 83, 239, 4, 153, 53, 158, 142,
142, 137, 160, 34, 151, 40, 92, 185, 56, 40, 203, 144, 158, 95, 20, 155, 139, 53, 163,
229, 145, 31, 81, 0, 0, 0, 0, 0, 0, 0, 0, 43, 106, 41, 82, 83, 75, 66, 76, 79, 67, 75,
58, 188, 174, 124, 112, 138, 29, 26, 158, 194, 209, 140, 216, 148, 128, 27, 238, 106,
189, 119, 214, 62, 100, 213, 246, 143, 66, 110, 12, 0, 120, 89, 116, 0, 0, 0, 0,
]
.to_vec();
let result = try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix)
.expect("failed trying to strip bip141");
assert!(result.is_none());
}
#[test]
fn test_try_strip_bip141_sri_not_yet_stripped() {
let coinbase_tx_prefix = [
2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 60, 2, 69, 8, 0, 22, 47, 83, 116,
114, 97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47, 47, 32,
]
.to_vec();
let coinbase_tx_suffix = [
254, 255, 255, 255, 2, 0, 242, 5, 42, 1, 0, 0, 0, 22, 0, 20, 235, 225, 183, 220, 194,
147, 204, 170, 14, 231, 67, 168, 111, 137, 223, 130, 88, 194, 8, 252, 0, 0, 0, 0, 0, 0,
0, 0, 38, 106, 36, 170, 33, 169, 237, 226, 246, 28, 63, 113, 209, 222, 253, 63, 169,
153, 223, 163, 105, 83, 117, 92, 105, 6, 137, 121, 153, 98, 180, 139, 235, 216, 54,
151, 78, 140, 249, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 8, 0, 0,
]
.to_vec();
let (coinbase_tx_prefix_stripped_bip141, coinbase_tx_suffix_stripped_bip141) =
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix)
.expect("failed trying to strip bip141")
.unwrap();
assert_eq!(
coinbase_tx_prefix_stripped_bip141,
[
2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 60, 2, 69, 8, 0, 22, 47, 83, 116,
114, 97, 116, 117, 109, 32, 86, 50, 32, 83, 82, 73, 32, 80, 111, 111, 108, 47, 47,
32
]
.to_vec()
);
assert_eq!(
coinbase_tx_suffix_stripped_bip141,
[
254, 255, 255, 255, 2, 0, 242, 5, 42, 1, 0, 0, 0, 22, 0, 20, 235, 225, 183, 220,
194, 147, 204, 170, 14, 231, 67, 168, 111, 137, 223, 130, 88, 194, 8, 252, 0, 0, 0,
0, 0, 0, 0, 0, 38, 106, 36, 170, 33, 169, 237, 226, 246, 28, 63, 113, 209, 222,
253, 63, 169, 153, 223, 163, 105, 83, 117, 92, 105, 6, 137, 121, 153, 98, 180, 139,
235, 216, 54, 151, 78, 140, 249, 68, 8, 0, 0
]
.to_vec()
);
}
#[test]
fn test_try_strip_bip141_rejects_short_prefix() {
let coinbase_tx_suffix = [0; MIN_COINBASE_TX_SUFFIX_LEN];
for prefix_len in 0..=FLAG_OFFSET {
let coinbase_tx_prefix = vec![0; prefix_len];
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::FailedToDeserializeCoinbaseInputs)
));
}
}
#[test]
fn test_try_strip_bip141_rejects_short_locktime_suffix() {
let coinbase_tx_prefix = [0, 0, 0, 0, 0, 1];
for suffix_len in 0..LOCKTIME_LEN {
let coinbase_tx_suffix = vec![0; suffix_len];
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::FailedToDeserializeCoinbaseLockTime)
));
}
}
#[test]
fn test_try_strip_bip141_rejects_short_witness_suffix() {
let coinbase_tx_prefix = [0, 0, 0, 0, 0, 1];
for suffix_len in LOCKTIME_LEN..MIN_COINBASE_TX_SUFFIX_LEN {
let coinbase_tx_suffix = vec![0; suffix_len];
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::FailedToDeserializeCoinbaseWitness)
));
}
}
#[test]
fn test_try_strip_bip141_accepts_minimum_lengths() {
let coinbase_tx_prefix = [0, 0, 0, 0, 0, 1];
let mut coinbase_tx_suffix = [0; MIN_COINBASE_TX_SUFFIX_LEN];
coinbase_tx_suffix[0] = 1;
coinbase_tx_suffix[1] = WITNESS_DATA_LEN as u8;
let result = try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix)
.expect("minimum valid bip141 parts should be accepted")
.expect("bip141 marker and flag should be stripped");
assert_eq!(result, (vec![0; 4], vec![0; 4]));
}
#[test]
fn test_try_strip_bip141_rejects_unsupported_flag() {
let mut coinbase_tx_suffix = [0; MIN_COINBASE_TX_SUFFIX_LEN];
coinbase_tx_suffix[0] = 1;
coinbase_tx_suffix[1] = WITNESS_DATA_LEN as u8;
for flag in 0x02..=0xff {
let coinbase_tx_prefix = [0, 0, 0, 0, 0, flag];
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::UnsupportedBip141Flag(rejected)) if rejected == flag
));
}
}
#[test]
fn test_try_strip_bip141_rejects_invalid_witness_framing() {
let coinbase_tx_prefix = [0, 0, 0, 0, 0, 1];
let mut coinbase_tx_suffix = [0; MIN_COINBASE_TX_SUFFIX_LEN];
coinbase_tx_suffix[0] = 2;
coinbase_tx_suffix[1] = WITNESS_DATA_LEN as u8;
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::UnexpectedWitnessFraming)
));
coinbase_tx_suffix[0] = 1;
coinbase_tx_suffix[1] = (WITNESS_DATA_LEN - 1) as u8;
assert!(matches!(
try_strip_bip141(&coinbase_tx_prefix, &coinbase_tx_suffix),
Err(StripBip141Error::UnexpectedWitnessFraming)
));
}
}