feature_dersig.py raw

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