feature_dersig.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2015-2022 The Limenka 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 LimenkaTestFramework
  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(LimenkaTestFramework):
  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          spendtx.rehash()
  85  
  86          tip = self.nodes[0].getbestblockhash()
  87          block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1
  88          block = create_block(int(tip, 16), create_coinbase(DERSIG_HEIGHT - 1), block_time, txlist=[spendtx])
  89          block.solve()
  90  
  91          assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 2)
  92          self.test_dersig_info(is_active=False)  # Not active as of current tip and next block does not need to obey rules
  93          peer.send_and_ping(msg_block(block))
  94          assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 1)
  95          self.test_dersig_info(is_active=True)  # Not active as of current tip, but next block must obey rules
  96          assert_equal(self.nodes[0].getbestblockhash(), block.hash)
  97  
  98          self.log.info("Test that blocks must now be at least version 3")
  99          tip = block.sha256
 100          block_time += 1
 101          block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time, version=2)
 102          block.solve()
 103  
 104          with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000002)']):
 105              peer.send_and_ping(msg_block(block))
 106              assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
 107              peer.sync_with_ping()
 108  
 109          self.log.info("Test that transactions with non-DER signatures cannot appear in a block")
 110          block.nVersion = 4
 111  
 112          coin_txid = self.coinbase_txids[1]
 113          spendtx = self.create_tx(coin_txid)
 114          unDERify(spendtx)
 115          spendtx.rehash()
 116  
 117          # First we show that this tx is valid except for DERSIG by getting it
 118          # rejected from the mempool for exactly that reason.
 119          spendtx_txid = spendtx.hash
 120          spendtx_wtxid = spendtx.getwtxid()
 121          expected = {
 122                  'txid': spendtx_txid,
 123                  'wtxid': spendtx_wtxid,
 124                  'allowed': False,
 125                  'reject-reason': 'mempool-script-verify-flag-failed (Non-canonical DER signature)',
 126                  'reject-details': 'mempool-script-verify-flag-failed (Non-canonical DER signature), ' +
 127                                    f"input 0 of {spendtx_txid} (wtxid {spendtx_wtxid}), spending {coin_txid}:0",
 128          }
 129          result = self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0)[0]
 130          # skip for now
 131          result.pop('usage')
 132          assert_equal(result, expected)
 133  
 134          # Now we verify that a block with this transaction is also invalid.
 135          block.vtx.append(spendtx)
 136          block.hashMerkleRoot = block.calc_merkle_root()
 137          block.solve()
 138  
 139          with self.nodes[0].assert_debug_log(expected_msgs=['Block validation error: mandatory-script-verify-flag-failed (Non-canonical DER signature)']):
 140              peer.send_and_ping(msg_block(block))
 141              assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
 142              peer.sync_with_ping()
 143  
 144          self.log.info("Test that a block with a DERSIG-compliant transaction is accepted")
 145          block.vtx[1] = self.create_tx(self.coinbase_txids[1])
 146          block.hashMerkleRoot = block.calc_merkle_root()
 147          block.solve()
 148  
 149          self.test_dersig_info(is_active=True)  # Not active as of current tip, but next block must obey rules
 150          peer.send_and_ping(msg_block(block))
 151          self.test_dersig_info(is_active=True)  # Active as of current tip
 152          assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
 153  
 154  
 155  if __name__ == '__main__':
 156      BIP66Test(__file__).main()
 157