tidecoin_primitives/script/
witness_program.rs1use core::convert::Infallible;
6use core::fmt;
7
8pub const WITNESS_PROGRAM_MIN_SIZE: usize = 2;
10pub const WITNESS_PROGRAM_MAX_SIZE: usize = 64;
12pub const P2A_PROGRAM: [u8; 2] = [78, 115];
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum WitnessProgramClass {
18 P2wpkh,
20 P2wsh,
22 P2wsh512,
24 P2a,
26 Upgradable,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum WitnessProgramError {
34 InvalidLength(usize),
36 InvalidSegwitV0Length(usize),
38}
39
40impl From<Infallible> for WitnessProgramError {
41 fn from(never: Infallible) -> Self {
42 match never {}
43 }
44}
45
46impl fmt::Display for WitnessProgramError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 Self::InvalidLength(len) => {
50 write!(f, "witness program must be between 2 and 64 bytes: length={}", len)
51 }
52 Self::InvalidSegwitV0Length(len) => {
53 write!(f, "a v0 witness program must be either 20 or 32 bytes: length={}", len)
54 }
55 }
56 }
57}
58
59#[cfg(feature = "std")]
60impl std::error::Error for WitnessProgramError {
61 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62 match self {
63 Self::InvalidLength(_) | Self::InvalidSegwitV0Length(_) => None,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct ParsedWitnessProgram<'a> {
71 version: u8,
72 program: &'a [u8],
73}
74
75impl<'a> ParsedWitnessProgram<'a> {
76 pub fn from_program(version: u8, program: &'a [u8]) -> Self {
78 Self { version, program }
79 }
80
81 pub fn parse_script_pubkey(script_bytes: &'a [u8]) -> Option<Self> {
83 if script_bytes.len() < WITNESS_PROGRAM_MIN_SIZE + 2 {
84 return None;
85 }
86
87 let version = match script_bytes[0] {
88 0x00 => 0,
89 0x51..=0x60 => script_bytes[0] - 0x50,
90 _ => return None,
91 };
92 let push_len = parse_direct_push_len(script_bytes.get(1).copied())?;
93 if script_bytes.len() != push_len + 2 {
94 return None;
95 }
96
97 Some(Self { version, program: &script_bytes[2..] })
98 }
99
100 pub fn version(self) -> u8 {
102 self.version
103 }
104
105 pub fn program(self) -> &'a [u8] {
107 self.program
108 }
109
110 pub fn class(self) -> WitnessProgramClass {
112 classify_witness_program(self.version, self.program)
113 }
114}
115
116pub fn validate_witness_program(
124 version: u8,
125 program_len: usize,
126) -> Result<(), WitnessProgramError> {
127 if !(WITNESS_PROGRAM_MIN_SIZE..=WITNESS_PROGRAM_MAX_SIZE).contains(&program_len) {
128 return Err(WitnessProgramError::InvalidLength(program_len));
129 }
130 if version == 0 && program_len != 20 && program_len != 32 {
131 return Err(WitnessProgramError::InvalidSegwitV0Length(program_len));
132 }
133 Ok(())
134}
135
136pub fn classify_witness_program(version: u8, program: &[u8]) -> WitnessProgramClass {
138 match (version, program.len()) {
139 (0, 20) => WitnessProgramClass::P2wpkh,
140 (0, 32) => WitnessProgramClass::P2wsh,
141 (1, 64) => WitnessProgramClass::P2wsh512,
142 (1, 2) if program == P2A_PROGRAM => WitnessProgramClass::P2a,
143 _ => WitnessProgramClass::Upgradable,
144 }
145}
146
147fn parse_direct_push_len(opcode: Option<u8>) -> Option<usize> {
148 let value = opcode?;
149 if value <= 75 {
150 Some(value as usize)
151 } else {
152 None
153 }
154}