zebrad/components/mempool/storage/
policy.rs1use zcash_script::opcode::PossiblyBad;
8use zcash_script::script::Evaluable as _;
9use zcash_script::{script, solver, Opcode};
10use zebra_chain::{transaction::Transaction, transparent};
11
12pub(super) const MAX_P2SH_SIGOPS: u32 = 15;
15
16pub(super) const MAX_STANDARD_TX_SIGOPS: u32 = 4000;
19
20pub(super) const MAX_STANDARD_SCRIPTSIG_SIZE: usize = 1650;
23
24pub(super) const MAX_STANDARD_MULTISIG_PUBKEYS: usize = 3;
27
28pub(super) fn standard_script_kind(
32 lock_script: &transparent::Script,
33) -> Option<solver::ScriptKind> {
34 let code = script::Code(lock_script.as_raw_bytes().to_vec());
35 let component = code.to_component().ok()?.refine().ok()?;
36 solver::standard(&component)
37}
38
39fn extract_p2sh_redeemed_script(unlock_script: &transparent::Script) -> Option<Vec<u8>> {
44 let code = script::Code(unlock_script.as_raw_bytes().to_vec());
45 let mut last_push_data: Option<Vec<u8>> = None;
46 for opcode in code.parse() {
47 if let Ok(PossiblyBad::Good(Opcode::PushValue(pv))) = opcode {
48 last_push_data = Some(pv.value());
49 }
50 }
51 last_push_data
52}
53
54fn count_script_push_ops(script_bytes: &[u8]) -> usize {
59 let code = script::Code(script_bytes.to_vec());
60 code.parse()
61 .filter(|op| matches!(op, Ok(PossiblyBad::Good(Opcode::PushValue(_)))))
62 .count()
63}
64
65fn script_sig_args_expected(kind: &solver::ScriptKind) -> Option<usize> {
74 match kind {
75 solver::ScriptKind::PubKey { .. } => Some(1),
76 solver::ScriptKind::PubKeyHash { .. } => Some(2),
77 solver::ScriptKind::ScriptHash { .. } => Some(1),
78 solver::ScriptKind::MultiSig { required, .. } => Some(*required as usize + 1),
79 solver::ScriptKind::NullData { .. } => None,
80 }
81}
82
83fn p2sh_redeemed_script_sigop_count(
88 input: &transparent::Input,
89 spent_output: &transparent::Output,
90) -> Option<u32> {
91 let unlock_script = match input {
92 transparent::Input::PrevOut { unlock_script, .. } => unlock_script,
93 transparent::Input::Coinbase { .. } => return None,
94 };
95
96 let lock_code = script::Code(spent_output.lock_script.as_raw_bytes().to_vec());
97 if !lock_code.is_pay_to_script_hash() {
98 return None;
99 }
100
101 let redeemed_bytes = extract_p2sh_redeemed_script(unlock_script)?;
102 let redeemed = script::Code(redeemed_bytes);
103 Some(redeemed.sig_op_count(true))
104}
105
106pub(super) fn p2sh_sigop_count(tx: &Transaction, spent_outputs: &[transparent::Output]) -> u32 {
115 tx.inputs()
116 .iter()
117 .zip(spent_outputs.iter())
118 .filter_map(|(input, spent_output)| p2sh_redeemed_script_sigop_count(input, spent_output))
119 .sum()
120}
121
122pub(super) fn are_inputs_standard(tx: &Transaction, spent_outputs: &[transparent::Output]) -> bool {
139 for (input, spent_output) in tx.inputs().iter().zip(spent_outputs.iter()) {
140 let unlock_script = match input {
141 transparent::Input::PrevOut { unlock_script, .. } => unlock_script,
142 transparent::Input::Coinbase { .. } => continue,
143 };
144
145 let script_kind = match standard_script_kind(&spent_output.lock_script) {
147 Some(kind) => kind,
148 None => return false,
149 };
150
151 let mut n_args_expected = match script_sig_args_expected(&script_kind) {
154 Some(n) => n,
155 None => return false,
156 };
157
158 let stack_size = count_script_push_ops(unlock_script.as_raw_bytes());
161
162 if matches!(script_kind, solver::ScriptKind::ScriptHash { .. }) {
164 let Some(redeemed_bytes) = extract_p2sh_redeemed_script(unlock_script) else {
165 return false;
166 };
167
168 let redeemed_code = script::Code(redeemed_bytes);
169
170 let redeemed_kind = {
172 let component = redeemed_code
173 .to_component()
174 .ok()
175 .and_then(|c| c.refine().ok());
176 component.and_then(|c| solver::standard(&c))
177 };
178
179 match redeemed_kind {
180 Some(ref inner_kind) => {
181 match script_sig_args_expected(inner_kind) {
183 Some(inner) => n_args_expected += inner,
184 None => return false,
185 }
186 }
187 None => {
188 let sigops = redeemed_code.sig_op_count(true);
192 if sigops > MAX_P2SH_SIGOPS {
193 return false;
194 }
195
196 continue;
198 }
199 }
200 }
201
202 if stack_size != n_args_expected {
204 return false;
205 }
206 }
207 true
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn count_script_push_ops_counts_pushes() {
216 let _init_guard = zebra_test::init();
217 let script_bytes = vec![0x00, 0x01, 0xaa, 0x01, 0xbb];
219 let count = count_script_push_ops(&script_bytes);
220 assert_eq!(count, 3, "should count 3 push operations");
221 }
222
223 #[test]
224 fn count_script_push_ops_empty_script() {
225 let _init_guard = zebra_test::init();
226 let count = count_script_push_ops(&[]);
227 assert_eq!(count, 0, "empty script should have 0 push ops");
228 }
229
230 #[test]
231 fn extract_p2sh_redeemed_script_extracts_last_push() {
232 let _init_guard = zebra_test::init();
233 let unlock_script = transparent::Script::new(&[0x03, 0x61, 0x62, 0x63, 0x02, 0x64, 0x65]);
236 let redeemed = extract_p2sh_redeemed_script(&unlock_script);
237 assert_eq!(
238 redeemed,
239 Some(vec![0x64, 0x65]),
240 "should extract the last push data"
241 );
242 }
243
244 #[test]
245 fn extract_p2sh_redeemed_script_empty_script() {
246 let _init_guard = zebra_test::init();
247 let unlock_script = transparent::Script::new(&[]);
248 let redeemed = extract_p2sh_redeemed_script(&unlock_script);
249 assert!(redeemed.is_none(), "empty scriptSig should return None");
250 }
251
252 #[test]
253 fn script_sig_args_expected_values() {
254 let _init_guard = zebra_test::init();
255
256 let pkh_kind = solver::ScriptKind::PubKeyHash { hash: [0xaa; 20] };
258 assert_eq!(script_sig_args_expected(&pkh_kind), Some(2));
259
260 let sh_kind = solver::ScriptKind::ScriptHash { hash: [0xbb; 20] };
262 assert_eq!(script_sig_args_expected(&sh_kind), Some(1));
263
264 let nd_kind = solver::ScriptKind::NullData { data: vec![] };
266 assert_eq!(script_sig_args_expected(&nd_kind), None);
267 }
268}