feature_dersig.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2015-present 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 """Test BIP66 (DER SIG). 6 7 Test the DERSIG soft-fork activation on regtest. 8 """ 9 10 from test_framework.blocktools import ( 11 create_block, 12 create_coinbase, 13 ) 14 from test_framework.messages import msg_block 15 from test_framework.p2p import P2PInterface 16 from test_framework.script import CScript 17 from test_framework.test_framework import BitcoinTestFramework 18 from test_framework.util import ( 19 assert_equal, 20 ) 21 from test_framework.wallet import ( 22 MiniWallet, 23 MiniWalletMode, 24 ) 25 26 27 # A canonical signature consists of: 28 # <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype> 29 def unDERify(tx): 30 """ 31 Make the signature in vin 0 of a tx non-DER-compliant, 32 by adding padding after the S-value. 33 """ 34 scriptSig = CScript(tx.vin[0].scriptSig) 35 newscript = [] 36 for i in scriptSig: 37 if (len(newscript) == 0): 38 newscript.append(i[0:-1] + b'\0' + i[-1:]) 39 else: 40 newscript.append(i) 41 tx.vin[0].scriptSig = CScript(newscript) 42 43 44 DERSIG_HEIGHT = 102 45 46 47 class BIP66Test(BitcoinTestFramework): 48 def set_test_params(self): 49 self.num_nodes = 1 50 # whitelist peers to speed up tx relay / mempool sync 51 self.noban_tx_relay = True 52 self.extra_args = [[ 53 f'-testactivationheight=dersig@{DERSIG_HEIGHT}', 54 ]] 55 self.setup_clean_chain = True 56 self.rpc_timeout = 240 57 58 def create_tx(self, input_txid): 59 utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid, mark_as_spent=False) 60 return self.miniwallet.create_self_transfer(utxo_to_spend=utxo_to_spend)['tx'] 61 62 def test_dersig_info(self, *, is_active): 63 assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['bip66'], 64 { 65 "active": is_active, 66 "height": DERSIG_HEIGHT, 67 "type": "buried", 68 }, 69 ) 70 71 def run_test(self): 72 peer = self.nodes[0].add_p2p_connection(P2PInterface()) 73 self.miniwallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK) 74 75 self.test_dersig_info(is_active=False) 76 77 self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2) 78 self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.generate(self.miniwallet, DERSIG_HEIGHT - 2)] 79 80 self.log.info("Test that a transaction with non-DER signature can still appear in a block") 81 82 spendtx = self.create_tx(self.coinbase_txids[0]) 83 unDERify(spendtx) 84 85 tip = self.nodes[0].getbestblockhash() 86 block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 87 block = create_block(int(tip, 16), create_coinbase(DERSIG_HEIGHT - 1), block_time, txlist=[spendtx]) 88 block.solve() 89 90 assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 2) 91 self.test_dersig_info(is_active=False) # Not active as of current tip and next block does not need to obey rules 92 peer.send_and_ping(msg_block(block)) 93 assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 1) 94 self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules 95 assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex) 96 97 self.log.info("Test that blocks must now be at least version 3") 98 tip = block.hash_int 99 block_time += 1 100 block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time, version=2) 101 block.solve() 102 103 with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash_hex}, bad-version(0x00000002)']): 104 peer.send_and_ping(msg_block(block)) 105 assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) 106 peer.sync_with_ping() 107 108 self.log.info("Test that transactions with non-DER signatures cannot appear in a block") 109 block.nVersion = 4 110 111 coin_txid = self.coinbase_txids[1] 112 spendtx = self.create_tx(coin_txid) 113 unDERify(spendtx) 114 115 # First we show that this tx is valid except for DERSIG by getting it 116 # rejected from the mempool for exactly that reason. 117 spendtx_txid = spendtx.txid_hex 118 spendtx_wtxid = spendtx.wtxid_hex 119 assert_equal( 120 [{ 121 'txid': spendtx_txid, 122 'wtxid': spendtx_wtxid, 123 'allowed': False, 124 'reject-reason': 'mempool-script-verify-flag-failed (Non-canonical DER signature)', 125 'reject-details': 'mempool-script-verify-flag-failed (Non-canonical DER signature), ' + 126 f"input 0 of {spendtx_txid} (wtxid {spendtx_wtxid}), spending {coin_txid}:0" 127 }], 128 self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0), 129 ) 130 131 # Now we verify that a block with this transaction is also invalid. 132 block.vtx.append(spendtx) 133 block.hashMerkleRoot = block.calc_merkle_root() 134 block.solve() 135 136 with self.nodes[0].assert_debug_log(expected_msgs=['Block validation error: block-script-verify-flag-failed (Non-canonical DER signature)']): 137 peer.send_and_ping(msg_block(block)) 138 assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) 139 peer.sync_with_ping() 140 141 self.log.info("Test that a block with a DERSIG-compliant transaction is accepted") 142 block.vtx[1] = self.create_tx(self.coinbase_txids[1]) 143 block.hashMerkleRoot = block.calc_merkle_root() 144 block.solve() 145 146 self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules 147 peer.send_and_ping(msg_block(block)) 148 self.test_dersig_info(is_active=True) # Active as of current tip 149 assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex) 150 151 152 if __name__ == '__main__': 153 BIP66Test(__file__).main()