From 4b5c0b6e541773eb35ddfecb689f313eae6a68dd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 9 Mar 2017 13:36:20 -0600 Subject: [PATCH 01/82] FakeBitcoinProxy: a mock RPC implementation. Use FakeBitcoinProxy as an alternative to mocking bitcoin.rpc.Proxy in unit tests by downstream applications. FakeBitcoinProxy implements a similar interface and returns values similar to those from the bitcoin.rpc.Proxy API. Downstream applications could use this for unit testing their bitcoin applications, leaving bitcoind "regtest" for integration testing modes instead. Also, unit tests for FakeBitcoinProxy are provided. --- bitcoin/tests/fakebitcoinproxy.py | 327 +++++++++++++++++++ bitcoin/tests/test_fakebitcoinproxy.py | 420 +++++++++++++++++++++++++ 2 files changed, 747 insertions(+) create mode 100644 bitcoin/tests/fakebitcoinproxy.py create mode 100644 bitcoin/tests/test_fakebitcoinproxy.py diff --git a/bitcoin/tests/fakebitcoinproxy.py b/bitcoin/tests/fakebitcoinproxy.py new file mode 100644 index 00000000..ac111615 --- /dev/null +++ b/bitcoin/tests/fakebitcoinproxy.py @@ -0,0 +1,327 @@ +""" +`FakeBitcoinProxy` allows for unit testing of code that normally uses bitcoin +RPC without requiring a running bitcoin node. + +`FakeBitcoinProxy` has an interface similar to `bitcoin.rpc.Proxy`, but does not +connect to a local bitcoin RPC node. Hence, `FakeBitcoinProxy` is similar to a +mock for the RPC tests. + +`FakeBitcoinProxy` does _not_ implement a full bitcoin RPC node. Instead, it +currently implements only a subset of the available RPC commands. Test setup is +responsible for populating a `FakeBitcoinProxy` object with reasonable mock +data. + +:author: Bryan Bishop +""" + +import random +import hashlib + +from bitcoin.core import ( + # bytes to hex (see x) + b2x, + + # convert hex string to bytes (see b2x) + x, + + # convert little-endian hex string to bytes (see b2lx) + lx, + + # convert bytes to little-endian hex string (see lx) + b2lx, + + # number of satoshis per bitcoin + COIN, + + # a type for a transaction that isn't finished building + CMutableTransaction, + CMutableTxIn, + CMutableTxOut, + COutPoint, + CTxIn, +) + +from bitcoin.wallet import ( + # bitcoin address initialized from base58-encoded string + CBitcoinAddress, + + # base58-encoded secret key + CBitcoinSecret, + + # has a nifty function from_pubkey + P2PKHBitcoinAddress, +) + +def make_address_from_passphrase(passphrase, compressed=True, as_str=True): + """ + Create a Bitcoin address from a passphrase. The passphrase is hashed and + then used as the secret bytes to construct the CBitcoinSecret. + """ + if not isinstance(passphrase, bytes): + passphrase = bytes(passphrase, "utf-8") + passphrasehash = hashlib.sha256(passphrase).digest() + private_key = CBitcoinSecret.from_secret_bytes(passphrasehash, compressed=compressed) + address = P2PKHBitcoinAddress.from_pubkey(private_key.pub) + if as_str: + return str(address) + else: + return address + +def make_txout(amount=None, address=None, counter=None): + """ + Make a CTxOut object based on the parameters. Otherwise randomly generate a + CTxOut to represent a transaction output. + + :param amount: amount in satoshis + """ + passphrase_template = "correct horse battery staple txout {counter}" + + if not counter: + counter = random.randrange(0, 2**50) + + if not address: + passphrase = passphrase_template.format(counter=counter) + address = make_address_from_passphrase(bytes(passphrase, "utf-8")) + + if not amount: + maxsatoshis = (21 * 1000 * 1000) * (100 * 1000 * 1000) # 21 million BTC * 100 million satoshi per BTC + amount = random.randrange(0, maxsatoshis) # between 0 satoshi and 21 million BTC + + txout = CMutableTxOut(amount, CBitcoinAddress(address).to_scriptPubKey()) + + return txout + +def make_blocks_from_blockhashes(blockhashes): + """ + Create some block data suitable for FakeBitcoinProxy to consume during + instantiation. + """ + blocks = [] + + for (height, blockhash) in enumerate(blockhashes): + block = {"hash": blockhash, "height": height, "tx": []} + if height != 0: + block["previousblockhash"] = previousblockhash + blocks.append(block) + previousblockhash = blockhash + + return blocks + +def make_rpc_batch_request_entry(rpc_name, params): + """ + Construct an entry for the list of commands that will be passed as a batch + (for `_batch`). + """ + return { + "id": "50", + "version": "1.1", + "method": rpc_name, + "params": params, + } + +class FakeBitcoinProxyException(Exception): + """ + Incorrect usage of fake proxy. + """ + pass + +class FakeBitcoinProxy(object): + """ + This is an alternative to using `bitcoin.rpc.Proxy` in tests. This class + can store a number of blocks and transactions, which can then be retrieved + by calling various "RPC" methods. + """ + + def __init__(self, blocks=None, transactions=None, getnewaddress_offset=None, getnewaddress_passphrase_template="getnewaddress passphrase template {}", num_fundrawtransaction_inputs=5): + """ + :param getnewaddress_offset: a number to start using and incrementing + in template used by getnewaddress. + :type getnewaddress_offset: int + :param int num_fundrawtransaction_inputs: number of inputs to create + during fundrawtransaction. + """ + self.blocks = blocks or {} + self.transactions = transactions or {} + + if getnewaddress_offset == None: + self._getnewaddress_offset = 0 + else: + self._getnewaddress_offset = getnewaddress_offset + + self._getnewaddress_passphrase_template = getnewaddress_passphrase_template + self._num_fundrawtransaction_inputs = num_fundrawtransaction_inputs + self.populate_blocks_with_blockheights() + + def _call(self, rpc_method_name, *args, **kwargs): + """ + This represents a "raw" RPC call, which has output that + python-bitcoinlib does not parse. + """ + method = getattr(self, rpc_method_name) + return method(*args, **kwargs) + + def populate_blocks_with_blockheights(self): + """ + Helper method to correctly apply "height" on all blocks. + """ + for (height, block) in enumerate(self.blocks): + block["height"] = height + + def getblock(self, blockhash, *args, **kwargs): + """ + :param blockhash: hash of the block to retrieve data for + :raise IndexError: invalid blockhash + """ + + # Note that the actual "getblock" bitcoind RPC call from + # python-bitcoinlib returns a CBlock object, not a dictionary. + + if isinstance(blockhash, bytes): + blockhash = b2lx(blockhash) + + for block in self.blocks: + if block["hash"] == blockhash: + return block + + raise IndexError("no block found for blockhash {}".format(blockhash)) + + def getblockhash(self, blockheight): + """ + Get block by blockheight. + + :type blockheight: int + :rtype: dict + """ + for block in self.blocks: + if block["height"] == int(blockheight): + return block["hash"] + + def getblockcount(self): + """ + Return the total number of blocks. When there is only one block in the + blockchain, this function will return zero. + + :rtype: int + """ + return len(self.blocks) - 1 + + def getrawtransaction(self, txid, *args, **kwargs): + """ + Get parsed transaction. + + :type txid: bytes or str + :rtype: dict + """ + if isinstance(txid, bytes): + txid = b2lx(txid) + return self.transactions[txid] + + def getnewaddress(self): + """ + Construct a new address based on a passphrase template. As more + addresses are generated, the template value goes up. + """ + passphrase = self._getnewaddress_passphrase_template.format(self._getnewaddress_offset) + address = make_address_from_passphrase(bytes(passphrase, "utf-8")) + self._getnewaddress_offset += 1 + return CBitcoinAddress(address) + + def importaddress(self, *args, **kwargs): + """ + Completely unrealistic fake version of importaddress. + """ + return True + + # This was implemented a long time ago and it's possible that this does not + # match the current behavior of fundrawtransaction. + def fundrawtransaction(self, given_transaction, *args, **kwargs): + """ + Make up some inputs for the given transaction. + """ + # just use any txid here + vintxid = lx("99264749804159db1e342a0c8aa3279f6ef4031872051a1e52fb302e51061bef") + + if isinstance(given_transaction, str): + given_bytes = x(given_transaction) + elif isinstance(given_transaction, CMutableTransaction): + given_bytes = given_transaction.serialize() + else: + raise FakeBitcoinProxyException("Wrong type passed to fundrawtransaction.") + + # this is also a clever way to not cause a side-effect in this function + transaction = CMutableTransaction.deserialize(given_bytes) + + for vout_counter in range(0, self._num_fundrawtransaction_inputs): + txin = CMutableTxIn(COutPoint(vintxid, vout_counter)) + transaction.vin.append(txin) + + # also allocate a single output (for change) + txout = make_txout() + transaction.vout.append(txout) + + transaction_hex = b2x(transaction.serialize()) + + return {"hex": transaction_hex, "fee": 5000000} + + def signrawtransaction(self, given_transaction): + """ + This method does not actually sign the transaction, but it does return + a transaction based on the given transaction. + """ + if isinstance(given_transaction, str): + given_bytes = x(given_transaction) + elif isinstance(given_transaction, CMutableTransaction): + given_bytes = given_transaction.serialize() + else: + raise FakeBitcoinProxyException("Wrong type passed to signrawtransaction.") + + transaction = CMutableTransaction.deserialize(given_bytes) + transaction_hex = b2x(transaction.serialize()) + return {"hex": transaction_hex} + + def sendrawtransaction(self, given_transaction): + """ + Pretend to broadcast and relay the transaction. Return the txid of the + given transaction. + """ + if isinstance(given_transaction, str): + given_bytes = x(given_transaction) + elif isinstance(given_transaction, CMutableTransaction): + given_bytes = given_transaction.serialize() + else: + raise FakeBitcoinProxyException("Wrong type passed to sendrawtransaction.") + transaction = CMutableTransaction.deserialize(given_bytes) + return b2lx(transaction.GetHash()) + + def _batch(self, batch_request_entries): + """ + Process a bunch of requests all at once. This mimics the _batch RPC + feature found in python-bitcoinlib and bitcoind RPC. + """ + necessary_keys = ["id", "version", "method", "params"] + + results = [] + + for (idx, request) in enumerate(batch_request_entries): + error = None + result = None + + # assert presence of important details + for necessary_key in necessary_keys: + if not necessary_key in request.keys(): + raise FakeBitcoinProxyException("Missing necessary key {} for _batch request number {}".format(necessary_key, idx)) + + if isinstance(request["params"], list): + method = getattr(self, request["method"]) + result = method(*request["params"]) + else: + # matches error message received through python-bitcoinrpc + error = {"message": "Params must be an array", "code": -32600} + + results.append({ + "error": error, + "id": request["id"], + "result": result, + }) + + return results diff --git a/bitcoin/tests/test_fakebitcoinproxy.py b/bitcoin/tests/test_fakebitcoinproxy.py new file mode 100644 index 00000000..a38be6b0 --- /dev/null +++ b/bitcoin/tests/test_fakebitcoinproxy.py @@ -0,0 +1,420 @@ +""" +:author: Bryan Bishop +""" + +import unittest +import random +from copy import copy + +from bitcoin.core import ( + # bytes to hex (see x) + b2x, + + # convert hex string to bytes (see b2x) + x, + + # convert little-endian hex string to bytes (see b2lx) + lx, + + # convert bytes to little-endian hex string (see lx) + b2lx, + + # number of satoshis per bitcoin + COIN, + + # a type for a transaction that isn't finished building + CMutableTransaction, + CMutableTxIn, + CMutableTxOut, + COutPoint, + CTxIn, +) + +from bitcoin.wallet import ( + # has a nifty function from_pubkey + P2PKHBitcoinAddress, +) + +from fakebitcoinproxy import ( + FakeBitcoinProxy, + make_txout, + make_blocks_from_blockhashes, + make_rpc_batch_request_entry, + + # TODO: import and test with FakeBitcoinProxyException +) + +class FakeBitcoinProxyTestCase(unittest.TestCase): + def test_constructor(self): + FakeBitcoinProxy() + + def test_constructor_accepts_blocks(self): + blockhash0 = "blockhash0" + blockhash1 = "blockhash1" + + blocks = [ + {"hash": blockhash0}, + {"hash": blockhash1}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + + self.assertNotEqual(proxy.blocks, {}) + self.assertTrue(proxy.blocks is blocks) + + def test_getblock_with_string(self): + blockhash0 = "blockhash0" + blockhash1 = "blockhash1" + + blocks = [ + {"hash": blockhash0}, + {"hash": blockhash1}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + result = proxy.getblock(blockhash0) + + self.assertEqual(type(result), dict) + + # should have a height now + self.assertTrue("height" in result.keys()) + self.assertEqual(result["height"], 0) + + def test_blockheight_extensively(self): + blockhashes = ["blockhash{}".format(x) for x in range(0, 10)] + + blocks = [] + for blockhash in blockhashes: + blocks.append({"hash": blockhash}) + + proxy = FakeBitcoinProxy(blocks=blocks) + + for (expected_height, blockhash) in enumerate(blockhashes): + blockdata = proxy.getblock(blockhash) + self.assertEqual(blockdata["height"], expected_height) + + def test_getblock_with_bytes(self): + blockhash0 = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936301" + + blocks = [ + {"hash": blockhash0}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + result = proxy.getblock(lx(blockhash0)) + + self.assertEqual(type(result), dict) + + def test_getblock_returns_transaction_data(self): + blockhash0 = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936302" + + transaction_txids = ["foo", "bar"] + + blocks = [ + {"hash": blockhash0, "tx": transaction_txids}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + result = proxy.getblock(blockhash0) + + self.assertTrue("tx" in result.keys()) + self.assertEqual(type(result["tx"]), list) + self.assertEqual(result["tx"], transaction_txids) + + def test_getblockhash_zero(self): + blockhash0 = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936302" + + blocks = [ + {"hash": blockhash0}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + + blockhash_result = proxy.getblockhash(0) + + self.assertEqual(blockhash_result, blockhash0) + + def test_getblockhash_many(self): + blockhashes = [ + "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936302", + "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936303", + "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936304", + "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936305", + ] + + blocks = make_blocks_from_blockhashes(blockhashes) + proxy = FakeBitcoinProxy(blocks=blocks) + + for (height, expected_blockhash) in enumerate(blockhashes): + blockhash_result = proxy.getblockhash(height) + self.assertEqual(blockhash_result, expected_blockhash) + + def test_getblockcount_zero(self): + blocks = [] + proxy = FakeBitcoinProxy(blocks=blocks) + count = proxy.getblockcount() + self.assertEqual(count, len(blocks) - 1) + + def test_getblockcount_many(self): + blockhash0 = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea64936301" + blockhash1 = "0000b9fc70e9a8bb863a8c37f0f3d10d4d3e99e0e94bd42b35f0fdbb497399eb" + + blocks = [ + {"hash": blockhash0}, + {"hash": blockhash1, "previousblockhash": blockhash0}, + ] + + proxy = FakeBitcoinProxy(blocks=blocks) + count = proxy.getblockcount() + self.assertEqual(count, len(blocks) - 1) + + def test_getrawtransaction(self): + txids = [ + "b9e3b3366b31136bd262c64ff6e4c29918a457840c5f53cbeeeefa41ca3b7005", + "21886e5c02049751acea9d462359d68910bef938d9a58288c961d18b6e50daef", + "b2d0cc6840be86516ce79ed7249bfd09f95923cc27784c2fa0dd910bfdb03173", + "eacb81d11a539047af73b5d810d3683af3b1171e683dcbfcfb2819286d762c0c", + "eee72e928403fea7bca29f366483c3e0ab629ac5dce384a0f541aacf6f810d30", + ] + + txdefault = {"tx": "", "confirmations": 0} + + transactions = dict([(txid, copy(txdefault)) for txid in txids]) + + # just making sure... + self.assertTrue(transactions[txids[0]] is not transactions[txids[1]]) + + proxy = FakeBitcoinProxy(transactions=transactions) + + for txid in txids: + txdata = proxy.getrawtransaction(txid) + + self.assertEqual(type(txdata), dict) + self.assertTrue("tx" in txdata.keys()) + self.assertEqual(type(txdata["tx"]), str) + self.assertEqual(txdata["tx"], "") + self.assertTrue("confirmations" in txdata.keys()) + self.assertEqual(txdata["confirmations"], 0) + + def test_getnewaddress_returns_different_addresses(self): + num_addresses = random.randint(10, 100) + addresses = set() + + proxy = FakeBitcoinProxy() + + for each in range(num_addresses): + address = proxy.getnewaddress() + addresses.add(str(address)) + + self.assertEqual(len(addresses), num_addresses) + + def test_getnewaddress_returns_cbitcoinaddress(self): + proxy = FakeBitcoinProxy() + address = proxy.getnewaddress() + self.assertEqual(type(address), P2PKHBitcoinAddress) + + def test_importaddress(self): + proxy = FakeBitcoinProxy() + proxy.importaddress("foo") + + def test_importaddress_with_parameters(self): + proxy = FakeBitcoinProxy() + address = "some_address" + label = "" + rescan = False + proxy.importaddress(address, label, rescan) + + def test_fundrawtransaction(self): + unfunded_transaction = CMutableTransaction([], [make_txout() for x in range(0, 5)]) + proxy = FakeBitcoinProxy() + + funded_transaction_hex = proxy.fundrawtransaction(unfunded_transaction)["hex"] + funded_transaction = CMutableTransaction.deserialize(x(funded_transaction_hex)) + + self.assertTrue(unfunded_transaction is not funded_transaction) + self.assertEqual(type(funded_transaction), type(unfunded_transaction)) + self.assertNotEqual(len(funded_transaction.vin), 0) + self.assertTrue(len(funded_transaction.vin) > len(unfunded_transaction.vin)) + self.assertEqual(type(funded_transaction.vin[0]), CTxIn) + + def test_fundrawtransaction_hex_hash(self): + unfunded_transaction = CMutableTransaction([], [make_txout() for x in range(0, 5)]) + proxy = FakeBitcoinProxy() + + funded_transaction_hex = proxy.fundrawtransaction(b2x(unfunded_transaction.serialize()))["hex"] + funded_transaction = CMutableTransaction.deserialize(x(funded_transaction_hex)) + + self.assertTrue(unfunded_transaction is not funded_transaction) + self.assertEqual(type(funded_transaction), type(unfunded_transaction)) + self.assertNotEqual(len(funded_transaction.vin), 0) + self.assertTrue(len(funded_transaction.vin) > len(unfunded_transaction.vin)) + self.assertEqual(type(funded_transaction.vin[0]), CTxIn) + + def test_fundrawtransaction_adds_output(self): + num_outputs = 5 + unfunded_transaction = CMutableTransaction([], [make_txout() for x in range(0, num_outputs)]) + proxy = FakeBitcoinProxy() + + funded_transaction_hex = proxy.fundrawtransaction(b2x(unfunded_transaction.serialize()))["hex"] + funded_transaction = CMutableTransaction.deserialize(x(funded_transaction_hex)) + + self.assertTrue(len(funded_transaction.vout) > num_outputs) + self.assertEqual(len(funded_transaction.vout), num_outputs + 1) + + def test_signrawtransaction(self): + num_outputs = 5 + given_transaction = CMutableTransaction([], [make_txout() for x in range(0, num_outputs)]) + proxy = FakeBitcoinProxy() + + result = proxy.signrawtransaction(given_transaction) + + self.assertEqual(type(result), dict) + self.assertTrue("hex" in result.keys()) + + result_transaction_hex = result["hex"] + result_transaction = CMutableTransaction.deserialize(x(result_transaction_hex)) + + self.assertTrue(result_transaction is not given_transaction) + self.assertTrue(result_transaction.vin is not given_transaction.vin) + self.assertTrue(result_transaction.vout is not given_transaction.vout) + self.assertEqual(len(result_transaction.vout), len(given_transaction.vout)) + self.assertEqual(result_transaction.vout[0].scriptPubKey, given_transaction.vout[0].scriptPubKey) + + def test_sendrawtransaction(self): + num_outputs = 5 + given_transaction = CMutableTransaction([], [make_txout() for x in range(0, num_outputs)]) + expected_txid = b2lx(given_transaction.GetHash()) + given_transaction_hex = b2x(given_transaction.serialize()) + proxy = FakeBitcoinProxy() + resulting_txid = proxy.sendrawtransaction(given_transaction_hex) + self.assertEqual(resulting_txid, expected_txid) + + def test__batch_empty_list_input(self): + requests = [] + self.assertEqual(len(requests), 0) # must be empty for test + proxy = FakeBitcoinProxy() + results = proxy._batch(requests) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 0) + + def test__batch_raises_when_no_params(self): + proxy = FakeBitcoinProxy() + with self.assertRaises(TypeError): + proxy._batch() + + def test__batch_same_count_results_as_requests(self): + proxy = FakeBitcoinProxy() + request = make_rpc_batch_request_entry("getblockcount", []) + requests = [request] + results = proxy._batch(requests) + self.assertEqual(len(requests), len(results)) + + def test__batch_gives_reasonable_getblockcount_result(self): + proxy = FakeBitcoinProxy() + request = make_rpc_batch_request_entry("getblockcount", []) + requests = [request] + results = proxy._batch(requests) + self.assertEqual(results[0]["result"], -1) + + def test__batch_result_keys(self): + expected_keys = ["error", "id", "result"] + proxy = FakeBitcoinProxy() + request = make_rpc_batch_request_entry("getblockcount", []) + requests = [request] + results = proxy._batch(requests) + result = results[0] + self.assertTrue(all([expected_key in result.keys() for expected_key in expected_keys])) + + def test__batch_result_error_is_none(self): + proxy = FakeBitcoinProxy() + request = make_rpc_batch_request_entry("getblockcount", []) + requests = [request] + results = proxy._batch(requests) + result = results[0] + self.assertEqual(result["error"], None) + + def test__batch_returns_error_when_given_invalid_params(self): + params = 1 + proxy = FakeBitcoinProxy() + request = make_rpc_batch_request_entry("getblockcount", params) + requests = [request] + results = proxy._batch(requests) + result = results[0] + self.assertEqual(type(result), dict) + self.assertIn("error", result.keys()) + self.assertIn("id", result.keys()) + self.assertIn("result", result.keys()) + self.assertEqual(result["result"], None) + self.assertEqual(type(result["error"]), dict) + self.assertIn("message", result["error"].keys()) + self.assertIn("code", result["error"].keys()) + self.assertEqual(result["error"]["message"], "Params must be an array") + + def test__batch_getblockhash_many(self): + block_count = 100 + blocks = [] + + previous_blockhash = None + for counter in range(0, block_count): + blockhash = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea649{}".format(counter) + block_data = { + "hash": blockhash, + } + + if previous_blockhash: + block_data["previous_blockhash"] = previous_blockhash + + blocks.append(block_data) + previous_blockhash = blockhash + + proxy = FakeBitcoinProxy(blocks=blocks) + count = proxy.getblockcount() + self.assertEqual(count, len(blocks) - 1) + + requests = [] + for counter in range(0, count + 1): # from 0 to len(blocks) + request = make_rpc_batch_request_entry("getblockhash", [counter]) + requests.append(request) + + results = proxy._batch(requests) + + self.assertEqual(type(results), list) + self.assertEqual(len(results), len(requests)) + + for (counter, result) in enumerate(results): + self.assertEqual(result["error"], None) + + expected_blockhash = "00008c0c84aee66413f1e8ff95fdca5e8ebf35c94b090290077cdcea649{}".format(counter) + self.assertEqual(result["result"], expected_blockhash) + + def test_make_blocks_from_blockhashes(self): + blockhashes = ["blah{}".format(x) for x in range(0, 25)] + blocks = make_blocks_from_blockhashes(blockhashes) + + self.assertEqual(type(blocks), list) + self.assertEqual(len(blocks), len(blockhashes)) + self.assertEqual(type(blocks[0]), dict) + self.assertTrue(all(["hash" in block.keys() for block in blocks])) + self.assertTrue(all(["height" in block.keys() for block in blocks])) + self.assertTrue(all(["tx" in block.keys() for block in blocks])) + self.assertNotIn("previousblockhash", blocks[0].keys()) + self.assertTrue(all(["previousblockhash" in block.keys() for block in blocks[1:]])) + self.assertEqual(blocks[-1]["previousblockhash"], blocks[-2]["hash"]) + self.assertEqual(sorted([block["hash"] for block in blocks]), sorted(blockhashes)) + + def test_make_blocks_from_blockhashes_empty(self): + blockhashes = [] + blocks = make_blocks_from_blockhashes(blockhashes) + self.assertEqual(type(blocks), list) + self.assertEqual(len(blocks), len(blockhashes)) + + # Just in case for some reason the function ever appends to the given + # list (incorrectly).. + self.assertEqual(len(blockhashes), 0) + self.assertEqual(len(blocks), 0) + + # and because paranoia + self.assertTrue(blockhashes is not blocks) + +if __name__ == "__main__": + unittest.main() From 96dd6973719674581175eaffb35452de0eca30af Mon Sep 17 00:00:00 2001 From: Wan Wenli Date: Tue, 22 May 2018 19:00:04 +0800 Subject: [PATCH 02/82] fix checking on p2sh-p2wsh --- bitcoin/core/script.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index ffd97b8b..e8b6a3c7 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -691,7 +691,7 @@ def is_witness_v0_keyhash(self): return len(self) == 22 and self[0:2] == b'\x00\x14' def is_witness_v0_nested_keyhash(self): - """Returns true if this is a scriptpubkey for V0 P2WPKH embedded in P2SH. """ + """Returns true if this is a scriptSig for V0 P2WPKH embedded in P2SH. """ return len(self) == 23 and self[0:3] == b'\x16\x00\x14' def is_witness_v0_scripthash(self): @@ -699,8 +699,8 @@ def is_witness_v0_scripthash(self): return len(self) == 34 and self[0:2] == b'\x00\x20' def is_witness_v0_nested_scripthash(self): - """Returns true if this is a scriptpubkey for V0 P2WSH embedded in P2SH. """ - return len(self) == 23 and self[0:2] == b'\xa9\x14' and self[-1] == b'\x87' + """Returns true if this is a scriptSig for V0 P2WSH embedded in P2SH. """ + return len(self) == 35 and self[0:3] == b'\x22\x00\x20' def is_push_only(self): """Test if the script only contains pushdata ops From cdcfd3b924d4933d127e01d0cf3b6599ef03eb16 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Thu, 13 Sep 2018 18:17:56 +0500 Subject: [PATCH 03/82] add ability to use libsecp256k1 for signing (supports RFC6979) Two new functions are added to core/key.py: is_libsec256k1_available() -- to check that libsec256k1 is present in the system use_libsecp256k1_for_signing(do_use) - if do_use is True, sign() function will use libsec256k1 (it will check _libsecp256k1_enable_signing global flag) if do_use is False, _libsecp256k1_enable_signing flag is set to False, and openssl will be used within sign() function. --- bitcoin/core/key.py | 70 ++++++++++++++++++++++++++++++++++++ bitcoin/tests/test_wallet.py | 57 ++++++++++++++++++++++++++++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 4fce32c2..7d8a5772 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -32,6 +32,12 @@ _ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') +_libsecp256k1_path = ctypes.util.find_library('secp256k1') +_libsecp256k1_enable_signing = False +_libsecp256k1_context = None +_libsecp256k1 = None + + class OpenSSLException(EnvironmentError): pass @@ -185,12 +191,49 @@ def _check_res_void_p(val, func, args): # pylint: disable=unused-argument _ssl.o2i_ECPublicKey.restype = ctypes.c_void_p _ssl.o2i_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long] +_ssl.BN_num_bits.restype = ctypes.c_int +_ssl.BN_num_bits.argtypes = [ctypes.c_void_p] +_ssl.EC_KEY_get0_private_key.restype = ctypes.c_void_p + # this specifies the curve used with ECDSA. _NID_secp256k1 = 714 # from openssl/obj_mac.h # test that OpenSSL supports secp256k1 _ssl.EC_KEY_new_by_curve_name(_NID_secp256k1) +SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0) +SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9) +SECP256K1_CONTEXT_SIGN = \ + (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) + + +def is_libsec256k1_available(): + return _libsecp256k1_path is not None + + +def use_libsecp256k1_for_signing(do_use): + global _libsecp256k1 + global _libsecp256k1_context + global _libsecp256k1_enable_signing + + if not do_use: + _libsecp256k1_enable_signing = False + return + + if not is_libsec256k1_available(): + raise ImportError("unable to locate libsecp256k1") + + if _libsecp256k1_context is None: + _libsecp256k1 = ctypes.cdll.LoadLibrary(_libsecp256k1_path) + _libsecp256k1.secp256k1_context_create.restype = ctypes.c_void_p + _libsecp256k1.secp256k1_context_create.errcheck = _check_res_void_p + _libsecp256k1_context = _libsecp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN) + assert(_libsecp256k1_context is not None) + + _libsecp256k1_enable_signing = True + + + # From openssl/ecdsa.h class ECDSA_SIG_st(ctypes.Structure): _fields_ = [("r", ctypes.c_void_p), @@ -258,12 +301,39 @@ def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()): r = self.get_raw_ecdh_key(other_pubkey) return kdf(r) + def get_raw_privkey(self): + bn = _ssl.EC_KEY_get0_private_key(self.k) + bn = ctypes.c_void_p(bn) + size = (_ssl.BN_num_bits(bn) + 7) / 8 + mb = ctypes.create_string_buffer(int(size)) + _ssl.BN_bn2bin(bn, mb) + return mb.raw.rjust(32, b'\x00') + + def _sign_with_libsecp256k1(self, hash): + raw_sig = ctypes.create_string_buffer(64) + result = _libsecp256k1.secp256k1_ecdsa_sign( + _libsecp256k1_context, raw_sig, hash, self.get_raw_privkey(), None, None) + assert 1 == result + sig_size0 = ctypes.c_size_t() + sig_size0.value = 75 + mb_sig = ctypes.create_string_buffer(sig_size0.value) + result = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der( + _libsecp256k1_context, mb_sig, ctypes.byref(sig_size0), raw_sig) + assert 1 == result + # libsecp256k1 creates signatures already in lower-S form, no further + # conversion needed. + return mb_sig.raw[:sig_size0.value] + + def sign(self, hash): # pylint: disable=redefined-builtin if not isinstance(hash, bytes): raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) if len(hash) != 32: raise ValueError('Hash must be exactly 32 bytes long') + if _libsecp256k1_enable_signing: + return self._sign_with_libsecp256k1(hash) + sig_size0 = ctypes.c_uint32() sig_size0.value = _ssl.ECDSA_size(self.k) mb_sig = ctypes.create_string_buffer(sig_size0.value) diff --git a/bitcoin/tests/test_wallet.py b/bitcoin/tests/test_wallet.py index 5ee839af..1ae36c01 100644 --- a/bitcoin/tests/test_wallet.py +++ b/bitcoin/tests/test_wallet.py @@ -11,11 +11,12 @@ from __future__ import absolute_import, division, print_function, unicode_literals +import hashlib import unittest from bitcoin.core import b2x, x from bitcoin.core.script import CScript, IsLowDERSignature -from bitcoin.core.key import CPubKey +from bitcoin.core.key import CPubKey, is_libsec256k1_available, use_libsecp256k1_for_signing from bitcoin.wallet import * class Test_CBitcoinAddress(unittest.TestCase): @@ -238,3 +239,57 @@ def test_sign_invalid_hash(self): hash = b'\x00' * 32 with self.assertRaises(ValueError): sig = key.sign(hash[0:-2]) + + +class Test_RFC6979(unittest.TestCase): + def test(self): + if not is_libsec256k1_available(): + return + + use_libsecp256k1_for_signing(True) + + # Test Vectors for RFC 6979 ECDSA, secp256k1, SHA-256 + # (private key, message, expected k, expected signature) + test_vectors = [ + (0x1, "Satoshi Nakamoto", 0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15, "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"), + (0x1, "All those moments will be lost in time, like tears in rain. Time to die...", 0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3, "8600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"), + (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, "Satoshi Nakamoto", 0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90, "fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d06b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"), + (0xf8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181, "Alan Turing", 0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1, "7063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c58dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"), + (0xe91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2, "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", 0x1F4B84C23A86A221D233F2521BE018D9318639D5B8BBD6374A8A59232D16AD3D, "b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6") + ] + for vector in test_vectors: + secret = CBitcoinSecret.from_secret_bytes(x('{:064x}'.format(vector[0]))) + encoded_sig = secret.sign(hashlib.sha256(vector[1].encode('utf8')).digest()) + + assert(encoded_sig[0] == 0x30) + assert(encoded_sig[1] == len(encoded_sig)-2) + assert(encoded_sig[2] == 0x02) + + rlen = encoded_sig[3] + rpos = 4 + assert(rlen in (32, 33)) + + if rlen == 33: + assert(encoded_sig[rpos] == 0) + rpos += 1 + rlen -= 1 + + rval = encoded_sig[rpos:rpos+rlen] + spos = rpos+rlen + assert(encoded_sig[spos] == 0x02) + + spos += 1 + slen = encoded_sig[spos] + assert(slen in (32, 33)) + + spos += 1 + if slen == 33: + assert(encoded_sig[spos] == 0) + spos += 1 + slen -= 1 + + sval = encoded_sig[spos:spos+slen] + sig = b2x(rval + sval) + assert(str(sig) == vector[3]) + + use_libsecp256k1_for_signing(False) From cd81db643cd092f8a4df341f3628e412e1eb6300 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Thu, 13 Sep 2018 18:32:09 +0500 Subject: [PATCH 04/82] handle non-standard RPC responses Sometimes there is a need to issue RPC commands to the services that just pretend to be bitcoind, by emulating some of its commands. These services might not follow the same protocol for error reporting as bitcoind does. An example of such service would be feesim (https://github.com/bitcoinfees/feesim, Model-based Bitcoin fee estimation) With these changes, we can handle non-standard error responses gracefully. --- bitcoin/rpc.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index f2c5d4a5..15c4736c 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -227,8 +227,13 @@ def _call(self, service_name, *args): self.__conn.request('POST', self.__url.path, postdata, headers) response = self._get_response() - if response['error'] is not None: - raise JSONRPCError(response['error']) + err = response.get('error') + if err is not None: + if isinstance(err, dict): + raise JSONRPCError( + {'code': err.get('code', -345), + 'message': err.get('message', 'error message not specified')}) + raise JSONRPCError({'code': -344, 'message': str(err)}) elif 'result' not in response: raise JSONRPCError({ 'code': -343, 'message': 'missing JSON-RPC result'}) @@ -256,8 +261,15 @@ def _get_response(self): raise JSONRPCError({ 'code': -342, 'message': 'missing HTTP response from server'}) - return json.loads(http_response.read().decode('utf8'), - parse_float=decimal.Decimal) + rdata = http_response.read().decode('utf8') + try: + return json.loads(rdata, parse_float=decimal.Decimal) + except Exception: + raise JSONRPCError({ + 'code': -342, + 'message': ('non-JSON HTTP response with \'%i %s\' from server: \'%.20s%s\'' + % (http_response.status, http_response.reason, + rdata, '...' if len(rdata) > 20 else ''))}) def close(self): if self.__conn is not None: From 526b862a867b2f9ac1ecd0d15ef40eca46b1eb72 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Thu, 13 Sep 2018 20:12:56 +0500 Subject: [PATCH 05/82] BaseProxy: allow to specify custom connection This allows to handle disconnects and reconnections outside the library, by supplying your own connection object instead of httplib.HTTPConnection --- bitcoin/rpc.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index f2c5d4a5..ddd1178b 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -123,7 +123,8 @@ def __init__(self, service_url=None, service_port=None, btc_conf_file=None, - timeout=DEFAULT_HTTP_TIMEOUT): + timeout=DEFAULT_HTTP_TIMEOUT, + connection=None): # Create a dummy connection early on so if __init__() fails prior to # __conn being created __del__() can detect the condition and handle it @@ -204,8 +205,11 @@ def __init__(self, authpair = authpair.encode('utf8') self.__auth_header = b"Basic " + base64.b64encode(authpair) - self.__conn = httplib.HTTPConnection(self.__url.hostname, port=port, - timeout=timeout) + if connection: + self.__conn = connection + else: + self.__conn = httplib.HTTPConnection(self.__url.hostname, port=port, + timeout=timeout) def _call(self, service_name, *args): self.__id_count += 1 From 629a91941029b4c8142e2ed730ba0e04df276bb6 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Thu, 13 Sep 2018 21:08:13 +0500 Subject: [PATCH 06/82] CScript.is_witness_scriptpubkey : size must match second byte + 2 Model CScript.is_witness_scriptpubkey after bitcoind's CScript::IsWitnessProgram -- it not only checks for script size and first opcode, but also checks that data push size equals script size + 2 (entire script consists of version byte and data push). In absense of this check, is_witness_scriptpubkey wrongfully detects 1-of-1 p2sh redeem script as a witness program. --- bitcoin/core/script.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index ffd97b8b..156843ee 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -678,9 +678,23 @@ def is_p2sh(self): _bord(self[22]) == OP_EQUAL) def is_witness_scriptpubkey(self): - """Returns true if this is a scriptpubkey signaling segregated witness - data. """ - return 3 <= len(self) <= 42 and CScriptOp(struct.unpack(' 42: + return False + + head = struct.unpack(' Date: Sun, 5 Nov 2017 01:42:04 +0100 Subject: [PATCH 07/82] Add Bech32 encoding/decoding; add CBech32Data --- bitcoin/bech32.py | 74 +++++++++++ bitcoin/segwit_addr.py | 123 +++++++++++++++++++ bitcoin/tests/data/bech32_encode_decode.json | 8 ++ bitcoin/tests/data/bech32_invalid.json | 12 ++ bitcoin/tests/test_bech32.py | 66 ++++++++++ 5 files changed, 283 insertions(+) create mode 100644 bitcoin/bech32.py create mode 100644 bitcoin/segwit_addr.py create mode 100644 bitcoin/tests/data/bech32_encode_decode.json create mode 100644 bitcoin/tests/data/bech32_invalid.json create mode 100644 bitcoin/tests/test_bech32.py diff --git a/bitcoin/bech32.py b/bitcoin/bech32.py new file mode 100644 index 00000000..98804be5 --- /dev/null +++ b/bitcoin/bech32.py @@ -0,0 +1,74 @@ +# Copyright (C) 2017 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +"""Bech32 encoding and decoding""" + +import sys +_bchr = chr +_bord = ord +if sys.version > '3': + long = int + _bchr = lambda x: bytes([x]) + _bord = lambda x: x + +from bitcoin.segwit_addr import encode, decode + +class Bech32Error(Exception): + pass + +class Bech32ChecksumError(Bech32Error): + pass + +class CBech32Data(bytes): + """Bech32-encoded data + + Includes a witver and checksum. + """ + def __new__(cls, s): + """from bech32 addr to """ + witver, data = decode('bc', s) # TODO: bc from bitcoin.params + if witver is None and data is None: + raise Bech32Error('Bech32 decoding error') + + return cls.from_bytes(witver, data) + + def __init__(self, s): + """Initialize from bech32-encoded string + + Note: subclasses put your initialization routines here, but ignore the + argument - that's handled by __new__(), and .from_bytes() will call + __init__() with None in place of the string. + """ + + @classmethod + def from_bytes(cls, witver, witprog): + """Instantiate from witver and data""" + if not (0 <= witver <= 16): + raise ValueError('witver must be in range 0 to 16 inclusive; got %d' % witver) + self = bytes.__new__(cls, witprog) + self.witver = witver + + return self + + def to_bytes(self): + """Convert to bytes instance + + Note that it's the data represented that is converted; the checkum and + witver is not included. + """ + return b'' + self + + def __str__(self): + """Convert to string""" + return encode('bc', self.witver, self) # TODO: bc from bitcoin.params + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) diff --git a/bitcoin/segwit_addr.py b/bitcoin/segwit_addr.py new file mode 100644 index 00000000..d450080c --- /dev/null +++ b/bitcoin/segwit_addr.py @@ -0,0 +1,123 @@ +# Copyright (c) 2017 Pieter Wuille +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +"""Reference implementation for Bech32 and segwit addresses.""" + + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + return bech32_polymod(bech32_hrp_expand(hrp) + data) == 1 + + +def bech32_create_checksum(hrp, data): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1 + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + + +def bech32_decode(bech): + """Validate a Bech32 string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None) + bech = bech.lower() + pos = bech.rfind('1') + if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + return (None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + if not bech32_verify_checksum(hrp, data): + return (None, None) + return (hrp, data[:-6]) + + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return (None, None) + if data[0] > 16: + return (None, None) + if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5)) + if decode(hrp, ret) == (None, None): + return None + return ret diff --git a/bitcoin/tests/data/bech32_encode_decode.json b/bitcoin/tests/data/bech32_encode_decode.json new file mode 100644 index 00000000..d8b2b0eb --- /dev/null +++ b/bitcoin/tests/data/bech32_encode_decode.json @@ -0,0 +1,8 @@ +[ +["0014751e76e8199196d454941c45d1b3a323f1433bd6", "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"], +["00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"], +["5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6", "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx"], +["6002751e", "BC1SW50QA3JX3S"], +["5210751e76e8199196d454941c45d1b3a323", "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj"], +["0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433", "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy"] +] diff --git a/bitcoin/tests/data/bech32_invalid.json b/bitcoin/tests/data/bech32_invalid.json new file mode 100644 index 00000000..aeb898b0 --- /dev/null +++ b/bitcoin/tests/data/bech32_invalid.json @@ -0,0 +1,12 @@ +[ + ["tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", "Invalid human-readable part"], + ["bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", "Invalid checksum"], + ["BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", "Invalid witness version"], + ["bc1rw5uspcuh", "Invalid program length"], + ["bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", "Invalid program length"], + ["BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "Invalid program length for witness version 0 (per BIP141)"], + ["tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", "Mixed case"], + ["bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "zero padding of more than 4 bits"], + ["tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", "Non-zero padding in 8-to-5 conversion"], + ["bc1gmk9yu", "Empty data section"] +] \ No newline at end of file diff --git a/bitcoin/tests/test_bech32.py b/bitcoin/tests/test_bech32.py new file mode 100644 index 00000000..7cf2bc58 --- /dev/null +++ b/bitcoin/tests/test_bech32.py @@ -0,0 +1,66 @@ +# Copyright (C) 2013-2014 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import json +import os +import unittest + +from binascii import unhexlify + +from bitcoin.core.script import CScript, OP_0, OP_1, OP_16 +from bitcoin.bech32 import * + + +def load_test_vectors(name): + with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd: + for testcase in json.load(fd): + yield testcase + +def to_scriptPubKey(witver, witprog): + """Decoded bech32 address to script""" + return CScript([witver]) + CScript(bytes(witprog)) + +class Test_bech32(unittest.TestCase): + + def op_decode(self, witver): + """OP encoding to int""" + if witver == OP_0: + return 0 + if OP_1 <= witver <= OP_16: + return witver - OP_1 + 1 + self.fail('Wrong witver: %d' % witver) + + def test_encode_decode(self): + for exp_bin, exp_bech32 in load_test_vectors('bech32_encode_decode.json'): + exp_bin = unhexlify(exp_bin.encode('utf8')) + witver = self.op_decode(exp_bin[0]) + hrp = exp_bech32[:exp_bech32.rindex('1')].lower() + self.assertEqual(exp_bin[1], len(exp_bin[2:])) + act_bech32 = encode(hrp, witver, exp_bin[2:]) + act_bin = decode(hrp, exp_bech32) + + self.assertEqual(act_bech32.lower(), exp_bech32.lower()) + self.assertEqual(to_scriptPubKey(*act_bin), exp_bin) + +class Test_CBech32Data(unittest.TestCase): + def test_from_data(self): + b = CBech32Data.from_bytes(0, unhexlify('751e76e8199196d454941c45d1b3a323f1433bd6')) + self.assertEqual(b.witver, 0) + self.assertEqual(str(b).upper(), 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4') + + def test_invalid_bech32_exception(self): + + for invalid, _ in load_test_vectors("bech32_invalid.json"): + msg = '%r should have raised Bech32Error but did not' % invalid + with self.assertRaises(Bech32Error, msg=msg): + CBech32Data(invalid) From 01083b7c98fadcf6e0a295f3803525f131f7e98f Mon Sep 17 00:00:00 2001 From: Martino Salvetti Date: Sun, 5 Nov 2017 01:49:29 +0100 Subject: [PATCH 08/82] Add BECH32_HRP in network parameters --- bitcoin/__init__.py | 3 +++ bitcoin/bech32.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index d52d9145..0cc75b81 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -31,6 +31,7 @@ class MainParams(bitcoin.core.CoreMainParams): BASE58_PREFIXES = {'PUBKEY_ADDR':0, 'SCRIPT_ADDR':5, 'SECRET_KEY' :128} + BECH32_HRP = 'bc' class TestNetParams(bitcoin.core.CoreTestNetParams): MESSAGE_START = b'\x0b\x11\x09\x07' @@ -43,6 +44,7 @@ class TestNetParams(bitcoin.core.CoreTestNetParams): BASE58_PREFIXES = {'PUBKEY_ADDR':111, 'SCRIPT_ADDR':196, 'SECRET_KEY' :239} + BECH32_HRP = 'tb' class RegTestParams(bitcoin.core.CoreRegTestParams): MESSAGE_START = b'\xfa\xbf\xb5\xda' @@ -52,6 +54,7 @@ class RegTestParams(bitcoin.core.CoreRegTestParams): BASE58_PREFIXES = {'PUBKEY_ADDR':111, 'SCRIPT_ADDR':196, 'SECRET_KEY' :239} + BECH32_HRP = 'tb' """Master global setting for what chain params we're using. diff --git a/bitcoin/bech32.py b/bitcoin/bech32.py index 98804be5..c59b8aa6 100644 --- a/bitcoin/bech32.py +++ b/bitcoin/bech32.py @@ -20,6 +20,7 @@ _bord = lambda x: x from bitcoin.segwit_addr import encode, decode +import bitcoin class Bech32Error(Exception): pass @@ -34,7 +35,7 @@ class CBech32Data(bytes): """ def __new__(cls, s): """from bech32 addr to """ - witver, data = decode('bc', s) # TODO: bc from bitcoin.params + witver, data = decode(bitcoin.params.BECH32_HRP, s) if witver is None and data is None: raise Bech32Error('Bech32 decoding error') @@ -68,7 +69,7 @@ def to_bytes(self): def __str__(self): """Convert to string""" - return encode('bc', self.witver, self) # TODO: bc from bitcoin.params + return encode(bitcoin.params.BECH32_HRP, self.witver, self) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, str(self)) From 737380ea9c82f67ccf72f1a6a644c527e7eae830 Mon Sep 17 00:00:00 2001 From: Martino Salvetti Date: Sun, 5 Nov 2017 22:54:07 +0100 Subject: [PATCH 09/82] Add python2 support to bech32 --- bitcoin/segwit_addr.py | 8 ++++++++ bitcoin/tests/test_bech32.py | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/bitcoin/segwit_addr.py b/bitcoin/segwit_addr.py index d450080c..67d17cff 100644 --- a/bitcoin/segwit_addr.py +++ b/bitcoin/segwit_addr.py @@ -20,6 +20,13 @@ """Reference implementation for Bech32 and segwit addresses.""" +import sys +_bchr = chr +_bord = lambda x: ord(x) if isinstance(x, str) else x +if sys.version > '3': + long = int + _bchr = lambda x: bytes([x]) + _bord = lambda x: x CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" @@ -85,6 +92,7 @@ def convertbits(data, frombits, tobits, pad=True): maxv = (1 << tobits) - 1 max_acc = (1 << (frombits + tobits - 1)) - 1 for value in data: + value = _bord(value) if value < 0 or (value >> frombits): return None acc = ((acc << frombits) | value) & max_acc diff --git a/bitcoin/tests/test_bech32.py b/bitcoin/tests/test_bech32.py index 7cf2bc58..957dcd92 100644 --- a/bitcoin/tests/test_bech32.py +++ b/bitcoin/tests/test_bech32.py @@ -14,6 +14,16 @@ import json import os import unittest +import array +import sys +_bchr = chr +_bord = ord +_tobytes = lambda x: array.array('B', x).tostring() +if sys.version > '3': + long = int + _bchr = lambda x: bytes([x]) + _bord = lambda x: x + _tobytes = bytes from binascii import unhexlify @@ -28,7 +38,7 @@ def load_test_vectors(name): def to_scriptPubKey(witver, witprog): """Decoded bech32 address to script""" - return CScript([witver]) + CScript(bytes(witprog)) + return CScript([witver]) + CScript(_tobytes(witprog)) class Test_bech32(unittest.TestCase): @@ -42,7 +52,7 @@ def op_decode(self, witver): def test_encode_decode(self): for exp_bin, exp_bech32 in load_test_vectors('bech32_encode_decode.json'): - exp_bin = unhexlify(exp_bin.encode('utf8')) + exp_bin = [_bord(y) for y in unhexlify(exp_bin.encode('utf8'))] witver = self.op_decode(exp_bin[0]) hrp = exp_bech32[:exp_bech32.rindex('1')].lower() self.assertEqual(exp_bin[1], len(exp_bin[2:])) @@ -50,7 +60,7 @@ def test_encode_decode(self): act_bin = decode(hrp, exp_bech32) self.assertEqual(act_bech32.lower(), exp_bech32.lower()) - self.assertEqual(to_scriptPubKey(*act_bin), exp_bin) + self.assertEqual(to_scriptPubKey(*act_bin), _tobytes(exp_bin)) class Test_CBech32Data(unittest.TestCase): def test_from_data(self): From 7c2b4e142db0149be5cb78788a9c81ad16f00384 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Fri, 14 Sep 2018 16:45:23 +0500 Subject: [PATCH 10/82] add Bech32-encoded address support introduce CBase58Address and CBech32Address make CBitcoinAddress a wrapper that returns CBase58Address or CBech32Address depending on input data introduce to_redeemScript() method, because for P2WPKHBitcoinAddress redeem script is not the same as scriptPubKey. unfortunately, for P2WSHBitcoinAddress to_redeemScript() cannot be implemented, as we need all pubkeys for that, and not only the hash. --- bitcoin/bech32.py | 6 ++ bitcoin/tests/test_bech32.py | 1 + bitcoin/tests/test_segwit.py | 8 +- bitcoin/wallet.py | 146 ++++++++++++++++++++++++++++++++--- 4 files changed, 147 insertions(+), 14 deletions(-) diff --git a/bitcoin/bech32.py b/bitcoin/bech32.py index c59b8aa6..fe95c70c 100644 --- a/bitcoin/bech32.py +++ b/bitcoin/bech32.py @@ -73,3 +73,9 @@ def __str__(self): def __repr__(self): return '%s(%r)' % (self.__class__.__name__, str(self)) + +__all__ = ( + 'Bech32Error', + 'Bech32ChecksumError', + 'CBech32Data', +) diff --git a/bitcoin/tests/test_bech32.py b/bitcoin/tests/test_bech32.py index 957dcd92..3efebca5 100644 --- a/bitcoin/tests/test_bech32.py +++ b/bitcoin/tests/test_bech32.py @@ -29,6 +29,7 @@ from bitcoin.core.script import CScript, OP_0, OP_1, OP_16 from bitcoin.bech32 import * +from bitcoin.segwit_addr import encode, decode def load_test_vectors(name): diff --git a/bitcoin/tests/test_segwit.py b/bitcoin/tests/test_segwit.py index 62e22ea2..9273fded 100644 --- a/bitcoin/tests/test_segwit.py +++ b/bitcoin/tests/test_segwit.py @@ -36,8 +36,8 @@ def test_p2wpkh_signaturehash(self): scriptpubkey = CScript(x('00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1')) value = int(6*COIN) - address = CBitcoinAddress.from_scriptPubKey(scriptpubkey) - self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx), + address = CBech32BitcoinAddress.from_scriptPubKey(scriptpubkey) + self.assertEqual(SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 1, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670')) @@ -47,8 +47,8 @@ def test_p2sh_p2wpkh_signaturehash(self): redeemscript = CScript(x('001479091972186c449eb1ded22b78e40d009bdf0089')) value = int(10*COIN) - address = CBitcoinAddress.from_scriptPubKey(redeemscript) - self.assertEqual(SignatureHash(address.to_scriptPubKey(), CTransaction.deserialize(unsigned_tx), + address = CBase58BitcoinAddress.from_scriptPubKey(redeemscript) + self.assertEqual(SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6')) diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index 3c463a3d..a2ff57d1 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -25,19 +25,93 @@ import bitcoin import bitcoin.base58 +import bitcoin.bech32 import bitcoin.core import bitcoin.core.key import bitcoin.core.script as script -class CBitcoinAddressError(bitcoin.base58.Base58Error): + +class CBitcoinAddress(object): + + def __new__(cls, s): + try: + return CBech32BitcoinAddress(s) + except bitcoin.bech32.Bech32Error: + pass + + try: + return CBase58BitcoinAddress(s) + except bitcoin.base58.Base58Error: + pass + + raise CBitcoinAddressError('Unrecognized encoding for bitcoin address') + + @classmethod + def from_scriptPubKey(cls, scriptPubKey): + """Convert a scriptPubKey to a subclass of CBitcoinAddress""" + try: + return CBech32BitcoinAddress.from_scriptPubKey(scriptPubKey) + except CBitcoinAddressError: + pass + + try: + return CBase58BitcoinAddress.from_scriptPubKey(scriptPubKey) + except CBitcoinAddressError: + pass + + raise CBitcoinAddressError('scriptPubKey is not in a recognized address format') + + +class CBitcoinAddressError(Exception): """Raised when an invalid Bitcoin address is encountered""" -class CBitcoinAddress(bitcoin.base58.CBase58Data): - """A Bitcoin address""" + +class CBech32BitcoinAddress(bitcoin.bech32.CBech32Data, CBitcoinAddress): + """A Bech32-encoded Bitcoin address""" + + @classmethod + def from_bytes(cls, witver, witprog): + + assert(witver == 0) + + self = super(CBech32BitcoinAddress, cls).from_bytes(witver, witprog) + + if len(self) == 32: + self.__class__ = P2WSHBitcoinAddress + elif len(self) == 20: + self.__class__ = P2WPKHBitcoinAddress + else: + raise CBitcoinAddressError('witness program does not match any known segwit address format') + + return self + + @classmethod + def from_scriptPubKey(cls, scriptPubKey): + """Convert a scriptPubKey to a CBech32BitcoinAddress + + Returns a CBech32BitcoinAddress subclass, either P2WSHBitcoinAddress or + P2WPKHBitcoinAddress. If the scriptPubKey is not recognized + CBitcoinAddressError will be raised. + """ + try: + return P2WSHBitcoinAddress.from_scriptPubKey(scriptPubKey) + except CBitcoinAddressError: + pass + + try: + return P2WPKHBitcoinAddress.from_scriptPubKey(scriptPubKey) + except CBitcoinAddressError: + pass + + raise CBitcoinAddressError('scriptPubKey not a valid bech32-encoded address') + + +class CBase58BitcoinAddress(bitcoin.base58.CBase58Data, CBitcoinAddress): + """A Base58-encoded Bitcoin address""" @classmethod def from_bytes(cls, data, nVersion): - self = super(CBitcoinAddress, cls).from_bytes(data, nVersion) + self = super(CBase58BitcoinAddress, cls).from_bytes(data, nVersion) if nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR']: self.__class__ = P2SHBitcoinAddress @@ -68,13 +142,10 @@ def from_scriptPubKey(cls, scriptPubKey): except CBitcoinAddressError: pass - raise CBitcoinAddressError('scriptPubKey not a valid address') + raise CBitcoinAddressError('scriptPubKey not a valid base58-encoded address') - def to_scriptPubKey(self): - """Convert an address to a scriptPubKey""" - raise NotImplementedError -class P2SHBitcoinAddress(CBitcoinAddress): +class P2SHBitcoinAddress(CBase58BitcoinAddress): @classmethod def from_bytes(cls, data, nVersion=None): if nVersion is None: @@ -112,7 +183,11 @@ def to_scriptPubKey(self): assert self.nVersion == bitcoin.params.BASE58_PREFIXES['SCRIPT_ADDR'] return script.CScript([script.OP_HASH160, self, script.OP_EQUAL]) -class P2PKHBitcoinAddress(CBitcoinAddress): + def to_redeemScript(self): + return self.to_scriptPubKey() + + +class P2PKHBitcoinAddress(CBase58BitcoinAddress): @classmethod def from_bytes(cls, data, nVersion=None): if nVersion is None: @@ -204,6 +279,55 @@ def to_scriptPubKey(self, nested=False): assert self.nVersion == bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR'] return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG]) + def to_redeemScript(self): + return self.to_scriptPubKey() + + +class P2WSHBitcoinAddress(CBech32BitcoinAddress): + + @classmethod + def from_scriptPubKey(cls, scriptPubKey): + """Convert a scriptPubKey to a P2WSH address + + Raises CBitcoinAddressError if the scriptPubKey isn't of the correct + form. + """ + if scriptPubKey.is_witness_v0_scripthash(): + return cls.from_bytes(0, scriptPubKey[2:34]) + else: + raise CBitcoinAddressError('not a P2WSH scriptPubKey') + + def to_scriptPubKey(self): + """Convert an address to a scriptPubKey""" + assert self.witver == 0 + return script.CScript([0, self]) + + def to_redeemScript(self): + return NotImplementedError("not enough data in p2wsh address to reconstruct redeem script") + + +class P2WPKHBitcoinAddress(CBech32BitcoinAddress): + + @classmethod + def from_scriptPubKey(cls, scriptPubKey): + """Convert a scriptPubKey to a P2WSH address + + Raises CBitcoinAddressError if the scriptPubKey isn't of the correct + form. + """ + if scriptPubKey.is_witness_v0_keyhash(): + return cls.from_bytes(0, scriptPubKey[2:22]) + else: + raise CBitcoinAddressError('not a P2WSH scriptPubKey') + + def to_scriptPubKey(self): + """Convert an address to a scriptPubKey""" + assert self.witver == 0 + return script.CScript([0, self]) + + def to_redeemScript(self): + return script.CScript([script.OP_DUP, script.OP_HASH160, self, script.OP_EQUALVERIFY, script.OP_CHECKSIG]) + class CKey(object): """An encapsulated private key @@ -256,6 +380,8 @@ def __init__(self, s): __all__ = ( 'CBitcoinAddressError', 'CBitcoinAddress', + 'CBase58BitcoinAddress', + 'CBech32BitcoinAddress', 'P2SHBitcoinAddress', 'P2PKHBitcoinAddress', 'CKey', From e8729897184c3fa75b533327b3eabbf98618a012 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Fri, 14 Sep 2018 17:50:34 +0500 Subject: [PATCH 11/82] add str() tests for P2WPKHBitcoinAddress and P2WSHBitcoinAddress unfortunately, at the moment, the tests do not work for python2, only for python3 --- bitcoin/tests/test_wallet.py | 15 +++++++++++++-- bitcoin/wallet.py | 4 +++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bitcoin/tests/test_wallet.py b/bitcoin/tests/test_wallet.py index 5ee839af..5ab19767 100644 --- a/bitcoin/tests/test_wallet.py +++ b/bitcoin/tests/test_wallet.py @@ -22,11 +22,14 @@ class Test_CBitcoinAddress(unittest.TestCase): def test_create_from_string(self): """Create CBitcoinAddress's from strings""" - def T(str_addr, expected_bytes, expected_nVersion, expected_class): + def T(str_addr, expected_bytes, expected_version, expected_class): addr = CBitcoinAddress(str_addr) self.assertEqual(addr.to_bytes(), expected_bytes) - self.assertEqual(addr.nVersion, expected_nVersion) self.assertEqual(addr.__class__, expected_class) + if isinstance(addr, CBase58BitcoinAddress): + self.assertEqual(addr.nVersion, expected_version) + elif isinstance(addr, CBech32BitcoinAddress): + self.assertEqual(addr.witver, expected_version) T('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', x('62e907b15cbf27d5425399ebf6f0fb50ebb88f18'), 0, @@ -36,6 +39,14 @@ def T(str_addr, expected_bytes, expected_nVersion, expected_class): x('4266fc6f2c2861d7fe229b279a79803afca7ba34'), 5, P2SHBitcoinAddress) + T('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4', + x('751e76e8199196d454941c45d1b3a323f1433bd6'), 0, + P2WPKHBitcoinAddress) + + T('bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', + x('c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d'), 0, + P2WSHBitcoinAddress) + def test_wrong_nVersion(self): """Creating a CBitcoinAddress from a unknown nVersion fails""" diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index a2ff57d1..5e1452e4 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -81,7 +81,7 @@ def from_bytes(cls, witver, witprog): elif len(self) == 20: self.__class__ = P2WPKHBitcoinAddress else: - raise CBitcoinAddressError('witness program does not match any known segwit address format') + raise CBitcoinAddressError('witness program does not match any known segwit address format') return self @@ -384,6 +384,8 @@ def __init__(self, s): 'CBech32BitcoinAddress', 'P2SHBitcoinAddress', 'P2PKHBitcoinAddress', + 'P2WSHBitcoinAddress', + 'P2WPKHBitcoinAddress', 'CKey', 'CBitcoinSecretError', 'CBitcoinSecret', From 542ac35ea7a3a6e2ca17c9ea19c40f6f3c5e8613 Mon Sep 17 00:00:00 2001 From: genme Date: Fri, 14 Sep 2018 20:00:45 +0500 Subject: [PATCH 12/82] Add python2 support to CBech32BitcoinAddress --- bitcoin/wallet.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index 5e1452e4..e0be96ee 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -17,11 +17,14 @@ from __future__ import absolute_import, division, print_function, unicode_literals +import array import sys _bord = ord +_tobytes = lambda x: array.array('B', x).tostring() if sys.version > '3': _bord = lambda x: x + _tobytes = bytes import bitcoin import bitcoin.base58 @@ -72,9 +75,11 @@ class CBech32BitcoinAddress(bitcoin.bech32.CBech32Data, CBitcoinAddress): @classmethod def from_bytes(cls, witver, witprog): - assert(witver == 0) - - self = super(CBech32BitcoinAddress, cls).from_bytes(witver, witprog) + assert witver == 0 + self = super(CBech32BitcoinAddress, cls).from_bytes( + witver, + _tobytes(witprog) + ) if len(self) == 32: self.__class__ = P2WSHBitcoinAddress From e8d2037bfb988735dd83a589f59819796c51e90e Mon Sep 17 00:00:00 2001 From: genme Date: Fri, 14 Sep 2018 20:44:02 +0500 Subject: [PATCH 13/82] Add tests --- bitcoin/tests/test_wallet.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/bitcoin/tests/test_wallet.py b/bitcoin/tests/test_wallet.py index 5ab19767..0f107e95 100644 --- a/bitcoin/tests/test_wallet.py +++ b/bitcoin/tests/test_wallet.py @@ -68,6 +68,12 @@ def T(hex_scriptpubkey, expected_str_address, expected_class): P2SHBitcoinAddress) T('76a914000000000000000000000000000000000000000088ac', '1111111111111111111114oLvT2', P2PKHBitcoinAddress) + T('0014751e76e8199196d454941c45d1b3a323f1433bd6', + 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + P2WPKHBitcoinAddress) + T('0020c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d', + 'bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9', + P2WSHBitcoinAddress) def test_from_nonstd_scriptPubKey(self): """CBitcoinAddress.from_scriptPubKey() with non-standard scriptPubKeys""" @@ -111,6 +117,23 @@ def test_from_invalid_scriptPubKey(self): with self.assertRaises(CBitcoinAddressError): CBitcoinAddress.from_scriptPubKey(scriptPubKey) + def test_to_redeemScript(self): + def T(str_addr, expected_scriptPubKey_hexbytes): + addr = CBitcoinAddress(str_addr) + + actual_scriptPubKey = addr.to_redeemScript() + self.assertEqual(b2x(actual_scriptPubKey), + expected_scriptPubKey_hexbytes) + + T('31h1vYVSYuKP6AhS86fbRdMw9XHieotbST', + 'a914000000000000000000000000000000000000000087') + + T('1111111111111111111114oLvT2', + '76a914000000000000000000000000000000000000000088ac') + + T('bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + '76a914751e76e8199196d454941c45d1b3a323f1433bd688ac') + def test_to_scriptPubKey(self): """CBitcoinAddress.to_scriptPubKey() works""" def T(str_addr, expected_scriptPubKey_hexbytes): @@ -125,10 +148,17 @@ def T(str_addr, expected_scriptPubKey_hexbytes): T('1111111111111111111114oLvT2', '76a914000000000000000000000000000000000000000088ac') + class Test_P2SHBitcoinAddress(unittest.TestCase): def test_from_redeemScript(self): - addr = P2SHBitcoinAddress.from_redeemScript(CScript()) - self.assertEqual(str(addr), '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') + def T(script, expected_str_address): + addr = P2SHBitcoinAddress.from_redeemScript(script) + self.assertEqual(str(addr), expected_str_address) + + T(CScript(), '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') + T(CScript(x('76a914751e76e8199196d454941c45d1b3a323f1433bd688ac')), + '3LRW7jeCvQCRdPF8S3yUCfRAx4eqXFmdcr') + class Test_P2PKHBitcoinAddress(unittest.TestCase): def test_from_non_canonical_scriptPubKey(self): From 1547251774fe15b672ad2230a1ab5ba1fcc28abd Mon Sep 17 00:00:00 2001 From: John Villar Date: Sun, 14 Oct 2018 18:34:49 -0400 Subject: [PATCH 14/82] Bitcoin's default Regtest bech32 HRP is 'bcrt' not 'tb' --- bitcoin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 0cc75b81..41f9f92a 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -54,7 +54,7 @@ class RegTestParams(bitcoin.core.CoreRegTestParams): BASE58_PREFIXES = {'PUBKEY_ADDR':111, 'SCRIPT_ADDR':196, 'SECRET_KEY' :239} - BECH32_HRP = 'tb' + BECH32_HRP = 'bcrt' """Master global setting for what chain params we're using. From 560513f6c3c3466065fe60bf18f3c033ae400b12 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Thu, 29 Nov 2018 10:13:31 +0500 Subject: [PATCH 15/82] call secp256k1_context_randomize() on _libsecp256k1_context --- bitcoin/core/key.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 7d8a5772..15de5083 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -19,6 +19,7 @@ import ctypes.util import hashlib import sys +from os import urandom import bitcoin import bitcoin.signature @@ -227,8 +228,15 @@ def use_libsecp256k1_for_signing(do_use): _libsecp256k1 = ctypes.cdll.LoadLibrary(_libsecp256k1_path) _libsecp256k1.secp256k1_context_create.restype = ctypes.c_void_p _libsecp256k1.secp256k1_context_create.errcheck = _check_res_void_p + _libsecp256k1.secp256k1_context_randomize.restype = ctypes.c_int + _libsecp256k1.secp256k1_context_randomize.argtypes = [ctypes.c_void_p, ctypes.c_char_p] _libsecp256k1_context = _libsecp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN) assert(_libsecp256k1_context is not None) + seed = urandom(32) + result = _libsecp256k1.secp256k1_context_randomize(_libsecp256k1_context, seed) + assert 1 == result + + _libsecp256k1_enable_signing = True From 18c883bea8e5217dd04f9da432801f711cfbd025 Mon Sep 17 00:00:00 2001 From: smasuda Date: Thu, 27 Dec 2018 21:36:48 +0900 Subject: [PATCH 16/82] add comment on an argument of getblock rpc --- bitcoin/rpc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index f2c5d4a5..125edd3f 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -449,6 +449,9 @@ def getblock(self, block_hash): raise TypeError('%s.getblock(): block_hash must be bytes; got %r instance' % (self.__class__.__name__, block_hash.__class__)) try: + # With this change ( https://github.com/bitcoin/bitcoin/commit/96c850c20913b191cff9f66fedbb68812b1a41ea#diff-a0c8f511d90e83aa9b5857e819ced344 ), + # bitcoin core's rpc takes 0/1/2 instead of true/false as the 2nd argument which specifies verbosity, since v0.15.0. + # The change above is backward-compatible so far; the old "false" is taken as the new "0". r = self._call('getblock', block_hash, False) except InvalidAddressOrKeyError as ex: raise IndexError('%s.getblock(): %s (%d)' % From c31b6e741422e856803b4eeae1b4ed4bda71c3ed Mon Sep 17 00:00:00 2001 From: Mark B Lundeberg <36528214+markblundeberg@users.noreply.github.com> Date: Sun, 3 Feb 2019 12:59:20 -0800 Subject: [PATCH 17/82] misc fixes key.py * EC_POINT_mul hears you the first time, don't need to call twice. * EC_KEY_set_private_key makes a copy of the key; without freeing it we have a memory leak. * BN_bin2bn happy to take None as arg and do allocation. --- bitcoin/core/key.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 4fce32c2..9341d7f6 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -211,16 +211,16 @@ def __del__(self): self.k = None def set_secretbytes(self, secret): - priv_key = _ssl.BN_bin2bn(secret, 32, _ssl.BN_new()) + priv_key = _ssl.BN_bin2bn(secret, 32, None) group = _ssl.EC_KEY_get0_group(self.k) pub_key = _ssl.EC_POINT_new(group) ctx = _ssl.BN_CTX_new() if not _ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx): raise ValueError("Could not derive public key from the supplied secret.") - _ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) _ssl.EC_KEY_set_private_key(self.k, priv_key) _ssl.EC_KEY_set_public_key(self.k, pub_key) _ssl.EC_POINT_free(pub_key) + _ssl.BN_free(priv_key) _ssl.BN_CTX_free(ctx) return self.k From 65377c72883ea259d291920962f1d483294dfa56 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Sat, 22 Jun 2019 19:35:09 +0500 Subject: [PATCH 18/82] add comment to __getattr__ in RawProxy to clarify the intent of check --- bitcoin/rpc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 125edd3f..a17237bf 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -289,7 +289,10 @@ def __init__(self, def __getattr__(self, name): if name.startswith('__') and name.endswith('__'): - # Python internal stuff + # Prevent RPC calls for non-existing python internal attribute + # access. If someone tries to get an internal attribute + # of RawProxy instance, and the instance does not have this + # attribute, we do not want the bogus RPC call to happen. raise AttributeError # Create a callable to do the actual call From e74ecae24e51e17694dcf450ff132fd245dcad85 Mon Sep 17 00:00:00 2001 From: Dmitry Petukhov Date: Sat, 22 Jun 2019 19:36:55 +0500 Subject: [PATCH 19/82] fix import in tests/test_fakebitcoinproxy.py --- bitcoin/tests/test_fakebitcoinproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/tests/test_fakebitcoinproxy.py b/bitcoin/tests/test_fakebitcoinproxy.py index a38be6b0..0d8114f7 100644 --- a/bitcoin/tests/test_fakebitcoinproxy.py +++ b/bitcoin/tests/test_fakebitcoinproxy.py @@ -35,7 +35,7 @@ P2PKHBitcoinAddress, ) -from fakebitcoinproxy import ( +from bitcoin.tests.fakebitcoinproxy import ( FakeBitcoinProxy, make_txout, make_blocks_from_blockhashes, From 98ecbc62b9f4d138979dd964d5941746f0233021 Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 5 Jul 2019 12:23:06 +0200 Subject: [PATCH 20/82] work around a python 27 incompatibility by defining FileNotFoundError --- bitcoin/rpc.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 125edd3f..6d3b6640 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -42,6 +42,11 @@ except ImportError: import urlparse +try: + FileNotFoundError +except NameError: + FileNotFoundError = IOError + import bitcoin from bitcoin.core import COIN, x, lx, b2lx, CBlock, CBlockHeader, CTransaction, COutPoint, CTxOut from bitcoin.core.script import CScript From 173e8604b7d4bb18c1b8305a5e69d52030bd37fc Mon Sep 17 00:00:00 2001 From: Florian Date: Fri, 5 Jul 2019 19:12:54 +0200 Subject: [PATCH 21/82] add version magic to python2 compat fix in rpc.py --- bitcoin/rpc.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 6d3b6640..63b7cd71 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -42,10 +42,12 @@ except ImportError: import urlparse -try: - FileNotFoundError -except NameError: - FileNotFoundError = IOError +# needed for python2 compatibility +if sys.version < '3': + try: + FileNotFoundError + except NameError: + FileNotFoundError = IOError import bitcoin from bitcoin.core import COIN, x, lx, b2lx, CBlock, CBlockHeader, CTransaction, COutPoint, CTxOut From 6fa8b95fe74b0c1e9719beaaee46fdf0de800aef Mon Sep 17 00:00:00 2001 From: "alexei.zamyatin" Date: Sat, 10 Aug 2019 18:44:31 +0200 Subject: [PATCH 22/82] Added generatetoaddress(self,numblocks,addr), replacing generate(self,numblocks) which will be dropped in bitcoin-core v0.19 --- bitcoin/rpc.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index b5beb78b..bea77c2b 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -381,7 +381,10 @@ def fundrawtransaction(self, tx, include_watching=False): return r def generate(self, numblocks): - """Mine blocks immediately (before the RPC call returns) + """ + DEPRECATED (will be removed in bitcoin-core v0.19) + + Mine blocks immediately (before the RPC call returns) numblocks - How many blocks are generated immediately. @@ -389,6 +392,19 @@ def generate(self, numblocks): """ r = self._call('generate', numblocks) return (lx(blk_hash) for blk_hash in r) + + def generatetoaddress(self, numblocks, addr): + """Mine blocks immediately (before the RPC call returns) and + allocate block reward to passed address. Replaces deprecated + "generate(self,numblocks)" method. + + numblocks - How many blocks are generated immediately. + addr - Address to receive block reward (CBitcoinAddress instance) + + Returns iterable of block hashes generated. + """ + r = self._call('generatetoaddress', numblocks, str(addr)) + return (lx(blk_hash) for blk_hash in r) def getaccountaddress(self, account=None): """Return the current Bitcoin address for receiving payments to this From bc9f5eddb73fa938e659c7520c53f12e5bd76d5b Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Sat, 17 Aug 2019 19:57:15 -0400 Subject: [PATCH 23/82] Release v0.10.2 --- bitcoin/__init__.py | 2 +- release-notes.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index d52d9145..dc71d760 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -16,7 +16,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.10.2dev' +__version__ = '0.10.2' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 57e26c3c..6313ad44 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,15 @@ # python-bitcoinlib release notes +## v0.10.2 + +Note: this will be the last release of python-bitcoinlib with Python 2.7 +compatibility. + +* New RPC `generatetoaddress(self,numblocks,addr)`. +* Fixed Python 2.7 incompatibility. +* Various OpenSSL fixes, including a memory leak. + + ## v0.10.1 Identical in every way to v0.10.0, but re-uploaded under a new version to fix a From 95cfa7e6d3636466091ecaaaadcd8dc21af90f65 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 20 Aug 2019 23:07:58 -0400 Subject: [PATCH 24/82] Reset version for future development --- bitcoin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index dc71d760..11d2a140 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -16,7 +16,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.10.2' +__version__ = '0.11.0dev' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' From de971091b3368866823ebd4f6c0eb5da409bb4c7 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 20 Aug 2019 23:13:59 -0400 Subject: [PATCH 25/82] Remove Python2 support --- README.md | 8 ++------ bitcoin/rpc.py | 7 ------- tox.ini | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 550d53d1..7d9d9078 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-bitcoinlib -This Python2/3 library provides an easy interface to the bitcoin data +This Python3 library provides an easy interface to the bitcoin data structures and protocol. The approach is low-level and "ground up", with a focus on providing tools to manipulate the internals of how Bitcoin works. @@ -95,9 +95,7 @@ the Satoshi codebase. Under bitcoin/tests using test data from Bitcoin Core. To run them: - python -m unittest discover && python3 -m unittest discover - -Please run the tests on both Python2 and Python3 for your pull-reqs! + python3 -m unittest discover Alternately, if Tox (see https://tox.readthedocs.org/) is available on your system, you can run unit tests for multiple Python versions: @@ -107,11 +105,9 @@ system, you can run unit tests for multiple Python versions: Currently, the following implementations are tried (any not installed are skipped): - * CPython 2.7 * CPython 3.3 * CPython 3.4 * CPython 3.5 - * PyPy * PyPy3 HTML coverage reports can then be found in the htmlcov/ subdirectory. diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index bea77c2b..61b21fc3 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -42,13 +42,6 @@ except ImportError: import urlparse -# needed for python2 compatibility -if sys.version < '3': - try: - FileNotFoundError - except NameError: - FileNotFoundError = IOError - import bitcoin from bitcoin.core import COIN, x, lx, b2lx, CBlock, CBlockHeader, CTransaction, COutPoint, CTxOut from bitcoin.core.script import CScript diff --git a/tox.ini b/tox.ini index 7e35da57..1def56fb 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ [tox] #------------------------------------------------------------------- -envlist = reset,py27,py33,py34,py35,pypy,pypy3,stats +envlist = reset,py33,py34,py35,pypy3,stats skip_missing_interpreters = True [testenv] #--------------------------------------------------------------- From 327eb3691ba9f9b95d94003496899ed4e3081c30 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 20 Aug 2019 23:20:27 -0400 Subject: [PATCH 26/82] Update versions tested by tox --- README.md | 8 -------- tox.ini | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 7d9d9078..22964d73 100644 --- a/README.md +++ b/README.md @@ -102,14 +102,6 @@ system, you can run unit tests for multiple Python versions: ./runtests.sh -Currently, the following implementations are tried (any not installed are -skipped): - - * CPython 3.3 - * CPython 3.4 - * CPython 3.5 - * PyPy3 - HTML coverage reports can then be found in the htmlcov/ subdirectory. ## Documentation diff --git a/tox.ini b/tox.ini index 1def56fb..4db912ad 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ [tox] #------------------------------------------------------------------- -envlist = reset,py33,py34,py35,pypy3,stats +envlist = reset,py33,py34,py35,py36,py37,pypy3,stats skip_missing_interpreters = True [testenv] #--------------------------------------------------------------- From 2e5a7c9e5fb277312e5bffb411abb2cbee751e51 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 21 Aug 2019 02:46:25 -0400 Subject: [PATCH 27/82] Add travis config --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..48cf063c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +git: + depth: 9999999 +language: python +python: + - "3.4" + - "3.5" + - "3.6" + - "3.7" +# command to run tests +script: python3 -m unittest discover -v From 59a42761840897a4286bfcdb04a3097e7614910f Mon Sep 17 00:00:00 2001 From: Emanuele Cisbani Date: Thu, 22 Aug 2019 12:06:15 +0200 Subject: [PATCH 28/82] added rpc call signrawtransactionwithwallet signrawtransaction is deprecated from bitcoincore 0.17.0 and removed from bitcoincore 0.18.0 --- bitcoin/rpc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 5da2f784..51f24ac9 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -719,6 +719,18 @@ def signrawtransaction(self, tx, *args): del r['hex'] return r + def signrawtransactionwithwallet(self, tx, *args): + """Sign inputs for transaction + bicoincore >= 0.17.x + + FIXME: implement options + """ + hextx = hexlify(tx.serialize()) + r = self._call('signrawtransactionwithwallet', hextx, *args) + r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + del r['hex'] + return r + def submitblock(self, block, params=None): """Submit a new block to the network. From 70ce3b3fd2d13d54662248c7903bf5e88b1f85d3 Mon Sep 17 00:00:00 2001 From: Jonathan Zernik Date: Thu, 10 Oct 2019 16:55:12 -0700 Subject: [PATCH 29/82] Fix error message for invalid P2WSH scriptPubKey. --- bitcoin/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index e0be96ee..8df2c22f 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -323,7 +323,7 @@ def from_scriptPubKey(cls, scriptPubKey): if scriptPubKey.is_witness_v0_keyhash(): return cls.from_bytes(0, scriptPubKey[2:22]) else: - raise CBitcoinAddressError('not a P2WSH scriptPubKey') + raise CBitcoinAddressError('not a P2WPKH scriptPubKey') def to_scriptPubKey(self): """Convert an address to a scriptPubKey""" From 6e9e31e7c380c2b8e666f81b29c29d1c47306db8 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 9 Jan 2020 10:38:14 -0600 Subject: [PATCH 30/82] fix typo: P2WSH -> P2WPKH --- bitcoin/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index 8df2c22f..d78e136e 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -315,7 +315,7 @@ class P2WPKHBitcoinAddress(CBech32BitcoinAddress): @classmethod def from_scriptPubKey(cls, scriptPubKey): - """Convert a scriptPubKey to a P2WSH address + """Convert a scriptPubKey to a P2WPKH address Raises CBitcoinAddressError if the scriptPubKey isn't of the correct form. From 854b632f157c7bf7cac96d5e37e3736d31bf6fb9 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 19 Jan 2020 15:18:18 -0600 Subject: [PATCH 31/82] Add OP_CHECKSEQUENCEVERIFY (OP_NOP3) See bip112: https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki --- bitcoin/core/script.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index 26a7a32d..46b83bd7 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -235,6 +235,7 @@ def __new__(cls, n): OP_NOP2 = CScriptOp(0xb1) OP_CHECKLOCKTIMEVERIFY = OP_NOP2 OP_NOP3 = CScriptOp(0xb2) +OP_CHECKSEQUENCEVERIFY = OP_NOP3 OP_NOP4 = CScriptOp(0xb3) OP_NOP5 = CScriptOp(0xb4) OP_NOP6 = CScriptOp(0xb5) @@ -357,6 +358,7 @@ def __new__(cls, n): OP_NOP2: 'OP_NOP2', OP_CHECKLOCKTIMEVERIFY: 'OP_CHECKLOCKTIMEVERIFY', OP_NOP3: 'OP_NOP3', + OP_CHECKSEQUENCEVERIFY: 'OP_CHECKSEQUENCEVERIFY', OP_NOP4: 'OP_NOP4', OP_NOP5: 'OP_NOP5', OP_NOP6: 'OP_NOP6', @@ -477,6 +479,7 @@ def __new__(cls, n): 'OP_NOP2': OP_NOP2, 'OP_CHECKLOCKTIMEVERIFY': OP_CHECKLOCKTIMEVERIFY, 'OP_NOP3': OP_NOP3, + 'OP_CHECKSEQUENCEVERIFY': OP_CHECKSEQUENCEVERIFY, 'OP_NOP4': OP_NOP4, 'OP_NOP5': OP_NOP5, 'OP_NOP6': OP_NOP6, @@ -1152,6 +1155,7 @@ def SignatureHash(script, txTo, inIdx, hashtype, amount=None, sigversion=SIGVERS 'OP_NOP2', 'OP_CHECKLOCKTIMEVERIFY', 'OP_NOP3', + 'OP_CHECKSEQUENCEVERIFY', 'OP_NOP4', 'OP_NOP5', 'OP_NOP6', From df646addf2d5f924dfab4585b77ef1ec09bf5e16 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 19 Jan 2020 23:01:35 -0600 Subject: [PATCH 32/82] [examples] Add segwit example for spending a P2WPKH output --- examples/spend-p2wpkh.py | 108 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100755 examples/spend-p2wpkh.py diff --git a/examples/spend-p2wpkh.py b/examples/spend-p2wpkh.py new file mode 100755 index 00000000..9992d388 --- /dev/null +++ b/examples/spend-p2wpkh.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +"""Low-level example of how to spend a P2WPKH output.""" + +import sys +if sys.version_info.major < 3: + sys.stderr.write("Sorry, Python 3.x required by this example.\n") + sys.exit(1) + +import hashlib + +from bitcoin import SelectParams +from bitcoin.core import b2x, b2lx, lx, COIN, COutPoint, CTxOut, CTxIn, CTxInWitness, CTxWitness, CScriptWitness, CMutableTransaction, Hash160 +from bitcoin.core.script import CScript, OP_0, SignatureHash, SIGHASH_ALL, SIGVERSION_WITNESS_V0 +from bitcoin.wallet import CBitcoinSecret, P2WPKHBitcoinAddress +from bitcoin.rpc import Proxy + +SelectParams("regtest") +connection = Proxy() + +if connection._call("getblockchaininfo")["chain"] != "regtest": + sys.stderr.write("This example is intended for regtest only.\n") + sys.exit(1) + + +# Create the (in)famous correct brainwallet secret key. +h = hashlib.sha256(b'correct horse battery staple').digest() +seckey = CBitcoinSecret.from_secret_bytes(h) + +# Create an address from that private key. +public_key = seckey.pub +scriptPubKey = CScript([OP_0, Hash160(public_key)]) +address = P2WPKHBitcoinAddress.from_scriptPubKey(scriptPubKey) + +# Give the private key to bitcoind (for ismine, listunspent, etc). +connection._call("importprivkey", str(seckey)) + +# Check if there's any funds available. +unspentness = lambda: connection._call("listunspent", 6, 9999, [str(address)], True, {"minimumAmount": 1.0}) +unspents = unspentness() +while len(unspents) == 0: + # mine some funds into the address + connection._call("generatetoaddress", 110, str(address)) + unspents = unspentness() + +# Choose the first UTXO, let's spend it! +unspent_utxo_details = unspents[0] +txid = unspent_utxo_details["txid"] +vout = unspent_utxo_details["vout"] +amount = int(float(unspent_utxo_details["amount"]) * COIN) + +# Calculate an amount for the upcoming new UTXO. Set a high fee to bypass +# bitcoind minfee setting. +amount_less_fee = int(amount - (0.01 * COIN)) + +# Create a destination to send the coins. +destination_address = connection._call("getnewaddress", "python-bitcoinlib-example", "bech32") +destination_address = P2WPKHBitcoinAddress(destination_address) +target_scriptPubKey = destination_address.to_scriptPubKey() + +# Create the unsigned transaction. +txin = CTxIn(COutPoint(lx(txid), vout)) +txout = CTxOut(amount_less_fee, target_scriptPubKey) +tx = CMutableTransaction([txin], [txout]) + +# Specify which transaction input is going to be signed for. +txin_index = 0 + +# When signing a P2WPKH transaction, use an "implicit" script that isn't +# specified in the scriptPubKey or the witness. +redeem_script = address.to_redeemScript() + +# Calculate the signature hash for the transaction. This is then signed by the +# private key that controls the UTXO being spent here at this txin_index. +sighash = SignatureHash(redeem_script, tx, txin_index, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) +signature = seckey.sign(sighash) + bytes([SIGHASH_ALL]) + +# Construct a witness for this transaction input. The public key is given in +# the witness so that the appropriate redeem_script can be calculated by +# anyone. The original scriptPubKey had only the Hash160 hash of the public +# key, not the public key itself, and the redeem script can be entirely +# re-constructed (from implicit template) if given just the public key. So the +# public key is added to the witness. This is P2WPKH in bip141. +witness = [signature, public_key] + +# Aggregate all of the witnesses together, and then assign them to the +# transaction object. +ctxinwitnesses = [CTxInWitness(CScriptWitness(witness))] +tx.wit = CTxWitness(ctxinwitnesses) + +# Broadcast the transaction to the regtest network. +spend_txid = connection.sendrawtransaction(tx) + +# Done! Print the transaction to standard output. Show the transaction +# serialization in hex (instead of bytes), and render the txid. +print("serialized transaction: {}".format(b2x(tx.serialize()))) +print("txid: {}".format(b2lx(spend_txid))) From aa18a92553cacb2aeaeb94995c92e36b30218e7d Mon Sep 17 00:00:00 2001 From: Wesley Kerfoot <378351+weskerfoot@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:46:33 -0500 Subject: [PATCH 33/82] Fix incorrect use of NotImplementedError --- bitcoin/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index 8df2c22f..f5663f39 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -308,7 +308,7 @@ def to_scriptPubKey(self): return script.CScript([0, self]) def to_redeemScript(self): - return NotImplementedError("not enough data in p2wsh address to reconstruct redeem script") + raise NotImplementedError("Not enough data in p2wsh address to reconstruct redeem script") class P2WPKHBitcoinAddress(CBech32BitcoinAddress): From 049e04c51c282523a95e49e54862c32dab71fadb Mon Sep 17 00:00:00 2001 From: JSwambo Date: Fri, 6 Mar 2020 14:53:02 +0000 Subject: [PATCH 34/82] [examples] Added example for spending from P2WSH output. --- examples/spend-p2wsh-txout.py | 91 +++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 examples/spend-p2wsh-txout.py diff --git a/examples/spend-p2wsh-txout.py b/examples/spend-p2wsh-txout.py new file mode 100644 index 00000000..df1044d1 --- /dev/null +++ b/examples/spend-p2wsh-txout.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2014 The python-bitcoinlib developers +# +# This file is part of python-bitcoinlib. +# +# It is subject to the license terms in the LICENSE file found in the top-level +# directory of this distribution. +# +# No part of python-bitcoinlib, including this file, may be copied, modified, +# propagated, or distributed except according to the terms contained in the +# LICENSE file. + +"""Low-level example of how to spend a P2WSH/BIP141 txout""" + +import sys +if sys.version_info.major < 3: + sys.stderr.write('Sorry, Python 3.x required by this example.\n') + sys.exit(1) + +import hashlib + +from bitcoin import SelectParams +from bitcoin.core import b2x, lx, COIN, COutPoint, CMutableTxOut, CMutableTxIn, CMutableTransaction, CTxInWitness, CTxWitness +from bitcoin.core.script import CScript, CScriptWitness, OP_0, OP_CHECKSIG, SignatureHash, SIGHASH_ALL, SIGVERSION_WITNESS_V0 +from bitcoin.wallet import CBitcoinSecret, CBitcoinAddress, P2WSHBitcoinAddress + +SelectParams('testnet') + +# Create the (in)famous correct brainwallet secret key. +h = hashlib.sha256(b'correct horse battery staple').digest() +seckey = CBitcoinSecret.from_secret_bytes(h) + +# Create a witnessScript and corresponding redeemScript. Similar to a scriptPubKey +# the redeemScript must be satisfied for the funds to be spent. +txin_witnessScript = CScript([seckey.pub, OP_CHECKSIG]) +txin_scriptHash = hashlib.sha256(txin_witnessScript).digest() +txin_redeemScript = CScript([OP_0, txin_scriptHash]) + + +# Convert the P2WSH scriptPubKey to a base58 Bitcoin address and print it. +# You'll need to send some funds to it to create a txout to spend. +txin_p2wsh_address = P2WSHBitcoinAddress.from_scriptPubKey(txin_redeemScript) +print('Pay to:', str(txin_p2wsh_address)) + +# Same as the txid:vout the createrawtransaction RPC call requires +# lx() takes *little-endian* hex and converts it to bytes; in Bitcoin +# transaction hashes are shown little-endian rather than the usual big-endian. +txid = lx('ace9dc7c987a52266e38fe8544c2d12182401341c98d151f4b394cf69aa5c3e5') +vout = 0 + +# Specify the amount send to your P2WSH address. +amount = int(0.0001 * COIN) + +# Calculate an amount for the upcoming new UTXO. Set a high fee (5%) to bypass +# bitcoind minfee setting. +amount_less_fee = amount * 0.95 + +# Create the txin structure, which includes the outpoint. The scriptSig +# defaults to being empty as is necessary for spending a P2WSH output. +txin = CMutableTxIn(COutPoint(txid, vout)) + + +# Specify a destination address and create the txout. +destination_address = CBitcoinAddress( + '2NGZrVvZG92qGYqzTLjCAewvPZ7JE8S8VxE').to_scriptPubKey() +txout = CMutableTxOut(amount_less_fee, destination_address) + +# Create the unsigned transaction. +tx = CMutableTransaction([txin], [txout]) + +# Calculate the signature hash for that transaction. Note how the script we use +# is the witnessScript, not the redeemScript. +sighash = SignatureHash(script=txin_witnessScript, txTo=tx, inIdx=0, + hashtype=SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) + +# Now sign it. We have to append the type of signature we want to the end, in +# this case the usual SIGHASH_ALL. +sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) + + +# # Construct a witness for this P2WSH transaction and add to tx. +witness = CScriptWitness([sig, txin_witnessScript]) +tx.wit = CTxWitness([CTxInWitness(witness)]) + +# TODO: upgrade VerifyScript to support Segregated Witness and place verify the witness program here. + +# Done! Print the transaction to standard output with the bytes-to-hex +# function. +print(b2x(tx.serialize())) + From 3fa0a4480d17dbd0e11c704fecdda1496d3125a1 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Fri, 6 Mar 2020 16:00:05 -0600 Subject: [PATCH 35/82] Release v0.11.0 --- bitcoin/__init__.py | 2 +- release-notes.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 24e7523b..5bba82fe 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -16,7 +16,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.0dev' +__version__ = '0.11.0' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 6313ad44..8e1c303f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,14 @@ # python-bitcoinlib release notes +## v0.11.0 + +* Bech32 implementation +* Segwit implementation (for P2WSH and P2WPKH transactions) and examples +* Use libsecp256k1 for signing +* Implement OP_CHECKSEQUENCEVERIFY + +New maintainer: Bryan Bishop + ## v0.10.2 Note: this will be the last release of python-bitcoinlib with Python 2.7 From 78aaaf6ed9a165c631e8858de036e433f5256f49 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Fri, 6 Mar 2020 16:18:51 -0600 Subject: [PATCH 36/82] Reset version for future development --- bitcoin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 5bba82fe..841b42b9 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -16,7 +16,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.0' +__version__ = '0.11.1dev' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' From 4cc440285b5ac4eefa4c9cbf976c22780ac28f86 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Fri, 6 Mar 2020 21:27:23 -0600 Subject: [PATCH 37/82] Remove wrong text in P2WPKH example explanation --- examples/spend-p2wpkh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/spend-p2wpkh.py b/examples/spend-p2wpkh.py index 9992d388..b2d4dd85 100755 --- a/examples/spend-p2wpkh.py +++ b/examples/spend-p2wpkh.py @@ -90,8 +90,8 @@ # the witness so that the appropriate redeem_script can be calculated by # anyone. The original scriptPubKey had only the Hash160 hash of the public # key, not the public key itself, and the redeem script can be entirely -# re-constructed (from implicit template) if given just the public key. So the -# public key is added to the witness. This is P2WPKH in bip141. +# re-constructed if given just the public key. So the public key is added to +# the witness. This is P2WPKH in bip141. witness = [signature, public_key] # Aggregate all of the witnesses together, and then assign them to the From bd3607dac7ba52d145bd9a87bdb7ae36b4a5f0b9 Mon Sep 17 00:00:00 2001 From: JSwambo Date: Mon, 9 Mar 2020 13:18:39 +0000 Subject: [PATCH 38/82] Fix file location of .cookie when on testnet --- bitcoin/rpc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 51f24ac9..4ff8fdd2 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -170,7 +170,9 @@ def __init__(self, ('http', conf['rpchost'], conf['rpcport'])) cookie_dir = conf.get('datadir', os.path.dirname(btc_conf_file)) - if bitcoin.params.NAME != "mainnet": + if bitcoin.params.NAME == 'testnet': + cookie_dir = os.path.join(cookie_dir, 'testnet3') + elif bitcoin.params.NAME == 'regtest': cookie_dir = os.path.join(cookie_dir, bitcoin.params.NAME) cookie_file = os.path.join(cookie_dir, ".cookie") try: From 9424551f822502671ffbe3811ed20585fad6bdcb Mon Sep 17 00:00:00 2001 From: Alexey Zagarin Date: Mon, 13 Jul 2020 10:25:51 +0700 Subject: [PATCH 39/82] Fix compatibility with macOS Catalina Closes #238. --- bitcoin/core/key.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index bbfa506f..37615e51 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -31,7 +31,9 @@ import bitcoin.core.script -_ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') +_ssl = ctypes.cdll.LoadLibrary( + ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or 'libeay32' +) _libsecp256k1_path = ctypes.util.find_library('secp256k1') _libsecp256k1_enable_signing = False From 1c36e7556e2f143f8ad6c5f65805d4e228e398c4 Mon Sep 17 00:00:00 2001 From: Bob McElrath Date: Wed, 23 Sep 2020 12:30:25 -0400 Subject: [PATCH 40/82] Check that secrets are exactly 32 bytes --- bitcoin/core/key.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 37615e51..04e4913a 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -264,6 +264,8 @@ def __del__(self): self.k = None def set_secretbytes(self, secret): + if(len(secret) != 32): + raise ValueError("Secret bytes must be exactly 32 bytes") priv_key = _ssl.BN_bin2bn(secret, 32, None) group = _ssl.EC_KEY_get0_group(self.k) pub_key = _ssl.EC_POINT_new(group) From 0d1960e986e874fcc012b8ebe6424c416b8d8b5c Mon Sep 17 00:00:00 2001 From: Janus Date: Mon, 12 Oct 2020 20:29:28 -0500 Subject: [PATCH 41/82] Remove attempted Python 2 compatibility This hacky approach precludes Mypy type checking. Python 2 is not supported any more, and Tox is not running the test suite in this version either. --- bitcoin/__init__.py | 1 - bitcoin/base58.py | 18 ++--------- bitcoin/bech32.py | 8 ----- bitcoin/bloom.py | 18 +++-------- bitcoin/core/__init__.py | 48 ++---------------------------- bitcoin/core/_bignum.py | 1 - bitcoin/core/key.py | 18 ++--------- bitcoin/core/script.py | 46 +++++++++++----------------- bitcoin/core/scripteval.py | 33 ++++++++------------ bitcoin/core/serialize.py | 31 +++++++------------ bitcoin/messages.py | 18 ++++------- bitcoin/net.py | 1 - bitcoin/rpc.py | 28 +++++------------ bitcoin/segwit_addr.py | 9 ------ bitcoin/signature.py | 8 ++--- bitcoin/signmessage.py | 11 +------ bitcoin/tests/__init__.py | 1 - bitcoin/tests/test_base58.py | 1 - bitcoin/tests/test_bech32.py | 18 ++--------- bitcoin/tests/test_bloom.py | 1 - bitcoin/tests/test_checkblock.py | 1 - bitcoin/tests/test_core.py | 1 - bitcoin/tests/test_key.py | 1 - bitcoin/tests/test_messages.py | 6 +--- bitcoin/tests/test_net.py | 20 +++++-------- bitcoin/tests/test_rpc.py | 1 - bitcoin/tests/test_script.py | 1 - bitcoin/tests/test_scripteval.py | 7 +---- bitcoin/tests/test_segwit.py | 8 +---- bitcoin/tests/test_serialize.py | 1 - bitcoin/tests/test_signmessage.py | 9 ------ bitcoin/tests/test_transactions.py | 1 - bitcoin/tests/test_wallet.py | 1 - bitcoin/wallet.py | 32 +++++++------------- examples/make-bootstrap-rpc.py | 5 ---- examples/msg-serializable.py | 5 ---- examples/publish-text.py | 6 ---- examples/spend-p2pkh-txout.py | 5 ---- examples/spend-p2sh-txout.py | 5 ---- examples/spend-p2wpkh.py | 3 -- examples/spend-p2wsh-txout.py | 5 ---- examples/timestamp-op-ret.py | 6 ---- 42 files changed, 93 insertions(+), 355 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 841b42b9..e4eaceac 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import bitcoin.core diff --git a/bitcoin/base58.py b/bitcoin/base58.py index 4253240e..18956223 100644 --- a/bitcoin/base58.py +++ b/bitcoin/base58.py @@ -12,15 +12,6 @@ """Base58 encoding and decoding""" -from __future__ import absolute_import, division, print_function, unicode_literals - -import sys -_bchr = chr -_bord = ord -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x import binascii @@ -52,10 +43,7 @@ def encode(b): res = ''.join(res[::-1]) # Encode leading zeros as base58 zeros - czero = b'\x00' - if sys.version > '3': - # In Python3 indexing a bytes returns numbers, not characters. - czero = 0 + czero = 0 pad = 0 for c in b: if c == czero: @@ -108,7 +96,7 @@ def __new__(cls, s): if check0 != check1: raise Base58ChecksumError('Checksum mismatch: expected %r, calculated %r' % (check0, check1)) - return cls.from_bytes(data, _bord(verbyte[0])) + return cls.from_bytes(data, verbyte[0]) def __init__(self, s): """Initialize from base58-encoded string @@ -138,7 +126,7 @@ def to_bytes(self): def __str__(self): """Convert to string""" - vs = _bchr(self.nVersion) + self + vs = bytes([self.nVersion]) + self check = bitcoin.core.Hash(vs)[0:4] return encode(vs + check) diff --git a/bitcoin/bech32.py b/bitcoin/bech32.py index fe95c70c..f140b0ae 100644 --- a/bitcoin/bech32.py +++ b/bitcoin/bech32.py @@ -11,14 +11,6 @@ """Bech32 encoding and decoding""" -import sys -_bchr = chr -_bord = ord -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - from bitcoin.segwit_addr import encode, decode import bitcoin diff --git a/bitcoin/bloom.py b/bitcoin/bloom.py index f74980cf..50073dc3 100644 --- a/bitcoin/bloom.py +++ b/bitcoin/bloom.py @@ -11,10 +11,8 @@ """Bloom filter support""" -from __future__ import absolute_import, division, print_function, unicode_literals import struct -import sys import math import bitcoin.core @@ -56,16 +54,12 @@ def MurmurHash3(nHashSeed, vDataToHash): # tail k1 = 0 j = (len(vDataToHash) // 4) * 4 - bord = ord - if sys.version > '3': - # In Py3 indexing bytes returns numbers, not characters - bord = lambda x: x if len(vDataToHash) & 3 >= 3: - k1 ^= bord(vDataToHash[j+2]) << 16 + k1 ^= vDataToHash[j+2] << 16 if len(vDataToHash) & 3 >= 2: - k1 ^= bord(vDataToHash[j+1]) << 8 + k1 ^= vDataToHash[j+1] << 8 if len(vDataToHash) & 3 >= 1: - k1 ^= bord(vDataToHash[j]) + k1 ^= vDataToHash[j] k1 &= 0xFFFFFFFF k1 = (k1 * c1) & 0xFFFFFFFF @@ -180,11 +174,7 @@ def stream_deserialize(cls, f): return deserialized def stream_serialize(self, f): - if sys.version > '3': - bitcoin.core.serialize.BytesSerializer.stream_serialize(self.vData, f) - else: - # 2.7 has problems with f.write(bytearray()) - bitcoin.core.serialize.BytesSerializer.stream_serialize(bytes(self.vData), f) + bitcoin.core.serialize.BytesSerializer.stream_serialize(self.vData, f) f.write(self.__struct.pack(self.nHashFuncs, self.nTweak, self.nFlags)) __all__ = ( diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 272bec5c..08961edc 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -9,28 +9,22 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function import binascii import struct -import sys import time -from .script import CScript, CScriptWitness, CScriptOp, OP_RETURN +from . import script +from .script import CScript, CScriptWitness, OP_RETURN from .serialize import * -if sys.version > '3': - _bytes = bytes -else: - _bytes = lambda x: bytes(bytearray(x)) - # Core definitions COIN = 100000000 MAX_BLOCK_SIZE = 1000000 MAX_BLOCK_WEIGHT = 4000000 MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50 -WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC = _bytes([OP_RETURN, 0x24, 0xaa, 0x21, 0xa9, 0xed]) +WITNESS_COINBASE_SCRIPTPUBKEY_MAGIC = bytes([OP_RETURN, 0x24, 0xaa, 0x21, 0xa9, 0xed]) def MoneyRange(nValue, params=None): global coreparams @@ -39,30 +33,14 @@ def MoneyRange(nValue, params=None): return 0 <= nValue <= params.MAX_MONEY -def _py2_x(h): - """Convert a hex string to bytes""" - return binascii.unhexlify(h) - def x(h): """Convert a hex string to bytes""" return binascii.unhexlify(h.encode('utf8')) -def _py2_b2x(b): - """Convert bytes to a hex string""" - return binascii.hexlify(b) - def b2x(b): """Convert bytes to a hex string""" return binascii.hexlify(b).decode('utf8') -def _py2_lx(h): - """Convert a little-endian hex string to bytes - - Lets you write uint256's and uint160's the way the Satoshi codebase shows - them. - """ - return binascii.unhexlify(h)[::-1] - def lx(h): """Convert a little-endian hex string to bytes @@ -71,14 +49,6 @@ def lx(h): """ return binascii.unhexlify(h.encode('utf8'))[::-1] -def _py2_b2lx(b): - """Convert bytes to a little-endian hex string - - Lets you show uint256's and uint160's the way the Satoshi codebase shows - them. - """ - return binascii.hexlify(b[::-1]) - def b2lx(b): """Convert bytes to a little-endian hex string @@ -87,18 +57,6 @@ def b2lx(b): """ return binascii.hexlify(b[::-1]).decode('utf8') -if not (sys.version > '3'): - x = _py2_x - b2x = _py2_b2x - lx = _py2_lx - b2lx = _py2_b2lx - -del _py2_x -del _py2_b2x -del _py2_lx -del _py2_b2lx - - def str_money_value(value): """Convert an integer money value to a fixed point string""" r = '%i.%08i' % (value // COIN, value % COIN) diff --git a/bitcoin/core/_bignum.py b/bitcoin/core/_bignum.py index 42985a7f..6edd7065 100644 --- a/bitcoin/core/_bignum.py +++ b/bitcoin/core/_bignum.py @@ -13,7 +13,6 @@ # # Internally used for script evaluation; not to be used externally. -from __future__ import absolute_import, division, print_function, unicode_literals import struct diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 37615e51..daee9a40 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -18,17 +18,10 @@ import ctypes import ctypes.util import hashlib -import sys from os import urandom import bitcoin import bitcoin.signature -_bchr = chr -_bord = ord -if sys.version > '3': - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - import bitcoin.core.script _ssl = ctypes.cdll.LoadLibrary( @@ -587,8 +580,8 @@ def recover_compact(cls, hash, sig): # pylint: disable=redefined-builtin if len(sig) != 65: raise ValueError("Signature should be 65 characters, not [%d]" % (len(sig), )) - recid = (_bord(sig[0]) - 27) & 3 - compressed = (_bord(sig[0]) - 27) & 4 != 0 + recid = (sig[0] - 27) & 3 + compressed = (sig[0] - 27) & 4 != 0 cec_key = CECKey() cec_key.set_compressed(compressed) @@ -620,12 +613,7 @@ def __str__(self): return repr(self) def __repr__(self): - # Always have represent as b'' so test cases don't have to - # change for py2/3 - if sys.version > '3': - return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) - else: - return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) + return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) __all__ = ( 'CECKey', diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index 46b83bd7..aa2cff87 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -15,18 +15,8 @@ is in bitcoin.core.scripteval """ -from __future__ import absolute_import, division, print_function - -import sys -_bchr = chr -_bord = ord -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - from io import BytesIO as _BytesIO -else: - from cStringIO import StringIO as _BytesIO + +from io import BytesIO import struct @@ -50,9 +40,9 @@ class CScriptOp(int): def encode_op_pushdata(d): """Encode a PUSHDATA op, returning bytes""" if len(d) < 0x4c: - return b'' + _bchr(len(d)) + d # OP_PUSHDATA + return bytes([len(d)]) + d # OP_PUSHDATA elif len(d) <= 0xff: - return b'\x4c' + _bchr(len(d)) + d # OP_PUSHDATA1 + return b'\x4c' + bytes([len(d)]) + d # OP_PUSHDATA1 elif len(d) <= 0xffff: return b'\x4d' + struct.pack(b' OP_PUSHDATA4: @@ -588,21 +578,21 @@ def raw_iter(self): pushdata_type = 'PUSHDATA1' if i >= len(self): raise CScriptInvalidError('PUSHDATA1: missing data length') - datasize = _bord(self[i]) + datasize = self[i] i += 1 elif opcode == OP_PUSHDATA2: pushdata_type = 'PUSHDATA2' if i + 1 >= len(self): raise CScriptInvalidError('PUSHDATA2: missing data length') - datasize = _bord(self[i]) + (_bord(self[i+1]) << 8) + datasize = self[i] + (self[i+1] << 8) i += 2 elif opcode == OP_PUSHDATA4: pushdata_type = 'PUSHDATA4' if i + 3 >= len(self): raise CScriptInvalidError('PUSHDATA4: missing data length') - datasize = _bord(self[i]) + (_bord(self[i+1]) << 8) + (_bord(self[i+2]) << 16) + (_bord(self[i+3]) << 24) + datasize = self[i] + (self[i+1] << 8) + (self[i+2] << 16) + (self[i+3] << 24) i += 4 else: @@ -676,9 +666,9 @@ def is_p2sh(self): Note that this test is consensus-critical. """ return (len(self) == 23 and - _bord(self[0]) == OP_HASH160 and - _bord(self[1]) == 0x14 and - _bord(self[22]) == OP_EQUAL) + self[0] == OP_HASH160 and + self[1] == 0x14 and + self[22] == OP_EQUAL) def is_witness_scriptpubkey(self): """Returns true if this is a scriptpubkey signaling segregated witness data. @@ -747,7 +737,7 @@ def has_canonical_pushes(self): if op > OP_16: continue - elif op < OP_PUSHDATA1 and op > OP_0 and len(data) == 1 and _bord(data[0]) <= 16: + elif op < OP_PUSHDATA1 and op > OP_0 and len(data) == 1 and data[0] <= 16: # Could have used an OP_n code, rather than a 1-byte push. return False @@ -770,7 +760,7 @@ def has_canonical_pushes(self): def is_unspendable(self): """Test if the script is provably unspendable""" return (len(self) > 0 and - _bord(self[0]) == OP_RETURN) + self[0] == OP_RETURN) def is_valid(self): """Return True if the script is valid, False otherwise @@ -1018,7 +1008,7 @@ def SignatureHash(script, txTo, inIdx, hashtype, amount=None, sigversion=SIGVERS serialize_outputs = txTo.vout[inIdx].serialize() hashOutputs = bitcoin.core.Hash(serialize_outputs) - f = _BytesIO() + f = BytesIO() f.write(struct.pack(" '3': - long = int - _bord = lambda x: x import hashlib @@ -122,7 +115,7 @@ def _CastToBigNum(s, err_raiser): def _CastToBool(s): for i in range(len(s)): - sv = _bord(s[i]) + sv = s[i] if sv != 0: if (i == (len(s) - 1)) and (sv == 0x80): return False @@ -137,7 +130,7 @@ def _CheckSig(sig, pubkey, script, txTo, inIdx, err_raiser): if len(sig) == 0: return False - hashtype = _bord(sig[-1]) + hashtype = sig[-1] sig = sig[:-1] # Raw signature hash due to the SIGHASH_SINGLE bug @@ -260,10 +253,10 @@ def _UnaryOp(opcode, stack, err_raiser): bn = -bn elif opcode == OP_NOT: - bn = long(bn == 0) + bn = int(bn == 0) elif opcode == OP_0NOTEQUAL: - bn = long(bn != 0) + bn = int(bn != 0) else: raise AssertionError("Unknown unary opcode encountered; this should not happen") @@ -305,16 +298,16 @@ def _BinOp(opcode, stack, err_raiser): bn = bn1 - bn2 elif opcode == OP_BOOLAND: - bn = long(bn1 != 0 and bn2 != 0) + bn = int(bn1 != 0 and bn2 != 0) elif opcode == OP_BOOLOR: - bn = long(bn1 != 0 or bn2 != 0) + bn = int(bn1 != 0 or bn2 != 0) elif opcode == OP_NUMEQUAL: - bn = long(bn1 == bn2) + bn = int(bn1 == bn2) elif opcode == OP_NUMEQUALVERIFY: - bn = long(bn1 == bn2) + bn = int(bn1 == bn2) if not bn: err_raiser(VerifyOpFailedError, opcode) else: @@ -324,19 +317,19 @@ def _BinOp(opcode, stack, err_raiser): return elif opcode == OP_NUMNOTEQUAL: - bn = long(bn1 != bn2) + bn = int(bn1 != bn2) elif opcode == OP_LESSTHAN: - bn = long(bn1 < bn2) + bn = int(bn1 < bn2) elif opcode == OP_GREATERTHAN: - bn = long(bn1 > bn2) + bn = int(bn1 > bn2) elif opcode == OP_LESSTHANOREQUAL: - bn = long(bn1 <= bn2) + bn = int(bn1 <= bn2) elif opcode == OP_GREATERTHANOREQUAL: - bn = long(bn1 >= bn2) + bn = int(bn1 >= bn2) elif opcode == OP_MIN: if bn1 < bn2: diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index c89b73e4..f1346b92 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -14,22 +14,11 @@ You probabably don't need to use these directly. """ -from __future__ import absolute_import, division, print_function, unicode_literals import hashlib import struct -# Py3 compatibility -import sys - -if sys.version > '3': - _bchr = lambda x: bytes([x]) - _bord = lambda x: x[0] - from io import BytesIO as _BytesIO -else: - _bchr = chr - _bord = ord - from cStringIO import StringIO as _BytesIO +from io import BytesIO MAX_SIZE = 0x02000000 @@ -98,7 +87,7 @@ def stream_deserialize(cls, f, **kwargs): def serialize(self, params={}): """Serialize, returning bytes""" - f = _BytesIO() + f = BytesIO() self.stream_serialize(f, **params) return f.getvalue() @@ -111,7 +100,7 @@ def deserialize(cls, buf, allow_padding=False, params={}): If allow_padding is False and not all bytes are consumed during deserialization DeserializationExtraDataError will be raised. """ - fd = _BytesIO(buf) + fd = BytesIO(buf) r = cls.stream_deserialize(fd, **params) if not allow_padding: padding = fd.read() @@ -179,14 +168,14 @@ def stream_deserialize(cls, f): @classmethod def serialize(cls, obj): - f = _BytesIO() + f = BytesIO() cls.stream_serialize(obj, f) return f.getvalue() @classmethod def deserialize(cls, buf): if isinstance(buf, str) or isinstance(buf, bytes): - buf = _BytesIO(buf) + buf = BytesIO(buf) return cls.stream_deserialize(buf) @@ -197,20 +186,20 @@ def stream_serialize(cls, i, f): if i < 0: raise ValueError('varint must be non-negative integer') elif i < 0xfd: - f.write(_bchr(i)) + f.write(bytes([i])) elif i <= 0xffff: - f.write(_bchr(0xfd)) + f.write(b'\xfd') f.write(struct.pack(b' '3': - _bchr = lambda x: bytes([x]) - _bord = lambda x: x[0] - from io import BytesIO as _BytesIO -else: - _bchr = chr - _bord = ord - from cStringIO import StringIO as _BytesIO +from io import BytesIO # Bad practice, so we have a __all__ at the end; this should be cleaned up # later. @@ -59,7 +51,7 @@ def msg_deser(cls, f, protover=PROTO_VERSION): raise NotImplementedError def to_bytes(self): - f = _BytesIO() + f = BytesIO() self.msg_ser(f) body = f.getvalue() res = bitcoin.params.MESSAGE_START @@ -77,7 +69,7 @@ def to_bytes(self): @classmethod def from_bytes(cls, b, protover=PROTO_VERSION): - f = _BytesIO(b) + f = BytesIO(b) return MsgSerializable.stream_deserialize(f, protover=protover) @classmethod @@ -107,7 +99,7 @@ def stream_deserialize(cls, f, protover=PROTO_VERSION): if command in messagemap: cls = messagemap[command] # print("Going to deserialize '%s'" % msg) - return cls.msg_deser(_BytesIO(msg)) + return cls.msg_deser(BytesIO(msg)) else: print("Command '%s' not in messagemap" % repr(command)) return None @@ -160,7 +152,7 @@ def msg_deser(cls, f, protover=PROTO_VERSION): else: c.fRelay = True return c - + def msg_ser(self, f): f.write(struct.pack(b" '3': - unhexlify = lambda h: binascii.unhexlify(h.encode('utf8')) - hexlify = lambda b: binascii.hexlify(b).decode('utf8') +unhexlify = lambda h: binascii.unhexlify(h.encode('utf8')) +hexlify = lambda b: binascii.hexlify(b).decode('utf8') class JSONRPCError(Exception): @@ -392,7 +378,7 @@ def fundrawtransaction(self, tx, include_watching=False): def generate(self, numblocks): """ DEPRECATED (will be removed in bitcoin-core v0.19) - + Mine blocks immediately (before the RPC call returns) numblocks - How many blocks are generated immediately. @@ -401,10 +387,10 @@ def generate(self, numblocks): """ r = self._call('generate', numblocks) return (lx(blk_hash) for blk_hash in r) - + def generatetoaddress(self, numblocks, addr): """Mine blocks immediately (before the RPC call returns) and - allocate block reward to passed address. Replaces deprecated + allocate block reward to passed address. Replaces deprecated "generate(self,numblocks)" method. numblocks - How many blocks are generated immediately. diff --git a/bitcoin/segwit_addr.py b/bitcoin/segwit_addr.py index 67d17cff..c762fdd8 100644 --- a/bitcoin/segwit_addr.py +++ b/bitcoin/segwit_addr.py @@ -20,14 +20,6 @@ """Reference implementation for Bech32 and segwit addresses.""" -import sys -_bchr = chr -_bord = lambda x: ord(x) if isinstance(x, str) else x -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" @@ -92,7 +84,6 @@ def convertbits(data, frombits, tobits, pad=True): maxv = (1 << tobits) - 1 max_acc = (1 << (frombits + tobits - 1)) - 1 for value in data: - value = _bord(value) if value < 0 or (value >> frombits): return None acc = ((acc << frombits) | value) & max_acc diff --git a/bitcoin/signature.py b/bitcoin/signature.py index f339491e..fd1d7404 100644 --- a/bitcoin/signature.py +++ b/bitcoin/signature.py @@ -9,17 +9,13 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals from bitcoin.core.serialize import * # Py3 compatibility import sys -if sys.version > '3': - from io import BytesIO as _BytesIO -else: - from cStringIO import StringIO as _BytesIO +from io import BytesIO class DERSignature(ImmutableSerializable): @@ -34,7 +30,7 @@ def __init__(self, r, s, length): def stream_deserialize(cls, f): assert ser_read(f, 1) == b"\x30" rs = BytesSerializer.stream_deserialize(f) - f = _BytesIO(rs) + f = BytesIO(rs) assert ser_read(f, 1) == b"\x02" r = BytesSerializer.stream_deserialize(f) assert ser_read(f, 1) == b"\x02" diff --git a/bitcoin/signmessage.py b/bitcoin/signmessage.py index a62fe8c1..38c20dc8 100644 --- a/bitcoin/signmessage.py +++ b/bitcoin/signmessage.py @@ -9,21 +9,12 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals from bitcoin.core.key import CPubKey from bitcoin.core.serialize import ImmutableSerializable from bitcoin.wallet import P2PKHBitcoinAddress import bitcoin import base64 -import sys - -_bchr = chr -_bord = ord -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x def VerifyMessage(address, message, sig): @@ -42,7 +33,7 @@ def SignMessage(key, message): if key.is_compressed: meta += 4 - return base64.b64encode(_bchr(meta) + sig) + return base64.b64encode(bytes([meta]) + sig) class BitcoinMessage(ImmutableSerializable): diff --git a/bitcoin/tests/__init__.py b/bitcoin/tests/__init__.py index 1d9a3755..770ca60a 100644 --- a/bitcoin/tests/__init__.py +++ b/bitcoin/tests/__init__.py @@ -9,4 +9,3 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals diff --git a/bitcoin/tests/test_base58.py b/bitcoin/tests/test_base58.py index a57d1fe1..74840205 100644 --- a/bitcoin/tests/test_base58.py +++ b/bitcoin/tests/test_base58.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import json import os diff --git a/bitcoin/tests/test_bech32.py b/bitcoin/tests/test_bech32.py index 3efebca5..9d7db073 100644 --- a/bitcoin/tests/test_bech32.py +++ b/bitcoin/tests/test_bech32.py @@ -9,22 +9,10 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import json import os import unittest -import array -import sys -_bchr = chr -_bord = ord -_tobytes = lambda x: array.array('B', x).tostring() -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - _tobytes = bytes - from binascii import unhexlify from bitcoin.core.script import CScript, OP_0, OP_1, OP_16 @@ -39,7 +27,7 @@ def load_test_vectors(name): def to_scriptPubKey(witver, witprog): """Decoded bech32 address to script""" - return CScript([witver]) + CScript(_tobytes(witprog)) + return CScript([witver]) + CScript(bytes(witprog)) class Test_bech32(unittest.TestCase): @@ -53,7 +41,7 @@ def op_decode(self, witver): def test_encode_decode(self): for exp_bin, exp_bech32 in load_test_vectors('bech32_encode_decode.json'): - exp_bin = [_bord(y) for y in unhexlify(exp_bin.encode('utf8'))] + exp_bin = unhexlify(exp_bin.encode('utf8')) witver = self.op_decode(exp_bin[0]) hrp = exp_bech32[:exp_bech32.rindex('1')].lower() self.assertEqual(exp_bin[1], len(exp_bin[2:])) @@ -61,7 +49,7 @@ def test_encode_decode(self): act_bin = decode(hrp, exp_bech32) self.assertEqual(act_bech32.lower(), exp_bech32.lower()) - self.assertEqual(to_scriptPubKey(*act_bin), _tobytes(exp_bin)) + self.assertEqual(to_scriptPubKey(*act_bin), bytes(exp_bin)) class Test_CBech32Data(unittest.TestCase): def test_from_data(self): diff --git a/bitcoin/tests/test_bloom.py b/bitcoin/tests/test_bloom.py index c0d7a019..f637600d 100644 --- a/bitcoin/tests/test_bloom.py +++ b/bitcoin/tests/test_bloom.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest diff --git a/bitcoin/tests/test_checkblock.py b/bitcoin/tests/test_checkblock.py index a011fe26..a2bf9ad0 100644 --- a/bitcoin/tests/test_checkblock.py +++ b/bitcoin/tests/test_checkblock.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import json import unittest diff --git a/bitcoin/tests/test_core.py b/bitcoin/tests/test_core.py index 1d612afd..66ab16dd 100644 --- a/bitcoin/tests/test_core.py +++ b/bitcoin/tests/test_core.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest diff --git a/bitcoin/tests/test_key.py b/bitcoin/tests/test_key.py index a99aa873..2526b948 100644 --- a/bitcoin/tests/test_key.py +++ b/bitcoin/tests/test_key.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest diff --git a/bitcoin/tests/test_messages.py b/bitcoin/tests/test_messages.py index 8b51a126..b5e115dd 100644 --- a/bitcoin/tests/test_messages.py +++ b/bitcoin/tests/test_messages.py @@ -16,11 +16,7 @@ msg_block, msg_getaddr, msg_ping, msg_pong, msg_mempool, MsgSerializable, \ msg_notfound, msg_reject -import sys -if sys.version > '3': - from io import BytesIO -else: - from cStringIO import StringIO as BytesIO +from io import BytesIO class MessageTestCase(unittest.TestCase): diff --git a/bitcoin/tests/test_net.py b/bitcoin/tests/test_net.py index df83856f..80f6d775 100644 --- a/bitcoin/tests/test_net.py +++ b/bitcoin/tests/test_net.py @@ -13,13 +13,7 @@ from bitcoin.net import CAddress, CAlert, CUnsignedAlert, CInv -# Py3 compatibility -import sys - -if sys.version > '3': - from io import BytesIO as _BytesIO -else: - from cStringIO import StringIO as _BytesIO +from io import BytesIO class TestUnsignedAlert(unittest.TestCase): @@ -27,10 +21,10 @@ def test_serialization(self): alert = CUnsignedAlert() alert.setCancel = [1, 2, 3] alert.strComment = b"Comment" - stream = _BytesIO() + stream = BytesIO() alert.stream_serialize(stream) - serialized = _BytesIO(stream.getvalue()) + serialized = BytesIO(stream.getvalue()) deserialized = CUnsignedAlert.stream_deserialize(serialized) self.assertEqual(deserialized, alert) @@ -41,10 +35,10 @@ def test_serialization(self): alert = CAlert() alert.setCancel = [1, 2, 3] alert.strComment = b"Comment" - stream = _BytesIO() + stream = BytesIO() alert.stream_serialize(stream) - serialized = _BytesIO(stream.getvalue()) + serialized = BytesIO(stream.getvalue()) deserialized = CAlert.stream_deserialize(serialized) self.assertEqual(deserialized, alert) @@ -55,10 +49,10 @@ def test_serialization(self): inv = CInv() inv.type = 123 inv.hash = b"0" * 32 - stream = _BytesIO() + stream = BytesIO() inv.stream_serialize(stream) - serialized = _BytesIO(stream.getvalue()) + serialized = BytesIO(stream.getvalue()) deserialized = CInv.stream_deserialize(serialized) self.assertEqual(deserialized, inv) diff --git a/bitcoin/tests/test_rpc.py b/bitcoin/tests/test_rpc.py index 07d1f964..716fb816 100644 --- a/bitcoin/tests/test_rpc.py +++ b/bitcoin/tests/test_rpc.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest diff --git a/bitcoin/tests/test_script.py b/bitcoin/tests/test_script.py index b461e675..ca4598ff 100644 --- a/bitcoin/tests/test_script.py +++ b/bitcoin/tests/test_script.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest import os diff --git a/bitcoin/tests/test_scripteval.py b/bitcoin/tests/test_scripteval.py index ee2ceb96..59f12182 100644 --- a/bitcoin/tests/test_scripteval.py +++ b/bitcoin/tests/test_scripteval.py @@ -9,16 +9,11 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import json import os import unittest -import sys -if sys.version > '3': - long = int - from binascii import unhexlify from bitcoin.core import * @@ -40,7 +35,7 @@ def ishex(s): for word in s.split(): if word.isdigit() or (word[0] == '-' and word[1:].isdigit()): - r.append(CScript([long(word)])) + r.append(CScript([int(word)])) elif word.startswith('0x') and ishex(word[2:]): # Raw ex data, inserted NOT pushed onto stack: r.append(unhexlify(word[2:].encode('utf8'))) diff --git a/bitcoin/tests/test_segwit.py b/bitcoin/tests/test_segwit.py index 9273fded..b2a0e1c8 100644 --- a/bitcoin/tests/test_segwit.py +++ b/bitcoin/tests/test_segwit.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest import random @@ -20,11 +19,6 @@ from bitcoin.core.script import * from bitcoin.wallet import * -if sys.version > '3': - _bytes = bytes -else: - _bytes = lambda x: bytes(bytearray(x)) - # Test serialization @@ -122,7 +116,7 @@ def test_checkblock(self): str_tx = '0100000001e3d0c1d051a3abe79d5951dcab86f71d8926aff5caed5ff9bd72cb593e4ebaf5010000006b483045022100a28e1c57e160296953e1af85c5034bb1b907c12c50367da75ba547874a8a8c52022040682e888ddce7fd5c72519a9f28f22f5164c8af864d33332bbc7f65596ad4ae012102db30394fd5cc8288bed607b9382338240c014a98c9c0febbfb380db74ceb30a3ffffffff020d920000000000001976a914ad781c8ffcc18b2155433cba2da4601180a66eef88aca3170000000000001976a914bacb1c4b0725e61e385c7093b4260533953c8e1688ac00000000' # SegWit transaction str_w_tx = '0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000' - witness_nonce = _bytes(random.getrandbits(8) for _ in range(32)) + witness_nonce = bytes(random.getrandbits(8) for _ in range(32)) coinbase = CMutableTransaction.deserialize(x(str_coinbase)) coinbase.wit = CTxWitness([CTxInWitness(CScriptWitness([witness_nonce]))]) diff --git a/bitcoin/tests/test_serialize.py b/bitcoin/tests/test_serialize.py index a5e45330..cc1056b8 100644 --- a/bitcoin/tests/test_serialize.py +++ b/bitcoin/tests/test_serialize.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest, random diff --git a/bitcoin/tests/test_signmessage.py b/bitcoin/tests/test_signmessage.py index 33dd7ad8..a643f574 100644 --- a/bitcoin/tests/test_signmessage.py +++ b/bitcoin/tests/test_signmessage.py @@ -9,23 +9,14 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import unittest from bitcoin.wallet import CBitcoinSecret from bitcoin.signmessage import BitcoinMessage, VerifyMessage, SignMessage -import sys import os import json -_bchr = chr -_bord = ord -if sys.version > '3': - long = int - _bchr = lambda x: bytes([x]) - _bord = lambda x: x - def load_test_vectors(name): with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd: return json.load(fd) diff --git a/bitcoin/tests/test_transactions.py b/bitcoin/tests/test_transactions.py index f96c5b32..2753e9fe 100644 --- a/bitcoin/tests/test_transactions.py +++ b/bitcoin/tests/test_transactions.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import json import unittest diff --git a/bitcoin/tests/test_wallet.py b/bitcoin/tests/test_wallet.py index ed7b81b5..c178cd51 100644 --- a/bitcoin/tests/test_wallet.py +++ b/bitcoin/tests/test_wallet.py @@ -9,7 +9,6 @@ # propagated, or distributed except according to the terms contained in the # LICENSE file. -from __future__ import absolute_import, division, print_function, unicode_literals import hashlib import unittest diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index 4579d285..eb5eeb98 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -15,16 +15,6 @@ scriptPubKeys; currently there is no actual wallet support implemented. """ -from __future__ import absolute_import, division, print_function, unicode_literals - -import array -import sys - -_bord = ord -_tobytes = lambda x: array.array('B', x).tostring() -if sys.version > '3': - _bord = lambda x: x - _tobytes = bytes import bitcoin import bitcoin.base58 @@ -78,7 +68,7 @@ def from_bytes(cls, witver, witprog): assert witver == 0 self = super(CBech32BitcoinAddress, cls).from_bytes( witver, - _tobytes(witprog) + bytes(witprog) ) if len(self) == 32: @@ -250,11 +240,11 @@ def from_scriptPubKey(cls, scriptPubKey, accept_non_canonical_pushdata=True, acc elif scriptPubKey.is_witness_v0_nested_keyhash(): return cls.from_bytes(scriptPubKey[3:23], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']) elif (len(scriptPubKey) == 25 - and _bord(scriptPubKey[0]) == script.OP_DUP - and _bord(scriptPubKey[1]) == script.OP_HASH160 - and _bord(scriptPubKey[2]) == 0x14 - and _bord(scriptPubKey[23]) == script.OP_EQUALVERIFY - and _bord(scriptPubKey[24]) == script.OP_CHECKSIG): + and scriptPubKey[0] == script.OP_DUP + and scriptPubKey[1] == script.OP_HASH160 + and scriptPubKey[2] == 0x14 + and scriptPubKey[23] == script.OP_EQUALVERIFY + and scriptPubKey[24] == script.OP_CHECKSIG): return cls.from_bytes(scriptPubKey[3:23], bitcoin.params.BASE58_PREFIXES['PUBKEY_ADDR']) elif accept_bare_checksig: @@ -263,14 +253,14 @@ def from_scriptPubKey(cls, scriptPubKey, accept_non_canonical_pushdata=True, acc # We can operate on the raw bytes directly because we've # canonicalized everything above. if (len(scriptPubKey) == 35 # compressed - and _bord(scriptPubKey[0]) == 0x21 - and _bord(scriptPubKey[34]) == script.OP_CHECKSIG): + and scriptPubKey[0] == 0x21 + and scriptPubKey[34] == script.OP_CHECKSIG): pubkey = scriptPubKey[1:34] elif (len(scriptPubKey) == 67 # uncompressed - and _bord(scriptPubKey[0]) == 0x41 - and _bord(scriptPubKey[66]) == script.OP_CHECKSIG): + and scriptPubKey[0] == 0x41 + and scriptPubKey[66] == script.OP_CHECKSIG): pubkey = scriptPubKey[1:65] @@ -379,7 +369,7 @@ def __init__(self, s): raise CBitcoinSecretError('Not a base58-encoded secret key: got nVersion=%d; expected nVersion=%d' % \ (self.nVersion, bitcoin.params.BASE58_PREFIXES['SECRET_KEY'])) - CKey.__init__(self, self[0:32], len(self) > 32 and _bord(self[32]) == 1) + CKey.__init__(self, self[0:32], len(self) > 32 and self[32] == 1) __all__ = ( diff --git a/examples/make-bootstrap-rpc.py b/examples/make-bootstrap-rpc.py index d2e15ce7..4279cc28 100755 --- a/examples/make-bootstrap-rpc.py +++ b/examples/make-bootstrap-rpc.py @@ -13,11 +13,6 @@ """Make a boostrap.dat file by getting the blocks from the RPC interface.""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import bitcoin import bitcoin.rpc diff --git a/examples/msg-serializable.py b/examples/msg-serializable.py index ae6004b9..b3c5aa69 100755 --- a/examples/msg-serializable.py +++ b/examples/msg-serializable.py @@ -13,11 +13,6 @@ """Serialize some bitcoin datastructures and show them in serialized and repr form.""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - from bitcoin import SelectParams from bitcoin.messages import msg_version, msg_tx, msg_block diff --git a/examples/publish-text.py b/examples/publish-text.py index 5c0cf504..afb15c16 100755 --- a/examples/publish-text.py +++ b/examples/publish-text.py @@ -28,13 +28,7 @@ # # https://github.com/petertodd/python-bitcoinlib/commit/6a0a2b9429edea318bea7b65a68a950cae536790 -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import argparse -import hashlib import logging import sys import os diff --git a/examples/spend-p2pkh-txout.py b/examples/spend-p2pkh-txout.py index 7da294a6..da2fd818 100755 --- a/examples/spend-p2pkh-txout.py +++ b/examples/spend-p2pkh-txout.py @@ -13,11 +13,6 @@ """Low-level example of how to spend a standard pay-to-pubkey-hash (P2PKH) txout""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import hashlib from bitcoin import SelectParams diff --git a/examples/spend-p2sh-txout.py b/examples/spend-p2sh-txout.py index 9f0215b0..b28396ec 100755 --- a/examples/spend-p2sh-txout.py +++ b/examples/spend-p2sh-txout.py @@ -13,11 +13,6 @@ """Low-level example of how to spend a P2SH/BIP16 txout""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import hashlib from bitcoin import SelectParams diff --git a/examples/spend-p2wpkh.py b/examples/spend-p2wpkh.py index 9992d388..9e499b73 100755 --- a/examples/spend-p2wpkh.py +++ b/examples/spend-p2wpkh.py @@ -14,9 +14,6 @@ """Low-level example of how to spend a P2WPKH output.""" import sys -if sys.version_info.major < 3: - sys.stderr.write("Sorry, Python 3.x required by this example.\n") - sys.exit(1) import hashlib diff --git a/examples/spend-p2wsh-txout.py b/examples/spend-p2wsh-txout.py index df1044d1..7d77c58f 100644 --- a/examples/spend-p2wsh-txout.py +++ b/examples/spend-p2wsh-txout.py @@ -13,11 +13,6 @@ """Low-level example of how to spend a P2WSH/BIP141 txout""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - import hashlib from bitcoin import SelectParams diff --git a/examples/timestamp-op-ret.py b/examples/timestamp-op-ret.py index 63d4758f..c8ae0a97 100755 --- a/examples/timestamp-op-ret.py +++ b/examples/timestamp-op-ret.py @@ -13,12 +13,6 @@ """Example of timestamping a file via OP_RETURN""" -import sys -if sys.version_info.major < 3: - sys.stderr.write('Sorry, Python 3.x required by this example.\n') - sys.exit(1) - -import hashlib import bitcoin.rpc import sys From bc5400cac534530ad66b0c9e2eb95e7b5653cf75 Mon Sep 17 00:00:00 2001 From: Janus Date: Tue, 13 Oct 2020 10:35:27 -0500 Subject: [PATCH 42/82] Rename and document hexlify/unhexlify wrappers These wrapper work differently than functions from binascii, even though they have the same name. That could be a source of confusion. In this commit, the functions are renamed to make it clear that they operate with strings. Because they cannot handle Unicode in any meaningful way, I have changed their implementation to not even attempt it, as it is more complicated to handle Unicode, and it doesn't even work anyway. --- bitcoin/rpc.py | 61 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 6602c4e0..42c2012c 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -41,8 +41,35 @@ DEFAULT_HTTP_TIMEOUT = 30 -unhexlify = lambda h: binascii.unhexlify(h.encode('utf8')) -hexlify = lambda b: binascii.hexlify(b).decode('utf8') +def unhexlify_str(h): + """ + Converts a string containing hexadecimal encoding into a bytes-object. + + It works by encoding the given string h as ASCII, then interpreting each of + its two-character ASCII tuples as bytes: + - "00" to byte 0 + - "ff" to byte 255 + - "FF" also to byte 255 + + The string must only contain characters in the ranges 0-9, a-f and A-F. + + If the string contains characters not in ASCII, + UnicodeEncodeError is raised. + If the string contains out-of-range ASCII characters, + binascii.Error is raised. + If number of encoded ASCII bytes is odd, + binascii.Error is raised. + """ + return binascii.unhexlify(h.encode('ascii')) + +def hexlify_str(b): + """ + Given an arbitrary bytes-like object, returns a string (that would encode + as ASCII) containing the hex-representation of the bytes-like object. + + Always succeeds. + """ + return binascii.hexlify(b).decode('ascii') class JSONRPCError(Exception): @@ -365,10 +392,10 @@ def fundrawtransaction(self, tx, include_watching=False): 'changepos': Position of added change output, or -1, } """ - hextx = hexlify(tx.serialize()) + hextx = hexlify_str(tx.serialize()) r = self._call('fundrawtransaction', hextx, include_watching) - r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) del r['hex'] r['fee'] = int(r['fee'] * COIN) @@ -456,7 +483,7 @@ def getblockheader(self, block_hash, verbose=False): 'nextblockhash':nextblockhash, 'chainwork':x(r['chainwork'])} else: - return CBlockHeader.deserialize(unhexlify(r)) + return CBlockHeader.deserialize(unhexlify_str(r)) def getblock(self, block_hash): @@ -477,7 +504,7 @@ def getblock(self, block_hash): except InvalidAddressOrKeyError as ex: raise IndexError('%s.getblock(): %s (%d)' % (self.__class__.__name__, ex.error['message'], ex.error['code'])) - return CBlock.deserialize(unhexlify(r)) + return CBlock.deserialize(unhexlify_str(r)) def getblockcount(self): """Return the number of blocks in the longest block chain""" @@ -556,7 +583,7 @@ def getrawtransaction(self, txid, verbose=False): raise IndexError('%s.getrawtransaction(): %s (%d)' % (self.__class__.__name__, ex.error['message'], ex.error['code'])) if verbose: - r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) del r['hex'] del r['txid'] del r['version'] @@ -565,7 +592,7 @@ def getrawtransaction(self, txid, verbose=False): del r['vout'] r['blockhash'] = lx(r['blockhash']) if 'blockhash' in r else None else: - r = CTransaction.deserialize(unhexlify(r)) + r = CTransaction.deserialize(unhexlify_str(r)) return r @@ -613,7 +640,7 @@ def gettxout(self, outpoint, includemempool=True): raise IndexError('%s.gettxout(): unspent txout %r not found' % (self.__class__.__name__, outpoint)) r['txout'] = CTxOut(int(r['value'] * COIN), - CScript(unhexlify(r['scriptPubKey']['hex']))) + CScript(unhexlify_str(r['scriptPubKey']['hex']))) del r['value'] del r['scriptPubKey'] r['bestblock'] = lx(r['bestblock']) @@ -653,7 +680,7 @@ def listunspent(self, minconf=0, maxconf=9999999, addrs=None): unspent['address'] = CBitcoinAddress(unspent['address']) except KeyError: pass - unspent['scriptPubKey'] = CScript(unhexlify(unspent['scriptPubKey'])) + unspent['scriptPubKey'] = CScript(unhexlify_str(unspent['scriptPubKey'])) unspent['amount'] = int(unspent['amount'] * COIN) r2.append(unspent) return r2 @@ -669,7 +696,7 @@ def sendrawtransaction(self, tx, allowhighfees=False): allowhighfees - Allow even if fees are unreasonably high. """ - hextx = hexlify(tx.serialize()) + hextx = hexlify_str(tx.serialize()) r = None if allowhighfees: r = self._call('sendrawtransaction', hextx, True) @@ -699,9 +726,9 @@ def signrawtransaction(self, tx, *args): FIXME: implement options """ - hextx = hexlify(tx.serialize()) + hextx = hexlify_str(tx.serialize()) r = self._call('signrawtransaction', hextx, *args) - r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) del r['hex'] return r @@ -711,9 +738,9 @@ def signrawtransactionwithwallet(self, tx, *args): FIXME: implement options """ - hextx = hexlify(tx.serialize()) + hextx = hexlify_str(tx.serialize()) r = self._call('signrawtransactionwithwallet', hextx, *args) - r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) del r['hex'] return r @@ -723,7 +750,7 @@ def submitblock(self, block, params=None): params is optional and is currently ignored by bitcoind. See https://en.bitcoin.it/wiki/BIP_0022 for full specification. """ - hexblock = hexlify(block.serialize()) + hexblock = hexlify_str(block.serialize()) if params is not None: return self._call('submitblock', hexblock, params) else: @@ -735,7 +762,7 @@ def validateaddress(self, address): if r['isvalid']: r['address'] = CBitcoinAddress(r['address']) if 'pubkey' in r: - r['pubkey'] = unhexlify(r['pubkey']) + r['pubkey'] = unhexlify_str(r['pubkey']) return r def unlockwallet(self, password, timeout=60): From b57bbeb507fd594c12e53d332871be63dd77af68 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Wed, 30 Dec 2020 15:09:48 +1100 Subject: [PATCH 43/82] docs: fix simple typo, probabably -> probably There is a small typo in bitcoin/core/serialize.py. Should read `probably` rather than `probabably`. --- bitcoin/core/serialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index c89b73e4..cf3e050f 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -11,7 +11,7 @@ """Serialization routines -You probabably don't need to use these directly. +You probably don't need to use these directly. """ from __future__ import absolute_import, division, print_function, unicode_literals From 9d71f1e6e07cb5e9a077caed2550af3719f87d75 Mon Sep 17 00:00:00 2001 From: Benoit Verret Date: Sat, 24 Apr 2021 21:11:52 -0400 Subject: [PATCH 44/82] Update README.md --- README.md | 2 +- examples/spend-p2wsh-txout.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/spend-p2wsh-txout.py diff --git a/README.md b/README.md index 22964d73..8945ba91 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ appropriately. See `examples/` directory. For instance this example creates a transaction spending a pay-to-script-hash transaction output: - $ PYTHONPATH=. examples/spend-pay-to-script-hash-txout.py + $ PYTHONPATH=. examples/spend-p2sh-txout.py diff --git a/examples/spend-p2wsh-txout.py b/examples/spend-p2wsh-txout.py old mode 100644 new mode 100755 From e311d27edfd8df40e4457934178113c482e6cb8d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 26 Jun 2021 13:21:25 -0500 Subject: [PATCH 45/82] fix misuse of the term redeemScript in p2wsh example --- examples/spend-p2wsh-txout.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/spend-p2wsh-txout.py b/examples/spend-p2wsh-txout.py index df1044d1..5a9c3a8d 100755 --- a/examples/spend-p2wsh-txout.py +++ b/examples/spend-p2wsh-txout.py @@ -33,14 +33,14 @@ # Create a witnessScript and corresponding redeemScript. Similar to a scriptPubKey # the redeemScript must be satisfied for the funds to be spent. -txin_witnessScript = CScript([seckey.pub, OP_CHECKSIG]) -txin_scriptHash = hashlib.sha256(txin_witnessScript).digest() -txin_redeemScript = CScript([OP_0, txin_scriptHash]) +txin_redeemScript = CScript([seckey.pub, OP_CHECKSIG]) +txin_scriptHash = hashlib.sha256(txin_redeemScript).digest() +txin_scriptPubKey = CScript([OP_0, txin_scriptHash]) # Convert the P2WSH scriptPubKey to a base58 Bitcoin address and print it. # You'll need to send some funds to it to create a txout to spend. -txin_p2wsh_address = P2WSHBitcoinAddress.from_scriptPubKey(txin_redeemScript) +txin_p2wsh_address = P2WSHBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) print('Pay to:', str(txin_p2wsh_address)) # Same as the txid:vout the createrawtransaction RPC call requires @@ -71,7 +71,7 @@ # Calculate the signature hash for that transaction. Note how the script we use # is the witnessScript, not the redeemScript. -sighash = SignatureHash(script=txin_witnessScript, txTo=tx, inIdx=0, +sighash = SignatureHash(script=txin_redeemScript, txTo=tx, inIdx=0, hashtype=SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) # Now sign it. We have to append the type of signature we want to the end, in @@ -80,7 +80,7 @@ # # Construct a witness for this P2WSH transaction and add to tx. -witness = CScriptWitness([sig, txin_witnessScript]) +witness = CScriptWitness([sig, txin_redeemScript]) tx.wit = CTxWitness([CTxInWitness(witness)]) # TODO: upgrade VerifyScript to support Segregated Witness and place verify the witness program here. From d05bc123db4774d32e7619cbecf2df8efd310806 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 26 Jun 2021 13:23:09 -0500 Subject: [PATCH 46/82] also fix a comment about redeemScript for p2wsh --- examples/spend-p2wsh-txout.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/spend-p2wsh-txout.py b/examples/spend-p2wsh-txout.py index 5a9c3a8d..8b2e8507 100755 --- a/examples/spend-p2wsh-txout.py +++ b/examples/spend-p2wsh-txout.py @@ -69,8 +69,7 @@ # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout]) -# Calculate the signature hash for that transaction. Note how the script we use -# is the witnessScript, not the redeemScript. +# Calculate the signature hash for that transaction. sighash = SignatureHash(script=txin_redeemScript, txTo=tx, inIdx=0, hashtype=SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) From 26c125161ec1c7593c8f3acdc4bbdcdde05d51c6 Mon Sep 17 00:00:00 2001 From: 0x0ff <0x0ff@onsats.org> Date: Sun, 28 Nov 2021 16:49:55 +0000 Subject: [PATCH 47/82] Add support for signet network --- bitcoin/__init__.py | 6 ++++++ bitcoin/core/__init__.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index e4eaceac..33c022f7 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -45,6 +45,12 @@ class TestNetParams(bitcoin.core.CoreTestNetParams): 'SECRET_KEY' :239} BECH32_HRP = 'tb' +class SigNetParams(bitcoin.core.CoreSigNetParams): + MESSAGE_START = b'\x0a\x03\xcf\x40' + DEFAULT_PORT = 38333 + RPC_PORT = 38332 + DNS_SEEDS = (("signet.bitcoin.sprovoost.nl", "seed.signet.bitcoin.sprovoost.nl")) + class RegTestParams(bitcoin.core.CoreRegTestParams): MESSAGE_START = b'\xfa\xbf\xb5\xda' DEFAULT_PORT = 18444 diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 08961edc..4c03c106 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -710,6 +710,10 @@ class CoreTestNetParams(CoreMainParams): NAME = 'testnet' GENESIS_BLOCK = CBlock.deserialize(x('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000')) +class CoreSigNetParams(CoreMainParams): + NAME = 'signet' + GENESIS_BLOCK = CBlock.deserialize(x('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad222030101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000')) + class CoreRegTestParams(CoreTestNetParams): NAME = 'regtest' GENESIS_BLOCK = CBlock.deserialize(x('0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000')) From 2bbd973d0cf9cbcd9dac6401a13ba81fb2874b2b Mon Sep 17 00:00:00 2001 From: sgmoore Date: Thu, 16 Dec 2021 05:51:33 -0800 Subject: [PATCH 48/82] Fix typo at line 62 Fix minor typo at line 62 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8945ba91..e153544d 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Rather confusingly Bitcoin Core shows transaction and block hashes as little-endian hex rather than the big-endian the rest of the world uses for SHA256. python-bitcoinlib provides the convenience functions x() and lx() in bitcoin.core to convert from big-endian and little-endian hex to raw bytes to -accomodate this. In addition see b2x() and b2lx() for conversion from bytes to +accommodate this. In addition see b2x() and b2lx() for conversion from bytes to big/little-endian hex. From af9e5c4c32b09a019534f354bf3c337d254d382f Mon Sep 17 00:00:00 2001 From: kafka2000 Date: Fri, 7 Jan 2022 08:12:06 +0300 Subject: [PATCH 49/82] Update getrawtransaction to work with block hash argument --- bitcoin/rpc.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 42c2012c..5907740a 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -566,7 +566,7 @@ def getrawmempool(self, verbose=False): r = [lx(txid) for txid in r] return r - def getrawtransaction(self, txid, verbose=False): + def getrawtransaction(self, txid, verbose=False, block_hash=None): """Return transaction with hash txid Raises IndexError if transaction not found. @@ -574,16 +574,22 @@ def getrawtransaction(self, txid, verbose=False): verbose - If true a dict is returned instead with additional information on the transaction. + block_hash - Hash of the block containing the transaction + (required when transaction not currently indexed by Bitcoin Core) + Note that if all txouts are spent and the transaction index is not enabled the transaction may not be available. """ try: - r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0) + if block_hash is None: + r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0) + else: + r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0, b2lx(block_hash)) except InvalidAddressOrKeyError as ex: raise IndexError('%s.getrawtransaction(): %s (%d)' % (self.__class__.__name__, ex.error['message'], ex.error['code'])) if verbose: - r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) del r['hex'] del r['txid'] del r['version'] @@ -592,8 +598,7 @@ def getrawtransaction(self, txid, verbose=False): del r['vout'] r['blockhash'] = lx(r['blockhash']) if 'blockhash' in r else None else: - r = CTransaction.deserialize(unhexlify_str(r)) - + r = CTransaction.deserialize(unhexlify(r)) return r def getreceivedbyaddress(self, addr, minconf=1): From 2aad684d99a43e7518411b9f8a2939cf2783ae32 Mon Sep 17 00:00:00 2001 From: James O'Beirne Date: Wed, 23 Feb 2022 21:59:13 -0500 Subject: [PATCH 50/82] Make signet usable Add its constant into `SelectParams`. --- README.md | 2 +- bitcoin/__init__.py | 7 +++++++ bitcoin/core/__init__.py | 2 ++ bitcoin/rpc.py | 2 +- examples/make-bootstrap-rpc.py | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e153544d..58e11fcb 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Do the following: import bitcoin bitcoin.SelectParams(NAME) -Where NAME is one of 'testnet', 'mainnet', or 'regtest'. The chain currently +Where NAME is one of 'testnet', 'mainnet', 'signet', or 'regtest'. The chain currently selected is a global variable that changes behavior everywhere, just like in the Satoshi codebase. diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 33c022f7..8a2bf953 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -50,6 +50,11 @@ class SigNetParams(bitcoin.core.CoreSigNetParams): DEFAULT_PORT = 38333 RPC_PORT = 38332 DNS_SEEDS = (("signet.bitcoin.sprovoost.nl", "seed.signet.bitcoin.sprovoost.nl")) + BASE58_PREFIXES = {'PUBKEY_ADDR':111, + 'SCRIPT_ADDR':196, + 'SECRET_KEY' :239} + + BECH32_HRP = 'tb' class RegTestParams(bitcoin.core.CoreRegTestParams): MESSAGE_START = b'\xfa\xbf\xb5\xda' @@ -84,5 +89,7 @@ def SelectParams(name): params = bitcoin.core.coreparams = TestNetParams() elif name == 'regtest': params = bitcoin.core.coreparams = RegTestParams() + elif name == 'signet': + params = bitcoin.core.coreparams = SigNetParams() else: raise ValueError('Unknown chain %r' % name) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 4c03c106..80cb6367 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -736,6 +736,8 @@ def _SelectCoreParams(name): coreparams = CoreTestNetParams() elif name == 'regtest': coreparams = CoreRegTestParams() + elif name == 'signet': + coreparams = CoreSigNetParams() else: raise ValueError('Unknown chain %r' % name) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 5907740a..56925390 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -355,7 +355,7 @@ def __init__(self, out of the file ``btc_conf_file``. If ``btc_conf_file`` is not specified, ``~/.bitcoin/bitcoin.conf`` or equivalent is used by default. The default port is set according to the chain parameters in - use: mainnet, testnet, or regtest. + use: mainnet, testnet, signet, or regtest. Usually no arguments to ``Proxy()`` are needed; the local bitcoind will be used. diff --git a/examples/make-bootstrap-rpc.py b/examples/make-bootstrap-rpc.py index 4279cc28..a26d98e2 100755 --- a/examples/make-bootstrap-rpc.py +++ b/examples/make-bootstrap-rpc.py @@ -29,7 +29,7 @@ if len(sys.argv) == 3: bitcoin.SelectParams(sys.argv[2]) except Exception as ex: - print('Usage: %s [network=(mainnet|testnet|regtest)] > bootstrap.dat' % sys.argv[0], file=sys.stderr) + print('Usage: %s [network=(mainnet|testnet|regtest|signet)] > bootstrap.dat' % sys.argv[0], file=sys.stderr) sys.exit(1) From 2e4bc025dc9eeca278776a772e80be0fb06e3c2c Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 28 Apr 2022 07:15:13 -0400 Subject: [PATCH 51/82] Replace `unhexlify()` calls with `unhexlify_str()` Fixes https://github.com/petertodd/python-bitcoinlib/issues/274 --- bitcoin/rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 5907740a..9959b133 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -589,7 +589,7 @@ def getrawtransaction(self, txid, verbose=False, block_hash=None): raise IndexError('%s.getrawtransaction(): %s (%d)' % (self.__class__.__name__, ex.error['message'], ex.error['code'])) if verbose: - r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) + r['tx'] = CTransaction.deserialize(unhexlify_str(r['hex'])) del r['hex'] del r['txid'] del r['version'] @@ -598,7 +598,7 @@ def getrawtransaction(self, txid, verbose=False, block_hash=None): del r['vout'] r['blockhash'] = lx(r['blockhash']) if 'blockhash' in r else None else: - r = CTransaction.deserialize(unhexlify(r)) + r = CTransaction.deserialize(unhexlify_str(r)) return r def getreceivedbyaddress(self, addr, minconf=1): From 27fa0e8ab0b84fcc54988fd6f66fd93a3266e7ba Mon Sep 17 00:00:00 2001 From: windsok Date: Tue, 26 Jul 2022 13:59:56 -0700 Subject: [PATCH 52/82] Add fallback to pure-python ripemd160 --- bitcoin/core/contrib/__init__.py | 0 bitcoin/core/contrib/ripemd160.py | 110 ++++++++++++++++++++++++++++++ bitcoin/core/serialize.py | 16 +++-- bitcoin/tests/test_ripemd160.py | 24 +++++++ 4 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 bitcoin/core/contrib/__init__.py create mode 100644 bitcoin/core/contrib/ripemd160.py create mode 100644 bitcoin/tests/test_ripemd160.py diff --git a/bitcoin/core/contrib/__init__.py b/bitcoin/core/contrib/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bitcoin/core/contrib/ripemd160.py b/bitcoin/core/contrib/ripemd160.py new file mode 100644 index 00000000..2f5eaeaf --- /dev/null +++ b/bitcoin/core/contrib/ripemd160.py @@ -0,0 +1,110 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Pure Python RIPEMD160 implementation. Note that this impelentation is not constant time. +Original source: https://github.com/bitcoin/bitcoin/pull/23716 +""" + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) \ No newline at end of file diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index d181c7d3..65a1f7a8 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -29,10 +29,18 @@ def Hash(msg): def Hash160(msg): """RIPEME160(SHA256(msg)) -> bytes""" - h = hashlib.new('ripemd160') - h.update(hashlib.sha256(msg).digest()) - return h.digest() - + try: + # This will fail with newer versions of OpenSSL such as included in + # Ubuntu 22.04 as OpenSSL no longer includes ripemd160 by default. + hashlib.new('ripemd160') + def ripemd160(msg): + return hashlib.new('ripemd160', msg).digest() + except: + # If OpenSSL ripemd160 provided by hashlib fails, use the pure + # python implementation instead + from bitcoin.core.contrib.ripemd160 import ripemd160 + + return ripemd160(hashlib.sha256(msg).digest()) class SerializationError(Exception): """Base class for serialization errors""" diff --git a/bitcoin/tests/test_ripemd160.py b/bitcoin/tests/test_ripemd160.py new file mode 100644 index 00000000..a135b55e --- /dev/null +++ b/bitcoin/tests/test_ripemd160.py @@ -0,0 +1,24 @@ +import unittest + +from bitcoin.core.contrib.ripemd160 import ripemd160 + + +class Test_ripemd160(unittest.TestCase): + def test_ripemd160(self): + """RIPEMD-160 test vectors.""" + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + for msg, hexout in [ + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), + (b"abcdefghijklmnopqrstuvwxyz", + "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), + (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), + (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "b0e20b6e3116640286ed3a87a5713079b21f5189"), + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") + ]: + self.assertEqual(ripemd160(msg).hex(), hexout) \ No newline at end of file From 3a5ec119af1e3aa3b96445074d319460bc655b10 Mon Sep 17 00:00:00 2001 From: windsok Date: Wed, 27 Jul 2022 10:52:52 -0700 Subject: [PATCH 53/82] remove openssl fallback for ripemd160, always use pure-python implementation --- bitcoin/core/serialize.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index 65a1f7a8..b12c0239 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -20,6 +20,8 @@ from io import BytesIO +from bitcoin.core.contrib.ripemd160 import ripemd160 + MAX_SIZE = 0x02000000 @@ -29,17 +31,6 @@ def Hash(msg): def Hash160(msg): """RIPEME160(SHA256(msg)) -> bytes""" - try: - # This will fail with newer versions of OpenSSL such as included in - # Ubuntu 22.04 as OpenSSL no longer includes ripemd160 by default. - hashlib.new('ripemd160') - def ripemd160(msg): - return hashlib.new('ripemd160', msg).digest() - except: - # If OpenSSL ripemd160 provided by hashlib fails, use the pure - # python implementation instead - from bitcoin.core.contrib.ripemd160 import ripemd160 - return ripemd160(hashlib.sha256(msg).digest()) class SerializationError(Exception): From 496bbcefafc34933c6573b629de71beca5625a15 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 26 Aug 2022 11:31:38 -0400 Subject: [PATCH 54/82] Use python3 for setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2805b530..c8d74f5c 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from setuptools import setup, find_packages import os From be4116feaf94671fc44780a0ccf8b2d140afd317 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 26 Aug 2022 11:32:02 -0400 Subject: [PATCH 55/82] Update tox python versions --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4db912ad..fc6b5cf3 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ [tox] #------------------------------------------------------------------- -envlist = reset,py33,py34,py35,py36,py37,pypy3,stats +envlist = reset,py34,py35,py36,py37,py38,py39,pypy3,stats skip_missing_interpreters = True [testenv] #--------------------------------------------------------------- From 807521ed14e242aa73bb3c9acf1cc7fd79a23539 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 26 Aug 2022 11:33:42 -0400 Subject: [PATCH 56/82] Release v0.11.1 --- bitcoin/__init__.py | 2 +- release-notes.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 8a2bf953..9e6626ea 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.1dev' +__version__ = '0.11.1' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 8e1c303f..aa961c5d 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,11 @@ # python-bitcoinlib release notes +## v0.11.1 + +* Pure-python RIPEMD-160, for newer versions of OpenSSL without RIPEMD-160 + support. +* Signet support + ## v0.11.0 * Bech32 implementation From 3f7c031db5532fb4a3f435b298092abdea6c7a9c Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 26 Aug 2022 11:57:48 -0400 Subject: [PATCH 57/82] Reset version for future development --- bitcoin/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 9e6626ea..ad1e03eb 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.1' +__version__ = '0.11.2.dev0' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' From f894632c29b12d24d93789c0a24cb64bb2580394 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Fri, 26 Aug 2022 16:26:26 -0300 Subject: [PATCH 58/82] Fix another usage of openssl's ripemd160 --- bitcoin/core/scripteval.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bitcoin/core/scripteval.py b/bitcoin/core/scripteval.py index 4064a9fc..e93e8720 100644 --- a/bitcoin/core/scripteval.py +++ b/bitcoin/core/scripteval.py @@ -23,6 +23,7 @@ import bitcoin.core._bignum import bitcoin.core.key import bitcoin.core.serialize +from bitcoin.core.contrib.ripemd160 import ripemd160 # Importing everything for simplicity; note that we use __all__ at the end so # we're not exporting the whole contents of the script module. @@ -624,9 +625,7 @@ def check_args(n): elif sop == OP_RIPEMD160: check_args(1) - h = hashlib.new('ripemd160') - h.update(stack.pop()) - stack.append(h.digest()) + stack.append(ripemd160(stack.pop())) elif sop == OP_ROT: check_args(3) From 7924f4d549e0d93991c663a343e0d835c6d9877a Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Sun, 28 Aug 2022 11:39:41 -0400 Subject: [PATCH 59/82] Release v0.11.2 --- bitcoin/__init__.py | 2 +- release-notes.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index ad1e03eb..7fdd5201 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.2.dev0' +__version__ = '0.11.2' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index aa961c5d..0120b35f 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,9 @@ # python-bitcoinlib release notes +## v0.11.2 + +* Fixed one remaining use of OpenSSL for RIPEMD-160 + ## v0.11.1 * Pure-python RIPEMD-160, for newer versions of OpenSSL without RIPEMD-160 From 517dac482fd6fad62e1616c6806e9c190cd13ac0 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Sun, 28 Aug 2022 11:49:42 -0400 Subject: [PATCH 60/82] Update release notes --- bitcoin/__init__.py | 2 +- release-notes.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 7fdd5201..5eed3382 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.11.2' +__version__ = '0.12.0.dev0' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 0120b35f..c92f01e5 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,9 @@ # python-bitcoinlib release notes +## v0.12.0-pending + +* `CECKey` now rejects secrets that aren't exactly 32 bytes + ## v0.11.2 * Fixed one remaining use of OpenSSL for RIPEMD-160 From 80fffb3c12582b283a351db55cdc39706649a04d Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 19 Oct 2022 16:13:48 -0400 Subject: [PATCH 61/82] Pass strSubVer.encode() to serializer The serializer expects bytes, but strSubVer is a string. So it needs to be encoded first. --- bitcoin/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/messages.py b/bitcoin/messages.py index edb7beb1..17ee1747 100644 --- a/bitcoin/messages.py +++ b/bitcoin/messages.py @@ -160,7 +160,7 @@ def msg_ser(self, f): self.addrTo.stream_serialize(f, True) self.addrFrom.stream_serialize(f, True) f.write(struct.pack(b" Date: Wed, 25 Jan 2023 08:46:10 -0500 Subject: [PATCH 62/82] Fix segfault with newer OpenSSL versions --- bitcoin/core/key.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 4b1cf5dd..8270066f 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -434,6 +434,12 @@ def verify(self, hash, sig): # pylint: disable=redefined-builtin norm_sig = ctypes.c_void_p(0) _ssl.d2i_ECDSA_SIG(ctypes.byref(norm_sig), ctypes.byref(ctypes.c_char_p(sig)), len(sig)) + # Newer versions of OpenSSL (>3.0.0?) seem to fail here, leaving a null + # pointer in norm_sig + if not norm_sig: + return False + + # Older versions (<3.0.0?) seem to fail here, with a empty derlen derlen = _ssl.i2d_ECDSA_SIG(norm_sig, 0) if derlen == 0: _ssl.ECDSA_SIG_free(norm_sig) From ca4bea679f3bb76b32279d44b41c833e83c8f46a Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 25 Jan 2023 08:52:54 -0500 Subject: [PATCH 63/82] Revert "Pass strSubVer.encode() to serializer" This reverts commit 80fffb3c12582b283a351db55cdc39706649a04d. Fixes issue #287 The rest of the codebase uses a byte string, not a python unicode string. --- bitcoin/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/messages.py b/bitcoin/messages.py index 17ee1747..edb7beb1 100644 --- a/bitcoin/messages.py +++ b/bitcoin/messages.py @@ -160,7 +160,7 @@ def msg_ser(self, f): self.addrTo.stream_serialize(f, True) self.addrFrom.stream_serialize(f, True) f.write(struct.pack(b" Date: Wed, 25 Jan 2023 09:07:22 -0500 Subject: [PATCH 64/82] Remove tests that fail on newer OpenSSL versions Seems that OpenSSL >3.0.0 (?) made certain signatures invalid that previously would verify. This causes certain edge cases fail. python-bitcoinlib doesn't promise consensus compatibility with Bitcoin Core, so removing these tests is fine. Frankly it's about time that all the consensus-related stuff get removed... --- bitcoin/tests/data/script_invalid.json | 18 ---------------- bitcoin/tests/data/script_valid.json | 30 -------------------------- bitcoin/tests/data/tx_valid.json | 18 ---------------- 3 files changed, 66 deletions(-) diff --git a/bitcoin/tests/data/script_invalid.json b/bitcoin/tests/data/script_invalid.json index 43513388..1a68f44c 100644 --- a/bitcoin/tests/data/script_invalid.json +++ b/bitcoin/tests/data/script_invalid.json @@ -455,18 +455,6 @@ "P2SH", "P2SH(2-of-3), 1 sig" ], -[ - "0x47 0x30440220005ece1335e7f657a1a1f476a7fb5bd90964e8a022489f890614a04acfb734c002206c12b8294a6513c7710e8c82d3c23d75cdbfe83200eb7efb495701958501a5d601", - "0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 CHECKSIG NOT", - "", - "P2PK NOT with too much R padding but no DERSIG" -], -[ - "0x47 0x304402208e43c0b91f7c1e5bc58e41c8185f8a6086e111b0090187968a86f2822462d3c902200a58f4076b1133b18ff1dc83ee51676e44c60cc608d9534e0df5ace0424fc0be01", - "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG NOT", - "", - "BIP66 example 2, without DERSIG" -], [ "0", "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", @@ -479,12 +467,6 @@ "", "BIP66 example 5, without DERSIG" ], -[ - "0 0x47 0x30440220b119d67d389315308d1745f734a51ff3ec72e06081e84e236fdf9dc2f5d2a64802204b04e3bc38674c4422ea317231d642b56dc09d214a1ecbbf16ecca01ed996e2201 0x47 0x3044022079ea80afd538d9ada421b5101febeb6bc874e01dde5bca108c1d0479aec339a4022004576db8f66130d1df686ccf00935703689d69cf539438da1edab208b0d63c4801", - "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 2 CHECKMULTISIG NOT", - "", - "BIP66 example 8, without DERSIG" -], [ "0 0 0x47 0x3044022081aa9d436f2154e8b6d600516db03d78de71df685b585a9807ead4210bd883490220534bb6bdf318a419ac0749660b60e78d17d515558ef369bf872eff405b676b2e01", "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 2 CHECKMULTISIG", diff --git a/bitcoin/tests/data/script_valid.json b/bitcoin/tests/data/script_valid.json index e5f0d17b..33ef138e 100644 --- a/bitcoin/tests/data/script_valid.json +++ b/bitcoin/tests/data/script_valid.json @@ -744,36 +744,12 @@ "P2SH", "P2SH(2-of-3)" ], -[ - "0x47 0x304402200060558477337b9022e70534f1fea71a318caf836812465a2509931c5e7c4987022078ec32bd50ac9e03a349ba953dfd9fe1c8d2dd8bdb1d38ddca844d3d5c78c11801", - "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", - "", - "P2PK with too much R padding but no DERSIG" -], -[ - "0x48 0x304502202de8c03fc525285c9c535631019a5f2af7c6454fa9eb392a3756a4917c420edd02210046130bf2baf7cfc065067c8b9e33a066d9c15edcea9feb0ca2d233e3597925b401", - "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", - "", - "P2PK with too much S padding but no DERSIG" -], -[ - "0x47 0x30440220d7a0417c3f6d1a15094d1cf2a3378ca0503eb8a57630953a9e2987e21ddd0a6502207a6266d686c99090920249991d3d42065b6d43eb70187b219c0db82e4f94d1a201", - "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", - "", - "P2PK with too little R padding but no DERSIG" -], [ "0x47 0x30440220005ece1335e7f757a1a1f476a7fb5bd90964e8a022489f890614a04acfb734c002206c12b8294a6513c7710e8c82d3c23d75cdbfe83200eb7efb495701958501a5d601", "0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 CHECKSIG NOT", "", "P2PK NOT with bad sig with too much R padding but no DERSIG" ], -[ - "0x47 0x30440220d7a0417c3f6d1a15094d1cf2a3378ca0503eb8a57630953a9e2987e21ddd0a6502207a6266d686c99090920249991d3d42065b6d43eb70187b219c0db82e4f94d1a201", - "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", - "", - "BIP66 example 1, without DERSIG" -], [ "0", "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG NOT", @@ -792,12 +768,6 @@ "", "BIP66 example 6, without DERSIG" ], -[ - "0 0x47 0x30440220cae00b1444babfbf6071b0ba8707f6bd373da3df494d6e74119b0430c5db810502205d5231b8c5939c8ff0c82242656d6e06edb073d42af336c99fe8837c36ea39d501 0x47 0x3044022027c2714269ca5aeecc4d70edc88ba5ee0e3da4986e9216028f489ab4f1b8efce022022bd545b4951215267e4c5ceabd4c5350331b2e4a0b6494c56f361fa5a57a1a201", - "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 2 CHECKMULTISIG", - "", - "BIP66 example 7, without DERSIG" -], [ "0 0 0x47 0x30440220da6f441dc3b4b2c84cfa8db0cd5b34ed92c9e01686de5a800d40498b70c0dcac02207c2cf91b0c32b860c4cd4994be36cfb84caf8bb7c3a8e4d96a31b2022c5299c501", "2 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x21 0x03363d90d447b00c9c99ceac05b6262ee053441c7e55552ffe526bad8f83ff4640 2 CHECKMULTISIG NOT", diff --git a/bitcoin/tests/data/tx_valid.json b/bitcoin/tests/data/tx_valid.json index d2a4c9df..ef70a542 100644 --- a/bitcoin/tests/data/tx_valid.json +++ b/bitcoin/tests/data/tx_valid.json @@ -5,18 +5,6 @@ ["serializedTransaction, enforceP2SH]"], ["Objects that are only a single string (like this one) are ignored"], -["The following is 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], -["It is of particular interest because it contains an invalidly-encoded signature which OpenSSL accepts"], -["See http://r6.ca/blog/20111119T211504Z.html"], -["It is also the first OP_CHECKMULTISIG transaction in standard form"], -[[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], -"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true], - -["The following is a tweaked form of 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63"], -["It has an arbitrary extra byte stuffed into the signature at pos length - 2"], -[[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], -"0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004A0048304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2bab01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", true], - ["The following is c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73"], ["It is of interest because it contains a 0-sequence as well as a signature of SIGHASH type 0 (which is not a real type)"], [[["406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602", 0, "DUP HASH160 0x14 0xdc44b1164188067c3a32d4780f5996fa14a4f2d9 EQUALVERIFY CHECKSIG"]], @@ -30,12 +18,6 @@ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]], "01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", true], -["The following is f7fdd091fa6d8f5e7a8c2458f5c38faffff2d3f1406b6e4fe2c99dcc0d2d1cbb"], -["It caught a bug in the workaround for 23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63 in an overly simple implementation"], -[[["b464e85df2a238416f8bdae11d120add610380ea07f4ef19c5f9dfd472f96c3d", 0, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"], -["b7978cc96e59a8b13e0865d3f95657561a7f725be952438637475920bac9eb21", 1, "DUP HASH160 0x14 0xbef80ecf3a44500fda1bc92176e442891662aed2 EQUALVERIFY CHECKSIG"]], -"01000000023d6cf972d4dff9c519eff407ea800361dd0a121de1da8b6f4138a2f25de864b4000000008a4730440220ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e022049cffa1cdc102a0b56e0e04913606c70af702a1149dc3b305ab9439288fee090014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff21ebc9ba20594737864352e95b727f1a565756f9d365083eb1a8596ec98c97b7010000008a4730440220503ff10e9f1e0de731407a4a245531c9ff17676eda461f8ceeb8c06049fa2c810220c008ac34694510298fa60b3f000df01caa244f165b727d4896eb84f81e46bcc4014104266abb36d66eb4218a6dd31f09bb92cf3cfa803c7ea72c1fc80a50f919273e613f895b855fb7465ccbc8919ad1bd4a306c783f22cd3227327694c4fa4c1c439affffffff01f0da5200000000001976a914857ccd42dded6df32949d4646dfa10a92458cfaa88ac00000000", true], - ["The following tests for the presence of a bug in the handling of SIGHASH_SINGLE"], ["It results in signing the constant 1, instead of something generated based on the transaction,"], ["when the input doing the signing has an index greater than the maximum output index"], From e5e2a36a5fa0e2e7d668ace3c764f24db244871b Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 25 Jan 2023 09:19:49 -0500 Subject: [PATCH 65/82] Release v0.12.0 --- bitcoin/__init__.py | 2 +- release-notes.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 5eed3382..90bc4a15 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.12.0.dev0' +__version__ = '0.12.0' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index c92f01e5..91e51cd3 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,8 +1,12 @@ # python-bitcoinlib release notes -## v0.12.0-pending +## v0.12.0 * `CECKey` now rejects secrets that aren't exactly 32 bytes +* Now compatible with OpenSSL v3; broke compatibility with certain script edge + cases around malformed signatures. This is acceptable because + python-bitcoinlib doesn't claim to have consensus compatibility and no-one + should use it for script/tx validation. ## v0.11.2 From 470205210a77d9d308eb3869f71a5d1b8fd92d1b Mon Sep 17 00:00:00 2001 From: User087 <79201998+User087@users.noreply.github.com> Date: Tue, 14 Mar 2023 12:26:08 +0000 Subject: [PATCH 66/82] Update key.py To resolve the error `FileNotFoundError: Could not find module 'libeay32' (or one of its dependencies). Try using the full path with constructor syntax.` occurring on Windows --- bitcoin/core/key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 8270066f..355d2a85 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -25,7 +25,7 @@ import bitcoin.core.script _ssl = ctypes.cdll.LoadLibrary( - ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or 'libeay32' + ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or ctypes.util.find_library('libeay32') ) _libsecp256k1_path = ctypes.util.find_library('secp256k1') From e3f1a0383e8ea96a7dafd74403c41c653eb1c4c0 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 26 Apr 2023 21:34:07 +0000 Subject: [PATCH 67/82] Minor: doc formatting fix --- bitcoin/core/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 80cb6367..7c2d5a85 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -434,9 +434,11 @@ def from_tx(cls, tx): return cls(tx.vin, tx.vout, tx.nLockTime, tx.nVersion, tx.wit) def GetTxid(self): - """Get the transaction ID. This differs from the transactions hash as - given by GetHash. GetTxid excludes witness data, while GetHash - includes it. """ + """Get the transaction ID. + + This differs from the transactions hash as given by GetHash. GetTxid + excludes witness data, while GetHash includes it. + """ if self.wit != CTxWitness(): txid = Hash(CTransaction(self.vin, self.vout, self.nLockTime, self.nVersion).serialize()) From 70d9cb5e7bbfd8e228cbe6878a2517cb3250521d Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 27 Apr 2023 21:04:22 +0000 Subject: [PATCH 68/82] Add calc_weight() to CTransaction --- bitcoin/core/__init__.py | 18 ++++++++++++++++++ bitcoin/tests/test_transactions.py | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 7c2d5a85..538501af 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -446,6 +446,24 @@ def GetTxid(self): txid = Hash(self.serialize()) return txid + def calc_weight(self): + """Calculate the transaction weight, as defined by BIP141. + + The transaction must contain at least one input and one output. + """ + # Not clear how calc_weight() should be defined for the zero vin/vout + # cases, so punting on that decision for now. + assert len(self.vin) > 0 + assert len(self.vout) > 0 + + # This special case isn't strictly necessary. But saves on serializing + # the transaction twice in the no-witness case. + if self.wit.is_null(): + return len(self.serialize()) * 4 + else: + stripped = CTransaction(self.vin, self.vout, self.nLockTime, self.nVersion) + return len(stripped.serialize()) * 3 + len(self.serialize()) + @__make_mutable class CMutableTransaction(CTransaction): """A mutable transaction""" diff --git a/bitcoin/tests/test_transactions.py b/bitcoin/tests/test_transactions.py index 2753e9fe..c223b9dd 100644 --- a/bitcoin/tests/test_transactions.py +++ b/bitcoin/tests/test_transactions.py @@ -150,3 +150,24 @@ def test_tx_invalid(self): flags.add(SCRIPT_VERIFY_P2SH) VerifyScript(tx.vin[i].scriptSig, prevouts[tx.vin[i].prevout], tx, i, flags=flags) + + def test_calc_weight(self): + # test vectors taken from rust-bitcoin + txs = [ + # one segwit input (P2WPKH) + ('020000000001018a763b78d3e17acea0625bf9e52b0dc1beb2241b2502185348ba8ff4a253176e0100000000ffffffff0280d725000000000017a914c07ed639bd46bf7087f2ae1dfde63b815a5f8b488767fda20300000000160014869ec8520fa2801c8a01bfdd2e82b19833cd0daf02473044022016243edad96b18c78b545325aaff80131689f681079fb107a67018cb7fb7830e02205520dae761d89728f73f1a7182157f6b5aecf653525855adb7ccb998c8e6143b012103b9489bde92afbcfa85129a82ffa512897105d1a27ad9806bded27e0532fc84e700000000', 565), + # one segwit input (P2WSH) + ('01000000000101a3ccad197118a2d4975fadc47b90eacfdeaf8268adfdf10ed3b4c3b7e1ad14530300000000ffffffff0200cc5501000000001976a91428ec6f21f4727bff84bb844e9697366feeb69f4d88aca2a5100d00000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220548f11130353b3a8f943d2f14260345fc7c20bde91704c9f1cbb5456355078cd0220383ed4ed39b079b618bcb279bbc1f2ca18cb028c4641cb522c9c5868c52a0dc20147304402203c332ecccb3181ca82c0600520ee51fee80d3b4a6ab110945e59475ec71e44ac0220679a11f3ca9993b04ccebda3c834876f353b065bb08f50076b25f5bb93c72ae1016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000', 766), + # one segwit input (P2WPKH) and two legacy inputs (P2PKH) + ('010000000001036b6b6ac7e34e97c53c1cc74c99c7948af2e6aac75d8778004ae458d813456764000000006a473044022001deec7d9075109306320b3754188f81a8236d0d232b44bc69f8309115638b8f02204e17a5194a519cf994d0afeea1268740bdc10616b031a521113681cc415e815c012103488d3272a9fad78ee887f0684cb8ebcfc06d0945e1401d002e590c7338b163feffffffffc75bd7aa6424aee972789ec28ba181254ee6d8311b058d165bd045154d7660b0000000006b483045022100c8641bcbee3e4c47a00417875015d8c5d5ea918fb7e96f18c6ffe51bc555b401022074e2c46f5b1109cd79e39a9aa203eadd1d75356415e51d80928a5fb5feb0efee0121033504b4c6dfc3a5daaf7c425aead4c2dbbe4e7387ce8e6be2648805939ecf7054ffffffff494df3b205cd9430a26f8e8c0dc0bb80496fbc555a524d6ea307724bc7e60eee0100000000ffffffff026d861500000000001976a9145c54ed1360072ebaf56e87693b88482d2c6a101588ace407000000000000160014761e31e2629c6e11936f2f9888179d60a5d4c1f900000247304402201fa38a67a63e58b67b6cfffd02f59121ca1c8a1b22e1efe2573ae7e4b4f06c2b022002b9b431b58f6e36b3334fb14eaecee7d2f06967a77ef50d8d5f90dda1057f0c01210257dc6ce3b1100903306f518ee8fa113d778e403f118c080b50ce079fba40e09a00000000', 1755), + # three legacy inputs (P2PKH) + ('0100000003e4d7be4314204a239d8e00691128dca7927e19a7339c7948bde56f669d27d797010000006b483045022100b988a858e2982e2daaf0755b37ad46775d6132057934877a5badc91dee2f66ff022020b967c1a2f0916007662ec609987e951baafa6d4fda23faaad70715611d6a2501210254a2dccd8c8832d4677dc6f0e562eaaa5d11feb9f1de2c50a33832e7c6190796ffffffff9e22eb1b3f24c260187d716a8a6c2a7efb5af14a30a4792a6eeac3643172379c000000006a47304402207df07f0cd30dca2cf7bed7686fa78d8a37fe9c2254dfdca2befed54e06b779790220684417b8ff9f0f6b480546a9e90ecee86a625b3ea1e4ca29b080da6bd6c5f67e01210254a2dccd8c8832d4677dc6f0e562eaaa5d11feb9f1de2c50a33832e7c6190796ffffffff1123df3bfb503b59769731da103d4371bc029f57979ebce68067768b958091a1000000006a47304402207a016023c2b0c4db9a7d4f9232fcec2193c2f119a69125ad5bcedcba56dd525e02206a734b3a321286c896759ac98ebfd9d808df47f1ce1fbfbe949891cc3134294701210254a2dccd8c8832d4677dc6f0e562eaaa5d11feb9f1de2c50a33832e7c6190796ffffffff0200c2eb0b000000001976a914e5eb3e05efad136b1405f5c2f9adb14e15a35bb488ac88cfff1b000000001976a9144846db516db3130b7a3c92253599edec6bc9630b88ac00000000', 2080), + # one segwit input (P2TR) + ('01000000000101b5cee87f1a60915c38bb0bc26aaf2b67be2b890bbc54bb4be1e40272e0d2fe0b0000000000ffffffff025529000000000000225120106daad8a5cb2e6fc74783714273bad554a148ca2d054e7a19250e9935366f3033760000000000002200205e6d83c44f57484fd2ef2a62b6d36cdcd6b3e06b661e33fd65588a28ad0dbe060141df9d1bfce71f90d68bf9e9461910b3716466bfe035c7dbabaa7791383af6c7ef405a3a1f481488a91d33cd90b098d13cb904323a3e215523aceaa04e1bb35cdb0100000000', 617), + # one legacy input (P2PKH) + ('0100000001c336895d9fa674f8b1e294fd006b1ac8266939161600e04788c515089991b50a030000006a47304402204213769e823984b31dcb7104f2c99279e74249eacd4246dabcf2575f85b365aa02200c3ee89c84344ae326b637101a92448664a8d39a009c8ad5d147c752cbe112970121028b1b44b4903c9103c07d5a23e3c7cf7aeb0ba45ddbd2cfdce469ab197381f195fdffffff040000000000000000536a4c5058325bb7b7251cf9e36cac35d691bd37431eeea426d42cbdecca4db20794f9a4030e6cb5211fabf887642bcad98c9994430facb712da8ae5e12c9ae5ff314127d33665000bb26c0067000bb0bf00322a50c300000000000017a9145ca04fdc0a6d2f4e3f67cfeb97e438bb6287725f8750c30000000000001976a91423086a767de0143523e818d4273ddfe6d9e4bbcc88acc8465003000000001976a914c95cbacc416f757c65c942f9b6b8a20038b9b12988ac00000000', 1396), + ] + + for tx, expected_wu in txs: + tx = CTransaction.deserialize(x(tx)) + self.assertEqual(tx.calc_weight(), expected_wu) From fbbe92452a05b8bed8a02de19442621b9c448562 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 27 Apr 2023 22:48:33 +0000 Subject: [PATCH 69/82] Remove out of date years on copyright notices The notices themselves are for the most part not even required: https://opensource.com/article/20/10/copyright-notices-open-source-software Also duplicative of the information in git, which is the authoritive source. --- bitcoin/__init__.py | 2 +- bitcoin/bech32.py | 2 +- bitcoin/bloom.py | 2 +- bitcoin/core/__init__.py | 2 +- bitcoin/core/_bignum.py | 2 +- bitcoin/core/script.py | 2 +- bitcoin/core/scripteval.py | 2 +- bitcoin/core/serialize.py | 2 +- bitcoin/messages.py | 2 +- bitcoin/net.py | 2 +- bitcoin/signature.py | 2 +- bitcoin/signmessage.py | 4 ++-- bitcoin/tests/__init__.py | 2 +- bitcoin/tests/test_base58.py | 2 +- bitcoin/tests/test_bech32.py | 2 +- bitcoin/tests/test_bloom.py | 2 +- bitcoin/tests/test_checkblock.py | 2 +- bitcoin/tests/test_core.py | 2 +- bitcoin/tests/test_key.py | 2 +- bitcoin/tests/test_messages.py | 2 +- bitcoin/tests/test_net.py | 2 +- bitcoin/tests/test_ripemd160.py | 2 +- bitcoin/tests/test_rpc.py | 2 +- bitcoin/tests/test_script.py | 2 +- bitcoin/tests/test_scripteval.py | 2 +- bitcoin/tests/test_segwit.py | 2 +- bitcoin/tests/test_serialize.py | 2 +- bitcoin/tests/test_signmessage.py | 2 +- bitcoin/tests/test_transactions.py | 2 +- bitcoin/tests/test_wallet.py | 2 +- bitcoin/wallet.py | 2 +- 31 files changed, 32 insertions(+), 32 deletions(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index 90bc4a15..a8d2250e 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2018 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/bech32.py b/bitcoin/bech32.py index f140b0ae..1ef88b57 100644 --- a/bitcoin/bech32.py +++ b/bitcoin/bech32.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/bloom.py b/bitcoin/bloom.py index 50073dc3..d0e3b765 100644 --- a/bitcoin/bloom.py +++ b/bitcoin/bloom.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/core/__init__.py b/bitcoin/core/__init__.py index 538501af..ea578f9b 100644 --- a/bitcoin/core/__init__.py +++ b/bitcoin/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2017 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/core/_bignum.py b/bitcoin/core/_bignum.py index 6edd7065..9b9f11ac 100644 --- a/bitcoin/core/_bignum.py +++ b/bitcoin/core/_bignum.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/core/script.py b/bitcoin/core/script.py index aa2cff87..c0058a41 100644 --- a/bitcoin/core/script.py +++ b/bitcoin/core/script.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/core/scripteval.py b/bitcoin/core/scripteval.py index e93e8720..9e38f792 100644 --- a/bitcoin/core/scripteval.py +++ b/bitcoin/core/scripteval.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2017 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index b12c0239..ac75da34 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2018 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/messages.py b/bitcoin/messages.py index edb7beb1..b4d7604f 100644 --- a/bitcoin/messages.py +++ b/bitcoin/messages.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/net.py b/bitcoin/net.py index 4d98fdd9..7348dd74 100644 --- a/bitcoin/net.py +++ b/bitcoin/net.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/signature.py b/bitcoin/signature.py index fd1d7404..89434b7a 100644 --- a/bitcoin/signature.py +++ b/bitcoin/signature.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/signmessage.py b/bitcoin/signmessage.py index 38c20dc8..d893f07a 100644 --- a/bitcoin/signmessage.py +++ b/bitcoin/signmessage.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # @@ -57,4 +57,4 @@ def __str__(self): return self.message.decode('ascii') def __repr__(self): - return 'BitcoinMessage(%s, %s)' % (self.magic, self.message) \ No newline at end of file + return 'BitcoinMessage(%s, %s)' % (self.magic, self.message) diff --git a/bitcoin/tests/__init__.py b/bitcoin/tests/__init__.py index 770ca60a..0ce1c371 100644 --- a/bitcoin/tests/__init__.py +++ b/bitcoin/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_base58.py b/bitcoin/tests/test_base58.py index 74840205..59091034 100644 --- a/bitcoin/tests/test_base58.py +++ b/bitcoin/tests/test_base58.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_bech32.py b/bitcoin/tests/test_bech32.py index 9d7db073..06da1244 100644 --- a/bitcoin/tests/test_bech32.py +++ b/bitcoin/tests/test_bech32.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_bloom.py b/bitcoin/tests/test_bloom.py index f637600d..436a97ea 100644 --- a/bitcoin/tests/test_bloom.py +++ b/bitcoin/tests/test_bloom.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_checkblock.py b/bitcoin/tests/test_checkblock.py index a2bf9ad0..10c1c9a9 100644 --- a/bitcoin/tests/test_checkblock.py +++ b/bitcoin/tests/test_checkblock.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_core.py b/bitcoin/tests/test_core.py index 66ab16dd..ff16ca56 100644 --- a/bitcoin/tests/test_core.py +++ b/bitcoin/tests/test_core.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_key.py b/bitcoin/tests/test_key.py index 2526b948..6cb18b54 100644 --- a/bitcoin/tests/test_key.py +++ b/bitcoin/tests/test_key.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_messages.py b/bitcoin/tests/test_messages.py index b5e115dd..8b201dc2 100644 --- a/bitcoin/tests/test_messages.py +++ b/bitcoin/tests/test_messages.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_net.py b/bitcoin/tests/test_net.py index 80f6d775..74a81325 100644 --- a/bitcoin/tests/test_net.py +++ b/bitcoin/tests/test_net.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_ripemd160.py b/bitcoin/tests/test_ripemd160.py index a135b55e..df1374d9 100644 --- a/bitcoin/tests/test_ripemd160.py +++ b/bitcoin/tests/test_ripemd160.py @@ -21,4 +21,4 @@ def test_ripemd160(self): (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") ]: - self.assertEqual(ripemd160(msg).hex(), hexout) \ No newline at end of file + self.assertEqual(ripemd160(msg).hex(), hexout) diff --git a/bitcoin/tests/test_rpc.py b/bitcoin/tests/test_rpc.py index 716fb816..743db350 100644 --- a/bitcoin/tests/test_rpc.py +++ b/bitcoin/tests/test_rpc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_script.py b/bitcoin/tests/test_script.py index ca4598ff..1ca62eb0 100644 --- a/bitcoin/tests/test_script.py +++ b/bitcoin/tests/test_script.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_scripteval.py b/bitcoin/tests/test_scripteval.py index 59f12182..a1762020 100644 --- a/bitcoin/tests/test_scripteval.py +++ b/bitcoin/tests/test_scripteval.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2017 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_segwit.py b/bitcoin/tests/test_segwit.py index b2a0e1c8..76b89de0 100644 --- a/bitcoin/tests/test_segwit.py +++ b/bitcoin/tests/test_segwit.py @@ -1,4 +1,4 @@ -# Copyright (C) 2016 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_serialize.py b/bitcoin/tests/test_serialize.py index cc1056b8..2031b8ac 100644 --- a/bitcoin/tests/test_serialize.py +++ b/bitcoin/tests/test_serialize.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_signmessage.py b/bitcoin/tests/test_signmessage.py index a643f574..bfdc3efb 100644 --- a/bitcoin/tests/test_signmessage.py +++ b/bitcoin/tests/test_signmessage.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_transactions.py b/bitcoin/tests/test_transactions.py index c223b9dd..bdff08f2 100644 --- a/bitcoin/tests/test_transactions.py +++ b/bitcoin/tests/test_transactions.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/tests/test_wallet.py b/bitcoin/tests/test_wallet.py index c178cd51..68ba69f0 100644 --- a/bitcoin/tests/test_wallet.py +++ b/bitcoin/tests/test_wallet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2015 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # diff --git a/bitcoin/wallet.py b/bitcoin/wallet.py index eb5eeb98..292d6592 100644 --- a/bitcoin/wallet.py +++ b/bitcoin/wallet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2012-2014 The python-bitcoinlib developers +# Copyright (C) The python-bitcoinlib developers # # This file is part of python-bitcoinlib. # From bca1e7fcc0a086e75e913a57cdf422afa7c7b7e5 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 27 Apr 2023 22:54:25 +0000 Subject: [PATCH 70/82] Update guidance on Core RPC compatibility --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58e11fcb..59c79f05 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ focus on providing tools to manipulate the internals of how Bitcoin works. sudo apt-get install libssl-dev -The RPC interface, `bitcoin.rpc`, is designed to work with Bitcoin Core v0.16.0. +The RPC interface, `bitcoin.rpc`, should work with Bitcoin Core v24.0 or later. Older versions may work but there do exist some incompatibilities. From 88907a8c3b235fe263c3955c449a6cb2802f8fa2 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 27 Apr 2023 22:54:59 +0000 Subject: [PATCH 71/82] Minor: formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59c79f05..e112c0df 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ system, you can run unit tests for multiple Python versions: HTML coverage reports can then be found in the htmlcov/ subdirectory. + ## Documentation Sphinx documentation is in the "doc" subdirectory. Run "make help" from there From 4ccb7534dff949412ead8ae4bdf5b2adef441b7e Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 27 Apr 2023 22:59:25 +0000 Subject: [PATCH 72/82] Release v0.12.1 --- bitcoin/__init__.py | 2 +- release-notes.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index a8d2250e..c9330107 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.12.0' +__version__ = '0.12.1' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 91e51cd3..8258fc78 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,10 @@ # python-bitcoinlib release notes +## v0.12.1 + +* Added `calc_weight()` method. +* (Hopefully) resolved failure to find `libeay32` on Windows. + ## v0.12.0 * `CECKey` now rejects secrets that aren't exactly 32 bytes From 51eeb58e04b07bf2b25f2be20293fa182cf0f93a Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Sat, 3 Jun 2023 18:27:17 +0000 Subject: [PATCH 73/82] Release v0.12.2 --- bitcoin/__init__.py | 2 +- release-notes.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index c9330107..dcb02de9 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -15,7 +15,7 @@ # Note that setup.py can break if __init__.py imports any external # dependencies, as these might not be installed when setup.py runs. In this # case __version__ could be moved to a separate version.py and imported here. -__version__ = '0.12.1' +__version__ = '0.12.2' class MainParams(bitcoin.core.CoreMainParams): MESSAGE_START = b'\xf9\xbe\xb4\xd9' diff --git a/release-notes.md b/release-notes.md index 8258fc78..5d5b14ee 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,5 +1,9 @@ # python-bitcoinlib release notes +## v0.12.2 + +* Fixed RPC cookie location for testnet + ## v0.12.1 * Added `calc_weight()` method. From 173a73e74781dcd2edce120d8685550a88a73189 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 8 Dec 2023 10:33:55 +0000 Subject: [PATCH 74/82] Update petertodd DNS seeds --- bitcoin/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bitcoin/__init__.py b/bitcoin/__init__.py index dcb02de9..8af1b1bb 100644 --- a/bitcoin/__init__.py +++ b/bitcoin/__init__.py @@ -25,6 +25,7 @@ class MainParams(bitcoin.core.CoreMainParams): ('bluematt.me', 'dnsseed.bluematt.me'), ('dashjr.org', 'dnsseed.bitcoin.dashjr.org'), ('bitcoinstats.com', 'seed.bitcoinstats.com'), + ('petertodd.org', 'seed.btc.petertodd.net'), ('xf2.org', 'bitseed.xf2.org'), ('bitcoin.jonasschnelli.ch', 'seed.bitcoin.jonasschnelli.ch')) BASE58_PREFIXES = {'PUBKEY_ADDR':0, @@ -37,7 +38,7 @@ class TestNetParams(bitcoin.core.CoreTestNetParams): DEFAULT_PORT = 18333 RPC_PORT = 18332 DNS_SEEDS = (('testnetbitcoin.jonasschnelli.ch', 'testnet-seed.bitcoin.jonasschnelli.ch'), - ('petertodd.org', 'seed.tbtc.petertodd.org'), + ('petertodd.org', 'seed.tbtc.petertodd.net'), ('bluematt.me', 'testnet-seed.bluematt.me'), ('bitcoin.schildbach.de', 'testnet-seed.bitcoin.schildbach.de')) BASE58_PREFIXES = {'PUBKEY_ADDR':111, From 2fabb36e9c5f6951392ae9074eec63127fbd48a6 Mon Sep 17 00:00:00 2001 From: mhh Date: Mon, 6 May 2024 15:38:05 +0200 Subject: [PATCH 75/82] Add HTTPS to supported RPC Proxy protocols --- bitcoin/rpc.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 417dc533..7bd45a2c 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -205,11 +205,14 @@ def __init__(self, self.__service_url = service_url self.__url = urlparse.urlparse(service_url) - if self.__url.scheme not in ('http',): + if self.__url.scheme not in ('http', 'https'): raise ValueError('Unsupported URL scheme %r' % self.__url.scheme) if self.__url.port is None: - port = httplib.HTTP_PORT + if self.__url.scheme == 'https': + port = httplib.HTTPS_PORT + else: + port = httplib.HTTP_PORT else: port = self.__url.port self.__id_count = 0 @@ -223,8 +226,12 @@ def __init__(self, if connection: self.__conn = connection else: - self.__conn = httplib.HTTPConnection(self.__url.hostname, port=port, - timeout=timeout) + if self.__url.scheme == 'https': + self.__conn = httplib.HTTPSConnection(self.__url.hostname, port=port, + timeout=timeout) + else: + self.__conn = httplib.HTTPConnection(self.__url.hostname, port=port, + timeout=timeout) def _call(self, service_name, *args): self.__id_count += 1 From fc46e335e24e2aa9414ad1d1177196ff8e55ffce Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Tue, 4 Jun 2024 13:46:44 +0200 Subject: [PATCH 76/82] core: support openssl 3 In modern version of openssl (iirc from 1.1) the library `libeay32` is removed in favor of libcrypto. See for more info https://github.com/openssl/openssl/issues/10332 This commit is adding the possibility to include libcrypto too Fixing the following error on a nix shell /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/_pytest/config/__init__.py:325: PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown. Plugin: helpconfig, Hook: pytest_cmdline_parse ConftestImportFailure: OSError: libeay32: cannot open shared object file: No such file or directory (from /home/vincent/gittea/work/lampo.rs/tests/lnprototest/tests/conftest.py) For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning config = pluginmanager.hook.pytest_cmdline_parse( ImportError while loading conftest '/home/vincent/gittea/work/lampo.rs/tests/lnprototest/tests/conftest.py'. tests/conftest.py:3: in import lnprototest /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/__init__.py:15: in from .event import ( /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/event.py:16: in from .namespace import namespace /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/namespace.py:6: in from .signature import SigType /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/signature.py:5: in from .utils import check_hex, privkey_expand /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/utils/__init__.py:18: in from .bitcoin_utils import ( /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/lnprototest/utils/bitcoin_utils.py:14: in from bitcoin.wallet import CBitcoinSecret /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/bitcoin/wallet.py:23: in import bitcoin.core.key /home/vincent/.cache/pypoetry/virtualenvs/lampo-lnprototest-IQvPSNpc-py3.11/lib/python3.11/site-packages/bitcoin/core/key.py:27: in _ssl = ctypes.cdll.LoadLibrary( /nix/store/sxr2igfkwhxbagri49b8krmcqz168sim-python3-3.11.8/lib/python3.11/ctypes/__init__.py:454: in LoadLibrary return self._dlltype(name) /nix/store/sxr2igfkwhxbagri49b8krmcqz168sim-python3-3.11.8/lib/python3.11/ctypes/__init__.py:376: in __init__ self._handle = _dlopen(self._name, mode) E OSError: libeay32: cannot open shared object file: No such file or directory make[1]: *** [Makefile:11: check] Error 4 Signed-off-by: Vincenzo Palazzo --- bitcoin/core/key.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index 355d2a85..a7c9cf4f 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -26,6 +26,7 @@ _ssl = ctypes.cdll.LoadLibrary( ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or ctypes.util.find_library('libeay32') + or ctypes.cdll.LoadLibrary("libcrypto") ) _libsecp256k1_path = ctypes.util.find_library('secp256k1') From d2fe2ad2507a9d0060947615ce85b1afeeb0a454 Mon Sep 17 00:00:00 2001 From: aviv57 Date: Wed, 30 Oct 2024 03:06:54 +0200 Subject: [PATCH 77/82] core: Fix a bug with loading libcrypto Looks like the fix in https://github.com/petertodd/python-bitcoinlib/pull/301 was not working. I got the following error while trying to run `from bitcoin.rpc import Proxy` on my machine ``` File ~\OneDrive\Documents\GitHub\python-bitcoinlib\bitcoin\rpc.py:38 36 from bitcoin.core import COIN, x, lx, b2lx, CBlock, CBlockHeader, CTransaction, COutPoint, CTxOut 37 from bitcoin.core.script import CScript ---> 38 from bitcoin.wallet import CBitcoinAddress, CBitcoinSecret 40 DEFAULT_USER_AGENT = "AuthServiceProxy/0.1" 42 DEFAULT_HTTP_TIMEOUT = 30 File ~\OneDrive\Documents\GitHub\python-bitcoinlib\bitcoin\wallet.py:23 21 import bitcoin.bech32 22 import bitcoin.core ---> 23 import bitcoin.core.key 24 import bitcoin.core.script as script 27 class CBitcoinAddress(object): File ~\OneDrive\Documents\GitHub\python-bitcoinlib\bitcoin\core\key.py:27 23 import bitcoin.signature 25 import bitcoin.core.script ---> 27 _ssl = ctypes.cdll.LoadLibrary( 28 ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or ctypes.util.find_library('libeay32') 29 or ctypes.cdll.LoadLibrary("libcrypto") 30 ) 32 _libsecp256k1_path = ctypes.util.find_library('secp256k1') 33 _libsecp256k1_enable_signing = False File ~\.pyenv\pyenv-win\versions\3.12.7\Lib\ctypes\__init__.py:460, in LibraryLoader.LoadLibrary(self, name) 459 def LoadLibrary(self, name): --> 460 return self._dlltype(name) File ~\.pyenv\pyenv-win\versions\3.12.7\Lib\ctypes\__init__.py:348, in CDLL.__init__(self, name, mode, handle, use_errno, use_last_error, winmode) 343 def __init__(self, name, mode=DEFAULT_MODE, handle=None, 344 use_errno=False, 345 use_last_error=False, 346 winmode=None): 347 if name: --> 348 name = _os.fspath(name) 349 self._name = name 350 flags = self._func_flags_ TypeError: expected str, bytes or os.PathLike object, not CDLL --- bitcoin/core/key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/core/key.py b/bitcoin/core/key.py index a7c9cf4f..0f902c8c 100644 --- a/bitcoin/core/key.py +++ b/bitcoin/core/key.py @@ -26,7 +26,7 @@ _ssl = ctypes.cdll.LoadLibrary( ctypes.util.find_library('ssl.35') or ctypes.util.find_library('ssl') or ctypes.util.find_library('libeay32') - or ctypes.cdll.LoadLibrary("libcrypto") + or ctypes.util.find_library('libcrypto') ) _libsecp256k1_path = ctypes.util.find_library('secp256k1') From e5beddaba860c3af228e220801ca6f01189f52f3 Mon Sep 17 00:00:00 2001 From: ZZiigguurraatt Date: Mon, 25 Nov 2024 08:40:47 -0500 Subject: [PATCH 78/82] add createwallet rpc command --- bitcoin/rpc.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 7bd45a2c..41f6c991 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -790,6 +790,15 @@ def unlockwallet(self, password, timeout=60): r = self._call('walletpassphrase', password, timeout) return r + def createwallet(self, name): + """create a new wallet. + + name - The wallet name. + + """ + r = self._call('createwallet', name) + return r + def _addnode(self, node, arg): r = self._call('addnode', node, arg) return r From 65e8f15ad71be0f5349a4145ed81a3301b09ef02 Mon Sep 17 00:00:00 2001 From: ZZiigguurraatt Date: Mon, 25 Nov 2024 09:00:27 -0500 Subject: [PATCH 79/82] add loadwallet rpc command --- bitcoin/rpc.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bitcoin/rpc.py b/bitcoin/rpc.py index 41f6c991..25310c71 100644 --- a/bitcoin/rpc.py +++ b/bitcoin/rpc.py @@ -799,6 +799,16 @@ def createwallet(self, name): r = self._call('createwallet', name) return r + def loadwallet(self, name, load_on_startup=False): + """load a wallet. + + name - The wallet name. + load_on_startup - whether to remember to load it automatically next time bitcoind starts. + + """ + r = self._call('loadwallet', name, load_on_startup) + return r + def _addnode(self, node, arg): r = self._call('addnode', node, arg) return r From 5c0ff08f7067b172c43ad4c730d1849b366e14d4 Mon Sep 17 00:00:00 2001 From: Jesus Christ <120573631+Gudnessuche@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:35:37 +0000 Subject: [PATCH 80/82] Update serialize.py (Docstring Typo:) The closing parenthesis in the docstring should be removed. --- bitcoin/core/serialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/core/serialize.py b/bitcoin/core/serialize.py index ac75da34..ae82d503 100644 --- a/bitcoin/core/serialize.py +++ b/bitcoin/core/serialize.py @@ -26,7 +26,7 @@ def Hash(msg): - """SHA256^2)(msg) -> bytes""" + """SHA256^2(msg) -> bytes""" return hashlib.sha256(hashlib.sha256(msg).digest()).digest() def Hash160(msg): From 5ca448572279b131b32d557b06009aa98757a5c5 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 14 Mar 2025 19:50:59 +0000 Subject: [PATCH 81/82] Create python-package.yml --- .github/workflows/python-package.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 00000000..dcfa3db4 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python package + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 91e334d831fd16c60c932ad7df42c88fd6567c02 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 14 Mar 2025 19:54:07 +0000 Subject: [PATCH 82/82] Fix flake8 fail --- bitcoin/tests/fakebitcoinproxy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bitcoin/tests/fakebitcoinproxy.py b/bitcoin/tests/fakebitcoinproxy.py index ac111615..effabf4a 100644 --- a/bitcoin/tests/fakebitcoinproxy.py +++ b/bitcoin/tests/fakebitcoinproxy.py @@ -97,6 +97,7 @@ def make_blocks_from_blockhashes(blockhashes): instantiation. """ blocks = [] + previousblockhash = None for (height, blockhash) in enumerate(blockhashes): block = {"hash": blockhash, "height": height, "tx": []}