invalid_txs.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2015-2022 The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """ 6 Templates for constructing various sorts of invalid transactions. 7 8 These templates (or an iterator over all of them) can be reused in different 9 contexts to test using a number of invalid transaction types. 10 11 Hopefully this makes it easier to get coverage of a full variety of tx 12 validation checks through different interfaces (AcceptBlock, AcceptToMemPool, 13 etc.) without repeating ourselves. 14 15 Invalid tx cases not covered here can be found by running: 16 17 $ diff \ 18 <(grep -IREho "bad-txns[a-zA-Z-]+" src | sort -u) \ 19 <(grep -IEho "bad-txns[a-zA-Z-]+" test/functional/data/invalid_txs.py | sort -u) 20 21 """ 22 import abc 23 24 from typing import Optional 25 from test_framework.messages import ( 26 COutPoint, 27 CTransaction, 28 CTxIn, 29 CTxOut, 30 MAX_MONEY, 31 SEQUENCE_FINAL, 32 ) 33 from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS 34 from test_framework.script import ( 35 CScript, 36 OP_0, 37 OP_2DIV, 38 OP_2MUL, 39 OP_AND, 40 OP_CAT, 41 OP_CHECKSIG, 42 OP_DIV, 43 OP_INVERT, 44 OP_LEFT, 45 OP_LSHIFT, 46 OP_MOD, 47 OP_MUL, 48 OP_OR, 49 OP_RETURN, 50 OP_RIGHT, 51 OP_RSHIFT, 52 OP_SUBSTR, 53 OP_XOR, 54 ) 55 from test_framework.script_util import ( 56 MIN_PADDING, 57 MIN_STANDARD_TX_NONWITNESS_SIZE, 58 script_to_p2sh_script, 59 ) 60 basic_p2sh = script_to_p2sh_script(CScript([OP_0])) 61 62 class BadTxTemplate: 63 """Allows simple construction of a certain kind of invalid tx. Base class to be subclassed.""" 64 __metaclass__ = abc.ABCMeta 65 66 # The expected error code given by bitcoind upon submission of the tx. 67 reject_reason: Optional[str] = "" 68 69 # Only specified if it differs from mempool acceptance error. 70 block_reject_reason = "" 71 72 # Do we expect to be disconnected after submitting this tx? 73 expect_disconnect = False 74 75 # Is this tx considered valid when included in a block, but not for acceptance into 76 # the mempool (i.e. does it violate policy but not consensus)? 77 valid_in_block = False 78 79 def __init__(self, *, spend_tx=None, spend_block=None): 80 self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx 81 self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) 82 self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", SEQUENCE_FINAL) 83 84 @abc.abstractmethod 85 def get_tx(self, *args, **kwargs): 86 """Return a CTransaction that is invalid per the subclass.""" 87 pass 88 89 90 class OutputMissing(BadTxTemplate): 91 reject_reason = "bad-txns-vout-empty" 92 expect_disconnect = True 93 94 def get_tx(self): 95 tx = CTransaction() 96 tx.vin.append(self.valid_txin) 97 tx.calc_sha256() 98 return tx 99 100 101 class InputMissing(BadTxTemplate): 102 reject_reason = "bad-txns-vin-empty" 103 expect_disconnect = True 104 105 # We use a blank transaction here to make sure 106 # it is interpreted as a non-witness transaction. 107 # Otherwise the transaction will fail the 108 # "surpufluous witness" check during deserialization 109 # rather than the input count check. 110 def get_tx(self): 111 tx = CTransaction() 112 tx.calc_sha256() 113 return tx 114 115 116 # The following check prevents exploit of lack of merkle 117 # tree depth commitment (CVE-2017-12842) 118 class SizeTooSmall(BadTxTemplate): 119 reject_reason = "tx-size-small" 120 expect_disconnect = False 121 valid_in_block = True 122 123 def get_tx(self): 124 tx = CTransaction() 125 tx.vin.append(self.valid_txin) 126 tx.vout.append(CTxOut(0, CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 2))))) 127 assert len(tx.serialize_without_witness()) == 64 128 assert MIN_STANDARD_TX_NONWITNESS_SIZE - 1 == 64 129 tx.calc_sha256() 130 return tx 131 132 133 class BadInputOutpointIndex(BadTxTemplate): 134 # Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins 135 # database can't distinguish between spent outpoints and outpoints which never existed. 136 reject_reason = None 137 expect_disconnect = False 138 139 def get_tx(self): 140 num_indices = len(self.spend_tx.vin) 141 bad_idx = num_indices + 100 142 143 tx = CTransaction() 144 tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", SEQUENCE_FINAL)) 145 tx.vout.append(CTxOut(0, basic_p2sh)) 146 tx.calc_sha256() 147 return tx 148 149 150 class DuplicateInput(BadTxTemplate): 151 reject_reason = 'bad-txns-inputs-duplicate' 152 expect_disconnect = True 153 154 def get_tx(self): 155 tx = CTransaction() 156 tx.vin.append(self.valid_txin) 157 tx.vin.append(self.valid_txin) 158 tx.vout.append(CTxOut(1, basic_p2sh)) 159 tx.calc_sha256() 160 return tx 161 162 163 class PrevoutNullInput(BadTxTemplate): 164 reject_reason = 'bad-txns-prevout-null' 165 expect_disconnect = True 166 167 def get_tx(self): 168 tx = CTransaction() 169 tx.vin.append(self.valid_txin) 170 tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff))) 171 tx.vout.append(CTxOut(1, basic_p2sh)) 172 tx.calc_sha256() 173 return tx 174 175 176 class NonexistentInput(BadTxTemplate): 177 reject_reason = None # Added as an orphan tx. 178 expect_disconnect = False 179 180 def get_tx(self): 181 tx = CTransaction() 182 tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", SEQUENCE_FINAL)) 183 tx.vin.append(self.valid_txin) 184 tx.vout.append(CTxOut(1, basic_p2sh)) 185 tx.calc_sha256() 186 return tx 187 188 189 class SpendTooMuch(BadTxTemplate): 190 reject_reason = 'bad-txns-in-belowout' 191 expect_disconnect = True 192 193 def get_tx(self): 194 return create_tx_with_script( 195 self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1)) 196 197 198 class CreateNegative(BadTxTemplate): 199 reject_reason = 'bad-txns-vout-negative' 200 expect_disconnect = True 201 202 def get_tx(self): 203 return create_tx_with_script(self.spend_tx, 0, amount=-1) 204 205 206 class CreateTooLarge(BadTxTemplate): 207 reject_reason = 'bad-txns-vout-toolarge' 208 expect_disconnect = True 209 210 def get_tx(self): 211 return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1) 212 213 214 class CreateSumTooLarge(BadTxTemplate): 215 reject_reason = 'bad-txns-txouttotal-toolarge' 216 expect_disconnect = True 217 218 def get_tx(self): 219 tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY) 220 tx.vout = [tx.vout[0]] * 2 221 tx.calc_sha256() 222 return tx 223 224 225 class InvalidOPIFConstruction(BadTxTemplate): 226 reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)" 227 expect_disconnect = True 228 valid_in_block = True 229 230 def get_tx(self): 231 return create_tx_with_script( 232 self.spend_tx, 0, script_sig=b'\x64' * 35, 233 amount=(self.spend_avail // 2)) 234 235 236 class TooManySigops(BadTxTemplate): 237 reject_reason = "bad-txns-too-many-sigops" 238 block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount" 239 expect_disconnect = False 240 241 def get_tx(self): 242 lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) 243 return create_tx_with_script( 244 self.spend_tx, 0, 245 script_pub_key=lotsa_checksigs, 246 amount=1) 247 248 def getDisabledOpcodeTemplate(opcode): 249 """ Creates disabled opcode tx template class""" 250 def get_tx(self): 251 tx = CTransaction() 252 vin = self.valid_txin 253 vin.scriptSig = CScript([opcode]) 254 tx.vin.append(vin) 255 tx.vout.append(CTxOut(1, basic_p2sh)) 256 tx.calc_sha256() 257 return tx 258 259 return type('DisabledOpcode_' + str(opcode), (BadTxTemplate,), { 260 'reject_reason': "disabled opcode", 261 'expect_disconnect': True, 262 'get_tx': get_tx, 263 'valid_in_block' : True 264 }) 265 266 # Disabled opcode tx templates (CVE-2010-5137) 267 DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [ 268 OP_CAT, 269 OP_SUBSTR, 270 OP_LEFT, 271 OP_RIGHT, 272 OP_INVERT, 273 OP_AND, 274 OP_OR, 275 OP_XOR, 276 OP_2MUL, 277 OP_2DIV, 278 OP_MUL, 279 OP_DIV, 280 OP_MOD, 281 OP_LSHIFT, 282 OP_RSHIFT]] 283 284 285 def iter_all_templates(): 286 """Iterate through all bad transaction template types.""" 287 return BadTxTemplate.__subclasses__()