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::test_v4(
108 inputs,
109 outputs,
110 LockTime::min_lock_time_timestamp(),
111 Height(0),
112 )
113 }
114
115 fn prevout_input(unlock_script: transparent::Script) -> transparent::Input {
117 transparent::Input::PrevOut {
118 outpoint: transparent::OutPoint {
119 hash: transaction::Hash([0xaa; 32]),
120 index: 0,
121 },
122 unlock_script,
123 sequence: 0xffffffff,
124 }
125 }
126
127 fn output_with_script(lock_script: transparent::Script) -> transparent::Output {
130 transparent::Output {
131 value: 100_000u64.try_into().unwrap(),
132 lock_script,
133 }
134 }
135
136 #[test]
139 fn are_inputs_standard_accepts_valid_p2pkh() {
140 let _init_guard = zebra_test::init();
141
142 let script_sig = push_only_script_sig(2);
144 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
145 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
146
147 assert!(
148 are_inputs_standard(&tx, &spent_outputs),
149 "valid P2PKH input with correct stack depth should be standard"
150 );
151 }
152
153 #[test]
154 fn are_inputs_standard_rejects_wrong_stack_depth() {
155 let _init_guard = zebra_test::init();
156
157 let script_sig = push_only_script_sig(3);
159 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
160 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
161
162 assert!(
163 !are_inputs_standard(&tx, &spent_outputs),
164 "P2PKH input with 3 pushes instead of 2 should be non-standard"
165 );
166 }
167
168 #[test]
169 fn are_inputs_standard_rejects_too_few_pushes() {
170 let _init_guard = zebra_test::init();
171
172 let script_sig = push_only_script_sig(1);
174 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
175 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
176
177 assert!(
178 !are_inputs_standard(&tx, &spent_outputs),
179 "P2PKH input with 1 push instead of 2 should be non-standard"
180 );
181 }
182
183 #[test]
184 fn are_inputs_standard_rejects_non_standard_spent_output() {
185 let _init_guard = zebra_test::init();
186
187 let non_standard_lock = transparent::Script::new(&[0x51, 0x52, 0x93]);
189 let script_sig = push_only_script_sig(1);
190 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
191 let spent_outputs = vec![output_with_script(non_standard_lock)];
192
193 assert!(
194 !are_inputs_standard(&tx, &spent_outputs),
195 "input spending a non-standard script should be non-standard"
196 );
197 }
198
199 #[test]
200 fn are_inputs_standard_accepts_p2sh_with_standard_redeemed_script() {
201 let _init_guard = zebra_test::init();
202
203 let redeemed_script_bytes = {
207 let mut s = vec![0x76, 0xa9, 0x14];
208 s.extend_from_slice(&[0xcc; 20]);
209 s.push(0x88);
210 s.push(0xac);
211 s
212 };
213
214 let script_sig = p2sh_script_sig(&[&[0xaa], &[0xbb], &redeemed_script_bytes]);
221
222 let lock_script = p2sh_lock_script(&[0xdd; 20]);
226 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
227 let spent_outputs = vec![output_with_script(lock_script)];
228
229 assert!(
230 are_inputs_standard(&tx, &spent_outputs),
231 "P2SH input with standard P2PKH redeemed script and correct stack depth should be standard"
232 );
233 }
234
235 #[test]
236 fn are_inputs_standard_rejects_p2sh_with_too_many_sigops() {
237 let _init_guard = zebra_test::init();
238
239 let redeemed_script_bytes: Vec<u8> = vec![0xac; 16];
242
243 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
247
248 let lock_script = p2sh_lock_script(&[0xdd; 20]);
249 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
250 let spent_outputs = vec![output_with_script(lock_script)];
251
252 assert!(
253 !are_inputs_standard(&tx, &spent_outputs),
254 "P2SH input with redeemed script exceeding MAX_P2SH_SIGOPS should be non-standard"
255 );
256 }
257
258 #[test]
259 fn are_inputs_standard_accepts_p2sh_with_non_standard_low_sigops() {
260 let _init_guard = zebra_test::init();
261
262 let redeemed_script_bytes: Vec<u8> = vec![0xac; 15];
265
266 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
267
268 let lock_script = p2sh_lock_script(&[0xdd; 20]);
269 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
270 let spent_outputs = vec![output_with_script(lock_script)];
271
272 assert!(
273 are_inputs_standard(&tx, &spent_outputs),
274 "P2SH input with non-standard redeemed script at exactly MAX_P2SH_SIGOPS should be accepted"
275 );
276 }
277
278 #[test]
281 fn p2sh_sigop_count_returns_sigops_for_p2sh_input() {
282 let _init_guard = zebra_test::init();
283
284 let redeemed_script_bytes: Vec<u8> = vec![0xac; 5];
286
287 let script_sig = p2sh_script_sig(&[&redeemed_script_bytes]);
288
289 let lock_script = p2sh_lock_script(&[0xdd; 20]);
290 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
291 let spent_outputs = vec![output_with_script(lock_script)];
292
293 let count = p2sh_sigop_count(&tx, &spent_outputs);
294 assert_eq!(
295 count, 5,
296 "p2sh_sigop_count should return 5 for a redeemed script with 5 OP_CHECKSIG"
297 );
298 }
299
300 #[test]
301 fn p2sh_sigop_count_returns_zero_for_non_p2sh() {
302 let _init_guard = zebra_test::init();
303
304 let script_sig = push_only_script_sig(2);
306 let tx = make_v4_tx(vec![prevout_input(script_sig)], vec![]);
307 let spent_outputs = vec![output_with_script(p2pkh_lock_script(&[0xaa; 20]))];
308
309 let count = p2sh_sigop_count(&tx, &spent_outputs);
310 assert_eq!(
311 count, 0,
312 "p2sh_sigop_count should return 0 for non-P2SH inputs"
313 );
314 }
315
316 #[test]
317 fn p2sh_sigop_count_sums_across_multiple_inputs() {
318 let _init_guard = zebra_test::init();
319
320 let redeemed_1: Vec<u8> = vec![0xac; 3];
322 let script_sig_1 = p2sh_script_sig(&[&redeemed_1]);
323 let lock_1 = p2sh_lock_script(&[0xdd; 20]);
324
325 let script_sig_2 = push_only_script_sig(2);
327 let lock_2 = p2pkh_lock_script(&[0xaa; 20]);
328
329 let redeemed_3: Vec<u8> = vec![0xac; 7];
331 let script_sig_3 = p2sh_script_sig(&[&redeemed_3]);
332 let lock_3 = p2sh_lock_script(&[0xee; 20]);
333
334 let tx = make_v4_tx(
335 vec![
336 prevout_input(script_sig_1),
337 prevout_input(script_sig_2),
338 prevout_input(script_sig_3),
339 ],
340 vec![],
341 );
342 let spent_outputs = vec![
343 output_with_script(lock_1),
344 output_with_script(lock_2),
345 output_with_script(lock_3),
346 ];
347
348 let count = p2sh_sigop_count(&tx, &spent_outputs);
349 assert_eq!(
350 count, 10,
351 "p2sh_sigop_count should sum sigops across all P2SH inputs (3 + 0 + 7)"
352 );
353 }
354
355 #[test]
356 fn are_inputs_standard_rejects_second_non_standard_input() {
357 let _init_guard = zebra_test::init();
358
359 let script_sig_ok = push_only_script_sig(2);
361 let lock_ok = p2pkh_lock_script(&[0xaa; 20]);
362
363 let script_sig_bad = push_only_script_sig(3);
365 let lock_bad = p2pkh_lock_script(&[0xbb; 20]);
366
367 let tx = make_v4_tx(
368 vec![prevout_input(script_sig_ok), prevout_input(script_sig_bad)],
369 vec![],
370 );
371 let spent_outputs = vec![output_with_script(lock_ok), output_with_script(lock_bad)];
372
373 assert!(
374 !are_inputs_standard(&tx, &spent_outputs),
375 "should reject when second input is non-standard even if first is valid"
376 );
377 }
378}