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