zebrad/components/mempool/storage/
policy.rs1#[cfg(test)]
9use zebra_chain::transparent;
10
11pub(super) use zebra_consensus::transaction::check::{
16 are_inputs_standard, standard_script_kind, MAX_STANDARD_SCRIPTSIG_SIZE,
17};
18
19pub(super) const MAX_STANDARD_TX_SIGOPS: u32 = 4000;
22
23pub(super) const MAX_STANDARD_MULTISIG_PUBKEYS: usize = 3;
26
27#[cfg(test)]
28pub(super) use zebra_script::p2sh_sigop_count;
29
30#[cfg(test)]
34pub(super) fn p2pkh_lock_script(hash: &[u8; 20]) -> transparent::Script {
35 let mut s = vec![0x76, 0xa9, 0x14];
36 s.extend_from_slice(hash);
37 s.push(0x88);
38 s.push(0xac);
39 transparent::Script::new(&s)
40}
41
42#[cfg(test)]
44pub(super) fn p2sh_lock_script(hash: &[u8; 20]) -> transparent::Script {
45 let mut s = vec![0xa9, 0x14];
46 s.extend_from_slice(hash);
47 s.push(0x87);
48 transparent::Script::new(&s)
49}
50
51#[cfg(test)]
53pub(super) fn p2pk_lock_script(pubkey: &[u8; 33]) -> transparent::Script {
54 let mut s = Vec::with_capacity(1 + 33 + 1);
55 s.push(0x21); s.extend_from_slice(pubkey);
57 s.push(0xac); transparent::Script::new(&s)
59}
60
61#[cfg(test)]
62mod tests {
63 use zebra_chain::{
64 block::Height,
65 transaction::{self, LockTime, Transaction},
66 };
67
68 use super::*;
69
70 fn push_only_script_sig(n_pushes: usize) -> transparent::Script {
75 let mut bytes = Vec::with_capacity(n_pushes * 2);
76 for _ in 0..n_pushes {
77 bytes.push(0x01);
79 bytes.push(0x42);
80 }
81 transparent::Script::new(&bytes)
82 }
83
84 fn p2sh_script_sig(push_items: &[&[u8]]) -> transparent::Script {
88 let mut bytes = Vec::new();
89 for item in push_items {
90 assert!(
91 item.len() <= 75,
92 "p2sh_script_sig only supports OP_PUSHBYTES (max 75 bytes), got {}",
93 item.len()
94 );
95 bytes.push(item.len() as u8);
97 bytes.extend_from_slice(item);
98 }
99 transparent::Script::new(&bytes)
100 }
101
102 fn make_v4_tx(
104 inputs: Vec<transparent::Input>,
105 outputs: Vec<transparent::Output>,
106 ) -> Transaction {
107 Transaction::V4 {
108 inputs,
109 outputs,
110 lock_time: LockTime::min_lock_time_timestamp(),
111 expiry_height: Height(0),
112 joinsplit_data: None,
113 sapling_shielded_data: None,
114 }
115 }
116
117 fn prevout_input(unlock_script: transparent::Script) -> transparent::Input {
119 transparent::Input::PrevOut {
120 outpoint: transparent::OutPoint {
121 hash: transaction::Hash([0xaa; 32]),
122 index: 0,
123 },
124 unlock_script,
125 sequence: 0xffffffff,
126 }
127 }
128
129 fn output_with_script(lock_script: transparent::Script) -> transparent::Output {
132 transparent::Output {
133 value: 100_000u64.try_into().unwrap(),
134 lock_script,
135 }
136 }
137
138 #[test]
141 fn are_inputs_standard_accepts_valid_p2pkh() {
142 let _init_guard = zebra_test::init();
143
144 let script_sig = push_only_script_sig(2);
146 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
147 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
148
149 assert!(
150 are_inputs_standard(&tx, &spent_outputs),
151 "valid P2PKH input with correct stack depth should be standard"
152 );
153 }
154
155 #[test]
156 fn are_inputs_standard_rejects_wrong_stack_depth() {
157 let _init_guard = zebra_test::init();
158
159 let script_sig = push_only_script_sig(3);
161 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
162 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
163
164 assert!(
165 !are_inputs_standard(&tx, &spent_outputs),
166 "P2PKH input with 3 pushes instead of 2 should be non-standard"
167 );
168 }
169
170 #[test]
171 fn are_inputs_standard_rejects_too_few_pushes() {
172 let _init_guard = zebra_test::init();
173
174 let script_sig = push_only_script_sig(1);
176 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
177 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
178
179 assert!(
180 !are_inputs_standard(&tx, &spent_outputs),
181 "P2PKH input with 1 push instead of 2 should be non-standard"
182 );
183 }
184
185 #[test]
186 fn are_inputs_standard_rejects_non_standard_spent_output() {
187 let _init_guard = zebra_test::init();
188
189 let non_standard_lock = transparent::Script::new(&[0x51, 0x52, 0x93]);
191 let script_sig = push_only_script_sig(1);
192 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
193 let spent_outputs = vec![output_with_script(non_standard_lock)];
194
195 assert!(
196 !are_inputs_standard(&tx, &spent_outputs),
197 "input spending a non-standard script should be non-standard"
198 );
199 }
200
201 #[test]
202 fn are_inputs_standard_accepts_p2sh_with_standard_redeemed_script() {
203 let _init_guard = zebra_test::init();
204
205 let redeemed_script_bytes = {
209 let mut s = vec![0x76, 0xa9, 0x14];
210 s.extend_from_slice(&[0xcc; 20]);
211 s.push(0x88);
212 s.push(0xac);
213 s
214 };
215
216 let script_sig = p2sh_script_sig(&[&[0xaa], &[0xbb], &redeemed_script_bytes]);
223
224 let lock_script = p2sh_lock_script(&[0xdd; 20]);
228 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
229 let spent_outputs = vec![output_with_script(lock_script)];
230
231 assert!(
232 are_inputs_standard(&tx, &spent_outputs),
233 "P2SH input with standard P2PKH redeemed script and correct stack depth should be standard"
234 );
235 }
236
237 #[test]
238 fn are_inputs_standard_rejects_p2sh_with_too_many_sigops() {
239 let _init_guard = zebra_test::init();
240
241 let redeemed_script_bytes: Vec<u8> = vec![0xac; 16];
244
245 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
249
250 let lock_script = p2sh_lock_script(&[0xdd; 20]);
251 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
252 let spent_outputs = vec![output_with_script(lock_script)];
253
254 assert!(
255 !are_inputs_standard(&tx, &spent_outputs),
256 "P2SH input with redeemed script exceeding MAX_P2SH_SIGOPS should be non-standard"
257 );
258 }
259
260 #[test]
261 fn are_inputs_standard_accepts_p2sh_with_non_standard_low_sigops() {
262 let _init_guard = zebra_test::init();
263
264 let redeemed_script_bytes: Vec<u8> = vec![0xac; 15];
267
268 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
269
270 let lock_script = p2sh_lock_script(&[0xdd; 20]);
271 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
272 let spent_outputs = vec![output_with_script(lock_script)];
273
274 assert!(
275 are_inputs_standard(&tx, &spent_outputs),
276 "P2SH input with non-standard redeemed script at exactly MAX_P2SH_SIGOPS should be accepted"
277 );
278 }
279
280 #[test]
283 fn p2sh_sigop_count_returns_sigops_for_p2sh_input() {
284 let _init_guard = zebra_test::init();
285
286 let redeemed_script_bytes: Vec<u8> = vec![0xac; 5];
288
289 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
290
291 let lock_script = p2sh_lock_script(&[0xdd; 20]);
292 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
293 let spent_outputs = vec![output_with_script(lock_script)];
294
295 let count = p2sh_sigop_count(&tx, &spent_outputs);
296 assert_eq!(
297 count, 5,
298 "p2sh_sigop_count should return 5 for a redeemed script with 5 OP_CHECKSIG"
299 );
300 }
301
302 #[test]
303 fn p2sh_sigop_count_returns_zero_for_non_p2sh() {
304 let _init_guard = zebra_test::init();
305
306 let script_sig = push_only_script_sig(2);
308 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
309 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
310
311 let count = p2sh_sigop_count(&tx, &spent_outputs);
312 assert_eq!(
313 count, 0,
314 "p2sh_sigop_count should return 0 for non-P2SH inputs"
315 );
316 }
317
318 #[test]
319 fn p2sh_sigop_count_sums_across_multiple_inputs() {
320 let _init_guard = zebra_test::init();
321
322 let redeemed_1: Vec<u8> = vec![0xac; 3];
324 let script_sig_1 = p2sh_script_sig(&[&redeemed_1]);
325 let lock_1 = p2sh_lock_script(&[0xdd; 20]);
326
327 let script_sig_2 = push_only_script_sig(2);
329 let lock_2 = p2pkh_lock_script(&[0xaa; 20]);
330
331 let redeemed_3: Vec<u8> = vec![0xac; 7];
333 let script_sig_3 = p2sh_script_sig(&[&redeemed_3]);
334 let lock_3 = p2sh_lock_script(&[0xee; 20]);
335
336 let tx = make_v4_tx(
337 vec![
338 prevout_input(script_sig_1),
339 prevout_input(script_sig_2),
340 prevout_input(script_sig_3),
341 ],
342 vec![],
343 );
344 let spent_outputs = vec![
345 output_with_script(lock_1),
346 output_with_script(lock_2),
347 output_with_script(lock_3),
348 ];
349
350 let count = p2sh_sigop_count(&tx, &spent_outputs);
351 assert_eq!(
352 count, 10,
353 "p2sh_sigop_count should sum sigops across all P2SH inputs (3 + 0 + 7)"
354 );
355 }
356
357 #[test]
358 fn are_inputs_standard_rejects_second_non_standard_input() {
359 let _init_guard = zebra_test::init();
360
361 let script_sig_ok = push_only_script_sig(2);
363 let lock_ok = p2pkh_lock_script(&[0xaa; 20]);
364
365 let script_sig_bad = push_only_script_sig(3);
367 let lock_bad = p2pkh_lock_script(&[0xbb; 20]);
368
369 let tx = make_v4_tx(
370 vec![prevout_input(script_sig_ok), prevout_input(script_sig_bad)],
371 vec![],
372 );
373 let spent_outputs = vec![output_with_script(lock_ok), output_with_script(lock_bad)];
374
375 assert!(
376 !are_inputs_standard(&tx, &spent_outputs),
377 "should reject when second input is non-standard even if first is valid"
378 );
379 }
380}