use core::convert::Infallible;
use core::fmt;
pub const WITNESS_PROGRAM_MIN_SIZE: usize = 2;
pub const WITNESS_PROGRAM_MAX_SIZE: usize = 64;
pub const P2A_PROGRAM: [u8; 2] = [78, 115];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WitnessProgramClass {
P2wpkh,
P2wsh,
P2wsh512,
P2a,
Upgradable,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum WitnessProgramError {
InvalidLength(usize),
InvalidSegwitV0Length(usize),
}
impl From<Infallible> for WitnessProgramError {
fn from(never: Infallible) -> Self {
match never {}
}
}
impl fmt::Display for WitnessProgramError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength(len) => {
write!(f, "witness program must be between 2 and 64 bytes: length={}", len)
}
Self::InvalidSegwitV0Length(len) => {
write!(f, "a v0 witness program must be either 20 or 32 bytes: length={}", len)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WitnessProgramError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidLength(_) | Self::InvalidSegwitV0Length(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParsedWitnessProgram<'a> {
version: u8,
program: &'a [u8],
}
impl<'a> ParsedWitnessProgram<'a> {
pub fn from_program(version: u8, program: &'a [u8]) -> Self {
Self { version, program }
}
pub fn parse_script_pubkey(script_bytes: &'a [u8]) -> Option<Self> {
if script_bytes.len() < WITNESS_PROGRAM_MIN_SIZE + 2 {
return None;
}
let version = match script_bytes[0] {
0x00 => 0,
0x51..=0x60 => script_bytes[0] - 0x50,
_ => return None,
};
let push_len = parse_direct_push_len(script_bytes.get(1).copied())?;
if script_bytes.len() != push_len + 2 {
return None;
}
Some(Self { version, program: &script_bytes[2..] })
}
pub fn version(self) -> u8 {
self.version
}
pub fn program(self) -> &'a [u8] {
self.program
}
pub fn class(self) -> WitnessProgramClass {
classify_witness_program(self.version, self.program)
}
}
pub fn validate_witness_program(
version: u8,
program_len: usize,
) -> Result<(), WitnessProgramError> {
if !(WITNESS_PROGRAM_MIN_SIZE..=WITNESS_PROGRAM_MAX_SIZE).contains(&program_len) {
return Err(WitnessProgramError::InvalidLength(program_len));
}
if version == 0 && program_len != 20 && program_len != 32 {
return Err(WitnessProgramError::InvalidSegwitV0Length(program_len));
}
Ok(())
}
pub fn classify_witness_program(version: u8, program: &[u8]) -> WitnessProgramClass {
match (version, program.len()) {
(0, 20) => WitnessProgramClass::P2wpkh,
(0, 32) => WitnessProgramClass::P2wsh,
(1, 64) => WitnessProgramClass::P2wsh512,
(1, 2) if program == P2A_PROGRAM => WitnessProgramClass::P2a,
_ => WitnessProgramClass::Upgradable,
}
}
fn parse_direct_push_len(opcode: Option<u8>) -> Option<usize> {
let value = opcode?;
if value <= 75 {
Some(value as usize)
} else {
None
}
}