tadded support for bech32 addresses, requires bitcoin core 0.16 - electrum-personal-server - Maximally lightweight electrum server for a single user HTML git clone https://git.parazyd.org/electrum-personal-server DIR Log DIR Files DIR Refs DIR README --- DIR commit f4e9cfba198c30c6f4209351a0966cdc2a36e4f0 DIR parent 726ce096461b3d5321d01d2b60cd492cae10face HTML Author: chris-belcher <chris-belcher@users.noreply.github.com> Date: Mon, 26 Feb 2018 16:15:36 +0000 added support for bech32 addresses, requires bitcoin core 0.16 Diffstat: M README.md | 8 ++++---- M config.cfg_sample | 6 +++--- M server.py | 28 +++++++++++++--------------- M util.py | 53 +++++++++++++++++++++++-------- 4 files changed, 60 insertions(+), 35 deletions(-) --- DIR diff --git a/README.md b/README.md t@@ -21,8 +21,10 @@ See also the Electrum bitcoin wallet [website](https://electrum.org/). ## How To Use -This application requires python3 and a Bitcoin full node built with wallet -capability. +This application requires python3 and a Bitcoin full node version 0.16 or +higher. Make sure you +[verify the digital signatures](https://bitcoin.stackexchange.com/questions/50185/how-to-verify-bitcoin-core-release-signing-keys) +of any binaries before running them, or compile from source. Download the latest release or clone the git repository. Enter the directory and rename the file `config.cfg_sample` to `config.cfg`, edit this file to t@@ -66,8 +68,6 @@ features such as: * Deterministic wallets and master public keys are not supported. Addresses must be imported individually. -* Bech32 bitcoin addresses are not supported. - * The Electrum server protocol has a caveat about multiple transactions included in the same block. So there may be weird behaviour if that happens. DIR diff --git a/config.cfg_sample b/config.cfg_sample t@@ -5,12 +5,12 @@ [wallets] ## Add addresses to this section -# These are just random addresses I found on a blockchain explorer +# These are just random example addresses found on a blockchain explorer # A key can be anything addr = 1DuqpoeTB9zLvVCXQG53VbMxvMkijk494n # A comma separated list is also accepted -my_test_addresses = 1KKszdQEpXgSY4EvsnGcGEzbQFmdcFwuNS,1EuEVUtQQ8hmuMHNdMdjLwjpkm6Ef7RYVk +my_test_addresses = 1KKszdQEpXgSY4EvsnGcGEzbQFmdcFwuNS,1EuEVUtQQ8hmuMHNdMdjLwjpkm6Ef7RYVk,bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq # And space separated more_test_addresses = 3Hh7QujVLqz11tiQsnUE5CSL16WEHBmiyR 1Arzu6mWZuXGTF9yR2hZhMgBJgu1Xh2bNC 1PXRLo1FQoZyF1Jhnz4qbG5x8Bo3pFpybz t@@ -23,7 +23,7 @@ password = password #how often in seconds to poll for new transactions when electrum not connected poll_interval_listening = 30 #how often in seconds to poll for new transactions when electrum is connected -poll_interval_connected = 5 +poll_interval_connected = 2 [electrum-server] #0.0.0.0 to accept connections from any IP DIR diff --git a/server.py b/server.py t@@ -57,7 +57,6 @@ from decimal import Decimal from jsonrpc import JsonRpc, JsonRpcError import util -import bitcoin as btc ADDRESSES_LABEL = "electrum-watchonly-addresses" t@@ -327,18 +326,17 @@ def run_electrum_server(hostport, rpc, address_history, unconfirmed_txes, def get_input_and_output_scriptpubkeys(rpc, txid): gettx = rpc.call("gettransaction", [txid]) - txd = btc.deserialize(gettx["hex"]) - output_scriptpubkeys = [sc['script'] for sc in txd['outs']] + txd = rpc.call("decoderawtransaction", [gettx["hex"]]) + output_scriptpubkeys = [out["scriptPubKey"]["hex"] for out in txd["vout"]] input_scriptpubkeys = [] - for ins in txd["ins"]: + for inn in txd["vin"]: try: - wallet_tx = rpc.call("gettransaction", [ins["outpoint"][ - "hash"]]) + wallet_tx = rpc.call("gettransaction", [inn["txid"]]) except JsonRpcError: #wallet doesnt know about this tx, so the input isnt ours continue - script = btc.deserialize(str(wallet_tx["hex"]))["outs"][ins[ - "outpoint"]["index"]]["script"] + input_decoded = rpc.call("decoderawtransaction", [wallet_tx["hex"]]) + script = input_decoded["vout"][inn["vout"]]["scriptPubKey"]["hex"] input_scriptpubkeys.append(script) return output_scriptpubkeys, input_scriptpubkeys, txd t@@ -346,12 +344,10 @@ def generate_new_history_element(rpc, tx, txd): if tx["confirmations"] == 0: unconfirmed_input = False total_input_value = 0 - for ins in txd["ins"]: - utxo = rpc.call("gettxout", [ins["outpoint"]["hash"], - ins["outpoint"]["index"], True]) + for inn in txd["vin"]: + utxo = rpc.call("gettxout", [inn["txid"], inn["vout"], True]) if utxo is None: - utxo = rpc.call("gettxout", [ins["outpoint"]["hash"], - ins["outpoint"]["index"], False]) + utxo = rpc.call("gettxout", [inn["txid"], inn["vout"], False]) if utxo is None: debug("utxo not found(!)") #TODO detect this and figure out how to tell t@@ -360,7 +356,8 @@ def generate_new_history_element(rpc, tx, txd): unconfirmed_input = unconfirmed_input or utxo["confirmations"] == 0 debug("total_input_value = " + str(total_input_value)) - fee = total_input_value - sum([sc["value"] for sc in txd["outs"]]) + fee = total_input_value - sum([int(Decimal(out["value"])*Decimal(1e8)) + for out in txd["vout"]]) height = -1 if unconfirmed_input else 0 new_history_element = ({"tx_hash": tx["txid"], "height": height, "fee": fee}) t@@ -505,7 +502,7 @@ def build_address_history_index(rpc, wallet_addresses): st = time.time() address_history = {} for addr in wallet_addresses: - scripthash = util.address_to_scripthash(addr) + scripthash = util.address_to_scripthash(addr, rpc) address_history[scripthash] = {'addr': addr, 'history': [], 'subscribed': False} wallet_addr_scripthashes = set(address_history.keys()) t@@ -530,6 +527,7 @@ def build_address_history_index(rpc, wallet_addresses): continue if tx["txid"] in obtained_txids: continue + debug("adding obtained tx=" + str(tx["txid"])) obtained_txids.add(tx["txid"]) #obtain all the addresses this transaction is involved with DIR diff --git a/util.py b/util.py t@@ -31,9 +31,11 @@ def script_to_scripthash(script): h = sha256(bytes.fromhex(script))[0:32] return bh2u(bytes(reversed(h))) -def address_to_scripthash(addr): - script = btc.address_to_script(addr) - return script_to_scripthash(script) +def address_to_script(addr, rpc): + return rpc.call("validateaddress", [addr])["scriptPubKey"] + +def address_to_scripthash(addr, rpc): + return script_to_scripthash(address_to_script(addr, rpc)) #the 'result' field in the blockchain.scripthash.subscribe method # reply uses this as a summary of the address t@@ -66,8 +68,10 @@ def hash_merkle_root(merkle_s, target_hash, pos): def script_to_address(script): + #TODO why is this even here? its not used anywhere, maybe old code #TODO bech32 addresses - #TODO testnet, although everything uses scripthash so the address vbyte doesnt matter + #TODO testnet, although everything uses scripthash so the address + # vbyte doesnt matter return btc.script_to_address(script, 0x00) def calc_tree_width(height, txcount): t@@ -85,23 +89,27 @@ def decend_merkle_tree(hashes, flags, height, txcount, pos): left = decend_merkle_tree(hashes, flags, height-1, txcount, pos*2) #bitcoin has a rule that if theres an odd number of nodes in # the merkle tree, the last hash is duplicated + #in the electrum format we must hash together the duplicate + # tree branch if pos*2+1 < calc_tree_width(height-1, txcount): right = decend_merkle_tree(hashes, flags, height-1, txcount, pos*2+1) else: - right = left - #TODO decend down one branch and hash it up, place in right + if isinstance(left, tuple): + right = expand_tree_hashing(left) + else: + right = left return (left, right) else: hs = next(hashes) - hs = hs[:4] + '...' + hs[-4:] - print(hs) + #hs = hs[:4] + '...' + hs[-4:] + #print(hs) return hs else: #txid node hs = next(hashes) - hs = hs[:4] + '...' + hs[-4:] - print(hs) + #hs = hs[:4] + '...' + hs[-4:] + #print(hs) if flag: return "tx:" + str(pos) + ":" + hs else: t@@ -130,6 +138,25 @@ def expand_tree_electrum_format(node, result): if not isinstance(right, tuple): result.append(right) +def deserialize_hash_node(node): + if node.startswith("tx"): + return node.split(":")[2] + else: + return node + +#recurse down into the tree, hashing everything and returning root hash +def expand_tree_hashing(node): + left, right = node + if isinstance(left, tuple): + hash_left = expand_tree_hashing(left) + else: + hash_left = deserialize_hash_node(left) + if isinstance(right, tuple): + hash_right = expand_tree_hashing(right) + else: + hash_right = deserialize_hash_node(right) + return hash_encode(Hash(hash_decode(hash_left) + hash_decode(hash_right))) + #https://github.com/bitcoin/bitcoin/blob/master/src/merkleblock.h #https://github.com/breadwallet/breadwallet-core/blob/master/BRMerkleBlock.c def convert_core_to_electrum_merkle_proof(proof): t@@ -279,20 +306,19 @@ merkle_test_vectors = [ parazyd.org:70 /git/electrum-personal-server/commit/f4e9cfba198c30c6f4209351a0966cdc2a36e4f0.gph:237: line too long