1use alloc::vec::Vec;
6
7use crate::{ScriptError, VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM, VERIFY_WITNESS_V1_512};
8use hashes::{sha256, sha512};
9use primitives::{
10 opcodes::all,
11 script::{
12 Builder as ScriptBuilderT, ParsedWitnessProgram, PushBytesBuf,
13 WitnessProgramClass as PrimitiveWitnessProgramClass,
14 },
15 Witness,
16};
17
18type Builder = ScriptBuilderT<()>;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum WitnessProgramClass {
23 P2wpkh,
25 P2wsh,
27 WitnessV1_512,
29 Upgradable,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum WitnessSigVersion {
36 V0,
38 V1_512,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum WitnessSigops {
45 None,
47 Fixed(u32),
49 CountExecutedScript,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum WitnessExecutionPlan {
56 Upgradable,
58 Execute {
60 sigversion: WitnessSigVersion,
62 script_bytes: Vec<u8>,
64 stack_items: Vec<Vec<u8>>,
66 sigops: WitnessSigops,
68 },
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct WitnessProgram<'a>(ParsedWitnessProgram<'a>);
74
75impl<'a> WitnessProgram<'a> {
76 pub fn parse_program(version: u8, program: &'a [u8]) -> Self {
78 Self(ParsedWitnessProgram::from_program(version, program))
79 }
80
81 pub fn parse(script_bytes: &'a [u8]) -> Option<Self> {
83 ParsedWitnessProgram::parse_script_pubkey(script_bytes).map(Self)
84 }
85
86 pub fn version(self) -> u8 {
88 self.0.version()
89 }
90
91 pub fn program(self) -> &'a [u8] {
93 self.0.program()
94 }
95
96 pub fn is_v1_512(script_bytes: &'a [u8]) -> bool {
98 Self::parse(script_bytes)
99 .is_some_and(|program| program.0.class() == PrimitiveWitnessProgramClass::P2wsh512)
100 }
101
102 pub fn classify(self, flags: u32) -> Result<WitnessProgramClass, ScriptError> {
104 match self.version() {
105 0 => match self.0.class() {
106 PrimitiveWitnessProgramClass::P2wpkh => Ok(WitnessProgramClass::P2wpkh),
107 PrimitiveWitnessProgramClass::P2wsh => Ok(WitnessProgramClass::P2wsh),
108 PrimitiveWitnessProgramClass::P2wsh512
109 | PrimitiveWitnessProgramClass::P2a
110 | PrimitiveWitnessProgramClass::Upgradable => {
111 Err(ScriptError::WitnessProgramWrongLength)
112 }
113 },
114 1 => {
115 if flags & VERIFY_WITNESS_V1_512 == 0 {
116 if flags & VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM != 0 {
117 Err(ScriptError::DiscourageUpgradableWitnessProgram)
118 } else {
119 Ok(WitnessProgramClass::Upgradable)
120 }
121 } else {
122 match self.0.class() {
123 PrimitiveWitnessProgramClass::P2wsh512 => {
124 Ok(WitnessProgramClass::WitnessV1_512)
125 }
126 PrimitiveWitnessProgramClass::P2wpkh
127 | PrimitiveWitnessProgramClass::P2wsh
128 | PrimitiveWitnessProgramClass::P2a
129 | PrimitiveWitnessProgramClass::Upgradable => {
130 Err(ScriptError::WitnessProgramWrongLength)
131 }
132 }
133 }
134 }
135 2..=16 => {
136 if flags & VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM != 0 {
137 Err(ScriptError::DiscourageUpgradableWitnessProgram)
138 } else {
139 Ok(WitnessProgramClass::Upgradable)
140 }
141 }
142 _ => Ok(WitnessProgramClass::Upgradable),
143 }
144 }
145
146 pub fn execution_plan(
148 self,
149 flags: u32,
150 witness: &Witness,
151 ) -> Result<WitnessExecutionPlan, ScriptError> {
152 match self.classify(flags)? {
153 WitnessProgramClass::P2wpkh => build_p2wpkh_plan(self.program(), witness),
154 WitnessProgramClass::P2wsh => build_p2wsh_plan(self.program(), witness),
155 WitnessProgramClass::WitnessV1_512 => {
156 build_witness_v1_512_plan(self.program(), witness)
157 }
158 WitnessProgramClass::Upgradable => Ok(WitnessExecutionPlan::Upgradable),
159 }
160 }
161}
162
163fn build_p2wpkh_plan(
164 program: &[u8],
165 witness: &Witness,
166) -> Result<WitnessExecutionPlan, ScriptError> {
167 if witness.len() != 2 {
168 return Err(ScriptError::WitnessProgramMismatch);
169 }
170
171 let program_bytes = PushBytesBuf::try_from(program.to_vec())
172 .map_err(|_| ScriptError::WitnessProgramWrongLength)?;
173 let script = Builder::new()
174 .push_opcode(all::OP_DUP)
175 .push_opcode(all::OP_HASH160)
176 .push_slice(program_bytes)
177 .push_opcode(all::OP_EQUALVERIFY)
178 .push_opcode(all::OP_CHECKSIG)
179 .into_script();
180
181 Ok(WitnessExecutionPlan::Execute {
182 sigversion: WitnessSigVersion::V0,
183 script_bytes: script.into_bytes(),
184 stack_items: witness_items(witness, witness.len()),
185 sigops: WitnessSigops::Fixed(1),
186 })
187}
188
189fn build_p2wsh_plan(
190 program: &[u8],
191 witness: &Witness,
192) -> Result<WitnessExecutionPlan, ScriptError> {
193 if witness.is_empty() {
194 return Err(ScriptError::WitnessProgramWitnessEmpty);
195 }
196
197 let witness_script_bytes = witness[witness.len() - 1].as_ref();
198 let script_hash = sha256::Hash::hash(witness_script_bytes);
199 let hash_bytes: &[u8] = script_hash.as_ref();
200 if hash_bytes != program {
201 return Err(ScriptError::WitnessProgramMismatch);
202 }
203
204 Ok(WitnessExecutionPlan::Execute {
205 sigversion: WitnessSigVersion::V0,
206 script_bytes: witness_script_bytes.to_vec(),
207 stack_items: witness_items(witness, witness.len() - 1),
208 sigops: WitnessSigops::CountExecutedScript,
209 })
210}
211
212fn build_witness_v1_512_plan(
213 program: &[u8],
214 witness: &Witness,
215) -> Result<WitnessExecutionPlan, ScriptError> {
216 if witness.is_empty() {
217 return Err(ScriptError::WitnessProgramWitnessEmpty);
218 }
219
220 let exec_script_bytes = witness[witness.len() - 1].as_ref();
221 let script_hash = sha512::Hash::hash(exec_script_bytes);
222 if script_hash.as_byte_array() != program {
223 return Err(ScriptError::WitnessProgramMismatch);
224 }
225
226 Ok(WitnessExecutionPlan::Execute {
227 sigversion: WitnessSigVersion::V1_512,
228 script_bytes: exec_script_bytes.to_vec(),
229 stack_items: witness_items(witness, witness.len() - 1),
230 sigops: WitnessSigops::CountExecutedScript,
231 })
232}
233
234fn witness_items(witness: &Witness, end: usize) -> Vec<Vec<u8>> {
235 witness.iter().take(end).map(|elem| elem.to_vec()).collect()
236}
237
238#[cfg(test)]
239mod tests {
240 use alloc::vec;
241
242 use super::{
243 WitnessExecutionPlan, WitnessProgram, WitnessProgramClass, WitnessSigVersion, WitnessSigops,
244 };
245 use crate::{ScriptError, VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM, VERIFY_WITNESS_V1_512};
246 use hashes::sha512;
247 use primitives::Witness;
248
249 #[test]
250 fn parses_witness_v0_program() {
251 let script = [
252 0x00, 0x20, 1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
253 0, 0, 0, 0, 0, 0, 0, 0,
254 ];
255 let program = WitnessProgram::parse(&script).expect("witness program");
256 assert_eq!(program.version(), 0);
257 assert_eq!(program.program().len(), 32);
258 }
259
260 #[test]
261 fn detects_tidecoin_witness_v1_512_program() {
262 let mut script = vec![0x51, 64];
263 script.extend_from_slice(&[7u8; 64]);
264 let program = WitnessProgram::parse(&script).expect("witness program");
265 assert_eq!(program.version(), 1);
266 assert_eq!(program.program(), &[7u8; 64]);
267 assert!(WitnessProgram::is_v1_512(&script));
268 }
269
270 #[test]
271 fn rejects_noncanonical_push_length() {
272 let script = [0x51, 0x4c, 0x40];
273 assert!(WitnessProgram::parse(&script).is_none());
274 }
275
276 #[test]
277 fn classifies_witness_v0_program_lengths() {
278 let p2wpkh = WitnessProgram::parse(&[
279 0x00, 20, 9u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
280 ])
281 .expect("p2wpkh");
282 assert_eq!(p2wpkh.classify(0).expect("classify"), WitnessProgramClass::P2wpkh);
283
284 let mut p2wsh_bytes = vec![0x00, 32];
285 p2wsh_bytes.extend_from_slice(&[5u8; 32]);
286 let p2wsh = WitnessProgram::parse(&p2wsh_bytes).expect("p2wsh");
287 assert_eq!(p2wsh.classify(0).expect("classify"), WitnessProgramClass::P2wsh);
288 }
289
290 #[test]
291 fn witness_v1_512_requires_feature_flag() {
292 let mut script = vec![0x51, 64];
293 script.extend_from_slice(&[1u8; 64]);
294 let witness_program = WitnessProgram::parse(&script).expect("witness v1");
295
296 assert_eq!(
297 witness_program.classify(0).expect("upgradable"),
298 WitnessProgramClass::Upgradable
299 );
300 assert_eq!(
301 witness_program.classify(VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM),
302 Err(ScriptError::DiscourageUpgradableWitnessProgram)
303 );
304 assert_eq!(
305 witness_program.classify(VERIFY_WITNESS_V1_512).expect("v1 enabled"),
306 WitnessProgramClass::WitnessV1_512
307 );
308 }
309
310 #[test]
311 fn builds_p2wpkh_execution_plan() {
312 let mut script = vec![0x00, 20];
313 script.extend_from_slice(&[3u8; 20]);
314 let witness_program = WitnessProgram::parse(&script).expect("p2wpkh");
315 let witness = Witness::from(vec![vec![1u8; 64], vec![2u8; 33]]);
316 let plan = witness_program.execution_plan(0, &witness).expect("plan");
317
318 match plan {
319 WitnessExecutionPlan::Execute { sigversion, script_bytes, stack_items, sigops } => {
320 assert_eq!(sigversion, WitnessSigVersion::V0);
321 assert_eq!(sigops, WitnessSigops::Fixed(1));
322 assert_eq!(stack_items.len(), 2);
323 assert!(!script_bytes.is_empty());
324 }
325 WitnessExecutionPlan::Upgradable => panic!("unexpected upgradable plan"),
326 }
327 }
328
329 #[test]
330 fn builds_witness_v1_512_execution_plan() {
331 let exec_script = vec![0x51];
332 let program_hash = sha512::Hash::hash(&exec_script);
333 let mut script = vec![0x51, 64];
334 script.extend_from_slice(program_hash.as_byte_array());
335 let witness_program = WitnessProgram::parse(&script).expect("witness v1");
336 let witness = Witness::from(vec![vec![1u8], exec_script.clone()]);
337 let plan = witness_program.execution_plan(VERIFY_WITNESS_V1_512, &witness).expect("plan");
338
339 match plan {
340 WitnessExecutionPlan::Execute { sigversion, script_bytes, stack_items, sigops } => {
341 assert_eq!(sigversion, WitnessSigVersion::V1_512);
342 assert_eq!(sigops, WitnessSigops::CountExecutedScript);
343 assert_eq!(script_bytes, exec_script);
344 assert_eq!(stack_items, vec![vec![1u8]]);
345 }
346 WitnessExecutionPlan::Upgradable => panic!("unexpected upgradable plan"),
347 }
348 }
349}