commit 480f603b2c9a988f5407e47105655152f7f58376
parent f4e9cfba198c30c6f4209351a0966cdc2a36e4f0
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date: Tue, 27 Feb 2018 16:19:48 +0000
added code to deliver merkle proofs to electrum, also added links to readme
Diffstat:
M | README.md | | | 19 | ++++++++++++------- |
A | merkleproof.py | | | 271 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | server.py | | | 32 | +++++++++++++++++++++----------- |
M | util.py | | | 326 | +------------------------------------------------------------------------------ |
4 files changed, 308 insertions(+), 340 deletions(-)
diff --git a/README.md b/README.md
@@ -4,10 +4,14 @@ Electrum Personal Server is an implementation of the Electrum server protocol
which fulfills the specific need of using the Electrum UI with full node
verification and privacy, but without the heavyweight server backend, for a
single user. It allows the user to benefit from all of Bitcoin Core's
-resource-saving features like pruning, blocksonly and disabled txindex. All
-of Electrum's feature-richness like hardware wallet integration,
-multisignature wallets, offline signing, mnemonic recovery phrases and so on
-can still be used, but backed by the user's own full node.
+resource-saving features like
+[pruning](https://bitcoin.org/en/release/v0.12.0#wallet-pruning),
+[blocksonly](https://bitcointalk.org/index.php?topic=1377345.0) and disabled
+txindex. All of Electrum's feature-richness like hardware wallet integration,
+[multisignature wallets](http://docs.electrum.org/en/latest/multisig.html),
+[offline signing](http://docs.electrum.org/en/latest/coldstorage.html),
+[mnemonic recovery phrases](https://en.bitcoin.it/wiki/Mnemonic_phrase)
+and so on can still be used, but backed by the user's own full node.
Using Electrum with Electrum Personal Server is probably the most
resource-efficient way right now to use a hardware wallet connected to your
@@ -60,9 +64,6 @@ tunnel.
This project is in alpha stages as there are several essential missing
features such as:
-* Merkle proofs are not handled, so every confirmed transaction is labelled
- `Not Verified`.
-
* The server does not support SSL so Electrum must be configured to disable it.
* Deterministic wallets and master public keys are not supported. Addresses
@@ -87,3 +88,6 @@ possible.
I can be contacted on freenode IRC on the `#bitcoin` and `#electrum` channels.
+## Media Coverage
+
+* https://bitcoinmagazine.com/articles/electrum-personal-server-will-give-users-full-node-security-they-need/+
\ No newline at end of file
diff --git a/merkleproof.py b/merkleproof.py
@@ -0,0 +1,271 @@
+
+import bitcoin as btc
+import binascii
+from math import ceil, log
+
+import util
+from util import hash_encode, hash_decode, Hash
+
+#lots of ideas and code taken from bitcoin core and breadwallet
+#https://github.com/bitcoin/bitcoin/blob/master/src/merkleblock.h
+#https://github.com/breadwallet/breadwallet-core/blob/master/BRMerkleBlock.c
+
+def calc_tree_width(height, txcount):
+ """Efficently calculates the number of nodes at given merkle tree height"""
+ return (txcount + (1 << height) - 1) >> height
+
+def decend_merkle_tree(hashes, flags, height, txcount, pos):
+ """Function recursively follows the flags bitstring down into the
+ tree, building up a tree in memory"""
+ flag = next(flags)
+ if height > 0:
+ #non-txid node
+ if flag:
+ left = decend_merkle_tree(hashes, flags, height-1, txcount, pos*2)
+ #bitcoin's merkle tree format has a rule that if theres an
+ # odd number of nodes in then the 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:
+ if isinstance(left, tuple):
+ right = expand_tree_hashing(left)
+ else:
+ right = left
+ return (left, right)
+ else:
+ hs = next(hashes)
+ return hs
+ else:
+ #txid node
+ hs = next(hashes)
+ if flag:
+ #for the actual transaction, also store its position with a flag
+ return "tx:" + str(pos) + ":" + hs
+ else:
+ return hs
+
+def deserialize_core_format_merkle_proof(hash_list, flag_value, txcount):
+ """Converts core's format for a merkle proof into a tree in memory"""
+ tree_depth = int(ceil(log(txcount, 2)))
+ hashes = iter(hash_list)
+ #one-liner which converts the flags value to a list of True/False bits
+ flags = (flag_value[i//8]&1 << i%8 != 0 for i in range(len(flag_value)*8))
+ try:
+ root_node = decend_merkle_tree(hashes, flags, tree_depth, txcount, 0)
+ return root_node
+ except StopIteration:
+ raise ValueError
+
+def expand_tree_electrum_format_merkle_proof(node, result):
+ """Recurse down into the tree, adding hashes to the result list
+ in depth order"""
+ left, right = node
+ if isinstance(left, tuple):
+ expand_tree_electrum_format_merkle_proof(left, result)
+ if isinstance(right, tuple):
+ expand_tree_electrum_format_merkle_proof(right, result)
+ if not isinstance(left, tuple):
+ result.append(left)
+ if not isinstance(right, tuple):
+ result.append(right)
+
+def get_node_hash(node):
+ if node.startswith("tx"):
+ return node.split(":")[2]
+ else:
+ return node
+
+def expand_tree_hashing(node):
+ """Recurse down into the tree, hashing everything and
+ returning root hash"""
+ left, right = node
+ if isinstance(left, tuple):
+ hash_left = expand_tree_hashing(left)
+ else:
+ hash_left = get_node_hash(left)
+ if isinstance(right, tuple):
+ hash_right = expand_tree_hashing(right)
+ else:
+ hash_right = get_node_hash(right)
+ return hash_encode(Hash(hash_decode(hash_left) + hash_decode(hash_right)))
+
+def convert_core_to_electrum_merkle_proof(proof):
+ """Bitcoin Core and Electrum use different formats for merkle
+ proof, this function converts from Core's format to Electrum's format"""
+ proof = binascii.unhexlify(proof)
+ pos = [0]
+ def read_as_int(bytez):
+ pos[0] += bytez
+ return btc.decode(proof[pos[0] - bytez:pos[0]][::-1], 256)
+ def read_var_int():
+ pos[0] += 1
+ val = btc.from_byte_to_int(proof[pos[0] - 1])
+ if val < 253:
+ return val
+ return read_as_int(pow(2, val - 252))
+ def read_bytes(bytez):
+ pos[0] += bytez
+ return proof[pos[0] - bytez:pos[0]]
+
+ merkle_root = proof[36:36+32]
+ pos[0] = 80
+ txcount = read_as_int(4)
+ hash_count = read_var_int()
+ hashes = [hash_encode(read_bytes(32)) for i in range(hash_count)]
+ flags_count = read_var_int()
+ flags = read_bytes(flags_count)
+
+ root_node = deserialize_core_format_merkle_proof(hashes, flags, txcount)
+ #check special case of a tree of zero height, block with only coinbase tx
+ if not isinstance(root_node, tuple):
+ root_node = root_node[5:] #remove the "tx:0:"
+ result = {"pos": 0, "merkle": [], "txid": root_node,
+ "merkleroot": hash_encode(merkle_root)}
+ return result
+
+ hashes_list = []
+ expand_tree_electrum_format_merkle_proof(root_node, hashes_list)
+ #remove the first or second element which is the txhash
+ tx = hashes_list[0]
+ if hashes_list[1].startswith("tx"):
+ tx = hashes_list[1]
+ assert(tx.startswith("tx"))
+ hashes_list.remove(tx)
+ #if the txhash was duplicated, that _is_ included in electrum's format
+ if hashes_list[0].startswith("tx"):
+ hashes_list[0] = tx.split(":")[2]
+ tx_pos, txid = tx.split(":")[1:3]
+ tx_pos = int(tx_pos)
+ result = {"pos": tx_pos, "merkle": hashes_list, "txid": txid,
+ "merkleroot": hash_encode(merkle_root)}
+ return result
+
+merkle_proof_test_vectors = [
+ #txcount 819, pos 5
+ "0300000026e696fba00f0a43907239305eed9e55824e0e376636380f000000000000000" +
+ "04f8a2ce51d6c69988029837688cbfc2f580799fa1747456b9c80ab808c1431acd0b07f" +
+ "5543201618cadcfbf7330300000b0ff1e0050fed22ca360e0935e053b0fe098f6f9e090" +
+ "f5631013361620d964fe2fd88544ae10b40621e1cd24bb4306e3815dc237f77118a45d7" +
+ "5ada9ee362314b70573732bce59615a3bcc1bbacd04b33b7819198212216b5d62d75be5" +
+ "9221ada17ba4fb2476b689cccd3be54732fd5630832a94f11fa3f0dafd6f904d43219e0" +
+ "d7de110158446b5b598bd241f7b5df4da0ebc7d30e7748d487917b718df51c681174e6a" +
+ "bab8042cc7c1c436221c098f06a56134f9247a812126d675d69c82ba1c715cfc0cde462" +
+ "fd1fbe5dc87f6b8db2b9c060fcd59a20e7fe8e921c3676937a873ff88684f4be4d015f2" +
+ "4f26af6d2cf78335e9218bcceba4507d0b4ba6cb933aa01ef77ae5eb411893ec0f74b69" +
+ "590fb0f5118ac937c02ccd47e9d90be78becd11ecf854d7d268eeb479b74d137278c0a5" +
+ "017d29e90cd5b35a4680201824fb0eb4f404e20dfeaec4d50549030b7e7e220b02eb210" +
+ "5f3d2e8bcc94d547214a9d03ff1600",
+ #txcount 47, pos 9
+ "0100000053696a625fbd16df418575bce0c4148886c422774fca5fcab80100000000000" +
+ "01532bfe4f9c4f56cd141028e5b59384c133740174b74b1982c7f01020b90ce05577c67" +
+ "508bdb051a7ec2ef942f000000076cde2eb7efa90b36d48aed612e559ff2ba638d8d400" +
+ "b14b0c58df00c6a6c33b65dc8fa02f4ca56e1f4dcf17186fa9bbd990ce150b6e2dc9e9e" +
+ "56bb4f270fe56fde6bdd73a7a7e82767714862888e6b759568fb117674ad23050e29311" +
+ "97494d457efb72efdb9cb79cd4a435724908a0eb31ec7f7a67ee03837319e098b43edad" +
+ "3be9af75ae7b30db6f4f93ba0fdd941fdf70fe8cc38982e03bd292f5bd02f28137d343f" +
+ "908c7d6417379afe8349a257af3ca1f74f623be6a416fe1aa96a8f259983f2cf32121bc" +
+ "e203955a378b3b44f132ea6ab94c7829a6c3b360c9f8da8e74027701",
+ #txcount 2582, pos 330
+ "000000206365d5e1d8b7fdf0b846cfa902115c1b8ced9dd49cb17800000000000000000" +
+ "01032e829e1f9a5a09d0492f9cd3ec0762b7facea555989c3927d3d975fd4078c771849" +
+ "5a45960018edd3b9e0160a00000dfe856a7d5d77c23ebf85c68b5eb303d85e56491ed6d" +
+ "204372625d0b4383df5a44d6e46d2db09d936b9f5d0b53e0dbcb3efb7773d457369c228" +
+ "fd1ce6e11645e366a58b3fc1e8a7c916710ce29a87265a6729a3b221b47ea9c8e6f4870" +
+ "7b112b8d67e5cfb3db5f88b042dc49e4e5bc2e61c28e1e0fbcba4c741bb5c75cac58ca0" +
+ "4161a7377d70f3fd19a3e248ed918c91709b49afd3760f89ed2fefbcc9c23447ccb40a2" +
+ "be7aba22b07189b0bf90c62db48a9fe37227e12c7af8c1d4c22f9f223530dacdd5f3ad8" +
+ "50ad4badf16cc24049a65334f59bf28c15cecda1a4cf3f2937bd70ee84f66569ce8ef95" +
+ "1d50cca46d60337e6c697685b38ad217967bbe6801d03c44fcb808cd035be31888380a2" +
+ "df1be14b6ff100de83cab0dce250e2b40ca3b47e8309f848646bee63b6185c176d84f15" +
+ "46a482e7a65a87d1a2d0d5a2b683e2cae0520df1e3525a71d71e1f551abd7d238c3bcb4" +
+ "ecaeea7d5988745fa421a8604a99857426957a2ccfa7cd8df145aa8293701989dd20750" +
+ "5923fcb339843944ce3d21dc259bcda9c251ed90d4e55af2cf5b15432050084f513ac74" +
+ "c0bdd4b6046fb70100",
+ #txcount 2861, pos 2860, last tx with many duplicated nodes down the tree
+ "00000020c656c90b521a2bbca14174f2939b882a28d23d86144b0e00000000000000000" +
+ "0cf5185a8e369c3de5f15e039e777760994fd66184b619d839dace3aec9953fd6d86159" +
+ "5ac1910018ee097a972d0b0000078d20d71c3114dbf52bb13f2c18f987891e8854d2d29" +
+ "f61c0b3d3256afcef7c0b1e6f76d6431f93390ebd28dbb81ad7c8f08459e85efeb23cc7" +
+ "2df2c5612215bf53dd4ab3703886bc8c82cb78ba761855e495fb5dc371cd8fe25ae974b" +
+ "df42269e267caf898a9f34cbf2350eaaa4afbeaea70636b5a3b73682186817db5b33290" +
+ "bd5c696bd8d0322671ff70c5447fcd7bdc127e5b84350c6b14b5a3b86b424d7db38d39f" +
+ "171f57e255a31c6c53415e3d65408b6519a40aacc49cad8e70646d4cb0d23d4a63068e6" +
+ "c220efc8a2781e9e774efdd334108d7453043bd3c8070d0e5903ad5b07",
+ #txcount 7, pos 6, duplicated entry in the last depth, at tx level
+ "0100000056e02c6d3278c754e0699517834741f7c4ad3dcbfeb7803a346200000000000" +
+ "0af3bdd5dd465443fd003e9281455e60aae573dd4d46304d7ba17276ea33d506488cbb4" +
+ "4dacb5001b9ebb193b0700000003cd3abb2eb7583165f36b56add7268be9027ead4cc8f" +
+ "888ec650d3b1c1f4de28a0ff7c8b463b2042d09598f0e5e5905de362aa1cf75252adc22" +
+ "719b8e1bc969adcfbc4782b8eafc9352263770b91a0f189ae051cbe0e26046c2b14cf3d" +
+ "8be0bc40135",
+ #txcount 6, pos 5, duplicated entry in the last-but-one depth
+ "01000000299edfd28524eae4fb6012e4087afdb6e1b912db85e612374b0300000000000" +
+ "0e16572394f8578a47bf36e15cd16faa5e3b9e18805cf4e271ae4ef95aa8cea7eb31fa1" +
+ "4e4b6d0b1a42857d960600000003f52b45ed953924366dab3e92707145c78615b639751" +
+ "ecb7be1c5ecc09b592ed588ca0e15a89e9049a2dbcadf4d8362bd1f74a6972f176617b5" +
+ "8a5466c8a4121fc3e2d6fa66c8637b387ef190ab46d6e9c9dae4bbccd871c72372b3dbc" +
+ "6edefea012d",
+ #txcount 5, pos 4, duplicated on the last and second last depth
+ "010000004d891de57908d89e9e5585648978d7650adc67e856c2d8c18c1800000000000" +
+ "04746fd317bffecd4ffb320239caa06685bafe0c1b5463b24d636e45788796657843d1b" +
+ "4d4c86041be68355c40500000002d8e26c89c46477f2407d866d2badbd98e43e732a670" +
+ "e96001faf1744b27e5fdd018733d72e31a2d6a0d94f2a3b35fcc66fb110c40c5bbff82b" +
+ "f87606553d541d011d",
+ #txcount 2739, pos 0, coinbase tx
+ "000000209f283da030c6e6d0ff5087a87c430d140ed6b4564fa34d00000000000000000" +
+ "0ec1513723e3652c6b8e777c41eb267ad8dd2025e85228840f5cfca7ffe1fb331afff8a" +
+ "5af8e961175e0f7691b30a00000df403e21a4751fbd52457f535378ac2dcf111199e9ea" +
+ "6f78f6c2663cb99b58203438d8f3b26f7f2804668c1df7d394a4726363d4873b2d85b71" +
+ "2e44cf4f5e4f33f22a8f3a1672846bd7c4570c668e6ee12befda23bfa3d0fcd30b1b079" +
+ "19b01c40b1e31b6d34fcdbb99539d46eb97a3ae15386f1ab0f28ecacadd9fc3fa4ce49a" +
+ "1a1839d815229f54036c8a3035d91e80e8dc127b62032b4e652550b4fc0aee0f6e85a14" +
+ "307d85ed9dde62acff9a0f7e3b52370a10d6c83ec13a0b4a8fafe87af368a167d7e9b63" +
+ "3b84b6ea65f1ce5e8ccc1840be0a4dab0099e25afccc7f2fdbda54cd65ecbac8d9a550c" +
+ "108b4e18d3af59129d373fde4c80848858fd6f7fc1e27387a38833473ca8a47729fa6e1" +
+ "cc14b584c14dad768108ff18cc6acdc9c31d32dc71c3c80856664a3fff870fe419a59aa" +
+ "9033356590475d36086f0b3c0ece34c0f3756675c610fb980ff3363af6f9c0918a7c677" +
+ "23371849de9c1026515c2900a80b3aee4f2625c8f48cd5eb967560ee8ebe58a8d41c331" +
+ "f6d5199795735d4f0494bdf592d166fa291062733619f0f133605087365639de2d9d5d6" +
+ "921f4b4204ff1f0000",
+ #txcount 1, pos 0, coinbase tx in an empty block, tree with height 1
+ "010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d0000000" +
+ "0112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd66" +
+ "49ffff001d1e2de5650100000001112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98" +
+ "173ac9799a852fa39add30101",
+ #txcount 2, pos 1, tree with height 2
+ "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc30010000000" +
+ "02793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b" +
+ "4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa2" +
+ "1aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a190" +
+ "2360baa5e4328e0105"
+]
+
+def test():
+ for proof in merkle_proof_test_vectors:
+ try:
+ electrum_proof = convert_core_to_electrum_merkle_proof(proof)
+ #print(electrum_proof)
+ implied_merkle_root = util.hash_merkle_root(
+ electrum_proof["merkle"], electrum_proof["txid"],
+ electrum_proof["pos"])
+ assert implied_merkle_root == electrum_proof["merkleroot"]
+ except ValueError:
+ import traceback
+ traceback.print_exc()
+ assert 0
+ print("All tests passed")
+
+'''
+proof = "010000004e24a2880cd72d9bde7502087bd3756819794dc7548f68dd68dc3001000000002793fce9cdf91b4f84760571bf6009d5f0ffaddbfdc9234ef58a036096092117b10f4b4cfd68011c903e350b0200000002ee50562fc6f995eff2df61be0d5f943bac941149aa21aacb32adc130c0f17d6a2077a642b1eabbc5120e31566a11e2689aa4d39b01cce9a1902360baa5e4328e0105"
+def chunks(d, n):
+ return [d[x:x + n] for x in range(0, len(d), n)]
+#print(proof)
+print("\" + \n\"".join(chunks(proof, 71)))
+'''
+
+if __name__ == "__main__":
+ test()
+
diff --git a/server.py b/server.py
@@ -56,7 +56,7 @@ from configparser import ConfigParser, NoSectionError
from decimal import Decimal
from jsonrpc import JsonRpc, JsonRpcError
-import util
+import util, merkleproof
ADDRESSES_LABEL = "electrum-watchonly-addresses"
@@ -143,18 +143,28 @@ def handle_query(sock, line, rpc, address_history):
#protocol documentation
#https://github.com/kyuupichan/electrumx/blob/master/docs/PROTOCOL.rst
if method == "blockchain.transaction.get":
- try:
- tx = rpc.call("gettransaction", [query["params"][0]])
- send_response(sock, query, tx["hex"])
- except JsonRpcError:
- debug("Unable to get tx " + query["params"][0])
+ tx = rpc.call("gettransaction", [query["params"][0]])
+ send_response(sock, query, tx["hex"])
elif method == "blockchain.transaction.get_merkle":
- #we dont support merkle proofs yet, but we must reply with
- #something otherwise electrum will disconnect from us
- #so reply with an invalid proof
- #https://github.com/spesmilo/electrum/blob/c8e67e2bd07efe042703bc1368d499c5e555f854/lib/verifier.py#L74
txid = query["params"][0]
- reply = {"block_height": 1, "pos": 0, "merkle": [txid]}
+ try:
+ tx = rpc.call("gettransaction", [txid])
+ core_proof = rpc.call("gettxoutproof", [[txid], tx["blockhash"]])
+ electrum_proof = merkleproof.convert_core_to_electrum_merkle_proof(
+ core_proof)
+ implied_merkle_root = util.hash_merkle_root(
+ electrum_proof["merkle"], txid, electrum_proof["pos"])
+ if implied_merkle_root != electrum_proof["merkleroot"]:
+ raise ValueError
+ txheader = get_block_header(rpc, tx["blockhash"])
+ reply = {"block_height": txheader["block_height"], "pos":
+ electrum_proof["pos"], "merkle": electrum_proof["merkle"]}
+ except (ValueError, JsonRpcError) as e:
+ log("WARNING: merkle proof failed for " + txid + " err=" + repr(e))
+ #so reply with an invalid proof which electrum handles without
+ # disconnecting us
+ #https://github.com/spesmilo/electrum/blob/c8e67e2bd07efe042703bc1368d499c5e555f854/lib/verifier.py#L74
+ reply = {"block_height": 1, "pos": 0, "merkle": [txid]}
send_response(sock, query, reply)
elif method == "blockchain.scripthash.subscribe":
scrhash = query["params"][0]
diff --git a/util.py b/util.py
@@ -1,7 +1,6 @@
import bitcoin as btc
import hashlib, binascii
-from math import ceil, log
## stuff copied from electrum's source
@@ -31,12 +30,6 @@ def script_to_scripthash(script):
h = sha256(bytes.fromhex(script))[0:32]
return bh2u(bytes(reversed(h)))
-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
def get_status_electrum(h):
@@ -66,7 +59,6 @@ def hash_merkle_root(merkle_s, target_hash, pos):
## end of electrum copypaste
-
def script_to_address(script):
#TODO why is this even here? its not used anywhere, maybe old code
#TODO bech32 addresses
@@ -74,319 +66,9 @@ def script_to_address(script):
# vbyte doesnt matter
return btc.script_to_address(script, 0x00)
-def calc_tree_width(height, txcount):
- return (txcount + (1 << height) - 1) >> height
-
-#follow the flags down into the tree, building up the datastructure
-def decend_merkle_tree(hashes, flags, height, txcount, pos):
- flag = next(flags)
- print("f=" + str(flag) + " height=" + str(height) + " txc=" +
- str(txcount) + " pos=" + str(pos) + " width=" +
- str(calc_tree_width(height, txcount)))
- if height > 0:
- #non-txid node
- if flag:
- 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:
- 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)
- return hs
- else:
- #txid node
- hs = next(hashes)
- #hs = hs[:4] + '...' + hs[-4:]
- #print(hs)
- if flag:
- return "tx:" + str(pos) + ":" + hs
- else:
- return hs
-
-def deserialize_core_format_merkle_proof(hash_list, flag_value, txcount):
- tree_depth = int(ceil(log(txcount, 2)))
- hashes = iter(hash_list)
- #one-liner which converts the flags value to a list of True/False bits
- flags = (flag_value[i//8]&1 << i%8 != 0 for i in range(len(flag_value)*8))
- try:
- root_node = decend_merkle_tree(hashes, flags, tree_depth, txcount, 0)
- return root_node
- except StopIteration:
- raise ValueError
-
-#recurse down into the tree, adding hashes to the result list in depth order
-def expand_tree_electrum_format(node, result):
- left, right = node
- if isinstance(left, tuple):
- expand_tree_electrum_format(left, result)
- if isinstance(right, tuple):
- expand_tree_electrum_format(right, result)
- if not isinstance(left, tuple):
- result.append(left)
- 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):
- proof = binascii.unhexlify(proof)
- pos = [0]
- def read_as_int(bytez):
- pos[0] += bytez
- return btc.decode(proof[pos[0] - bytez:pos[0]][::-1], 256)
- def read_var_int():
- pos[0] += 1
- val = btc.from_byte_to_int(proof[pos[0] - 1])
- if val < 253:
- return val
- return read_as_int(pow(2, val - 252))
- def read_bytes(bytez):
- pos[0] += bytez
- return proof[pos[0] - bytez:pos[0]]
-
- pos[0] = 80
- txcount = read_as_int(4)
- hash_count = read_var_int()
- hashes = [binascii.hexlify(read_bytes(32)[::-1]).decode()
- for i in range(hash_count)]
- flags_count = read_var_int()
- flags = read_bytes(flags_count)
-
- print(hashes)
- print([flags[i//8]&1 << i%8 != 0 for i in range(len(flags)*8)])
- print(txcount)
-
- root_node = deserialize_core_format_merkle_proof(hashes, flags, txcount)
- print(root_node)
- hashes_list = []
- expand_tree_electrum_format(root_node, hashes_list)
-
- #remove the first or second element which is the txhash
- tx = hashes_list[0]
- if hashes_list[1].startswith("tx"):
- tx = hashes_list[1]
- assert(tx.startswith("tx"))
- hashes_list.remove(tx)
- #if the txhash was duplicated, that is included in electrum's format
- if hashes_list[0].startswith("tx"):
- hashes_list[0] = tx.split(":")[2]
- pos, txid = tx.split(":")[1:3]
- pos = int(pos)
- blockhash = binascii.hexlify(btc.bin_dbl_sha256(proof[:80])[::-1])
- result = {"pos": pos, "merkle": hashes_list, "txid": txid,
- "blockhash": blockhash.decode()}
- return result
-
-merkle_test_vectors = [
- {'coreproof':
- "0300000026e696fba00f0a43907239305eed9e55824e0e376636380f00000000000" +
- "000004f8a2ce51d6c69988029837688cbfc2f580799fa1747456b9c80ab808c1431" +
- "acd0b07f5543201618cadcfbf7330300000b0ff1e0050fed22ca360e0935e053b0f" +
- "e098f6f9e090f5631013361620d964fe2fd88544ae10b40621e1cd24bb4306e3815" +
- "dc237f77118a45d75ada9ee362314b70573732bce59615a3bcc1bbacd04b33b7819" +
- "198212216b5d62d75be59221ada17ba4fb2476b689cccd3be54732fd5630832a94f" +
- "11fa3f0dafd6f904d43219e0d7de110158446b5b598bd241f7b5df4da0ebc7d30e7" +
- "748d487917b718df51c681174e6abab8042cc7c1c436221c098f06a56134f9247a8" +
- "12126d675d69c82ba1c715cfc0cde462fd1fbe5dc87f6b8db2b9c060fcd59a20e7f" +
- "e8e921c3676937a873ff88684f4be4d015f24f26af6d2cf78335e9218bcceba4507" +
- "d0b4ba6cb933aa01ef77ae5eb411893ec0f74b69590fb0f5118ac937c02ccd47e9d" +
- "90be78becd11ecf854d7d268eeb479b74d137278c0a5017d29e90cd5b35a4680201" +
- "824fb0eb4f404e20dfeaec4d50549030b7e7e220b02eb2105f3d2e8bcc94d547214" +
- "a9d03ff1600",
- 'electrumproof':
- {'pos': 5, 'merkle': [
- '4b3162e39eda5ad7458a11777f23dc15386e30b44bd21c1e62400be14a5488fd',
- 'e01932d404f9d6af0d3ffa114fa9320863d52f7354bed3cc9c686b47b24fba17',
- 'e24f960d6261330131560f099e6f8f09feb053e035090e36ca22ed0f05e0f10f',
- '681cf58d717b9187d448770ed3c7eba04ddfb5f741d28b595b6b44580111ded7',
- 'a12bc8695d676d1212a847924f13566af098c02162431c7ccc4280ababe67411',
- '7a9376361c928efee7209ad5fc60c0b9b28d6b7fc85dbe1ffd62e4cdc0cf15c7',
- '33b96cbab4d00745bacebc18925e3378cfd2f66af2245f014dbef48486f83f87',
- 'ec8be70bd9e947cd2cc037c98a11f5b00f59694bf7c03e8911b45eae77ef01aa',
- 'b04f82010268a4355bcd909ed217500a8c2737d1749b47eb8e267d4d85cf1ed1',
- '9d4a2147d594cc8b2e3d5f10b22eb020e2e7b7309054504deceadf204e404feb'],
- 'txid':
- 'da1a2259be752dd6b5162221989181b7334bd0acbbc1bca31596e5bc32375770',
- 'blockhash':
- "000000000000000014491e51be24278716c24d12ec0dbadf8c5f04f7f1846f5a"}
- },
- {"coreproof":
- "0100000053696a625fbd16df418575bce0c4148886c422774fca5fcab8010000000" +
- "000001532bfe4f9c4f56cd141028e5b59384c133740174b74b1982c7f01020b90ce" +
- "05577c67508bdb051a7ec2ef942f000000076cde2eb7efa90b36d48aed612e559ff" +
- "2ba638d8d400b14b0c58df00c6a6c33b65dc8fa02f4ca56e1f4dcf17186fa9bbd99" +
- "0ce150b6e2dc9e9e56bb4f270fe56fde6bdd73a7a7e82767714862888e6b759568f" +
- "b117674ad23050e2931197494d457efb72efdb9cb79cd4a435724908a0eb31ec7f7" +
- "a67ee03837319e098b43edad3be9af75ae7b30db6f4f93ba0fdd941fdf70fe8cc38" +
- "982e03bd292f5bd02f28137d343f908c7d6417379afe8349a257af3ca1f74f623be" +
- "6a416fe1aa96a8f259983f2cf32121bce203955a378b3b44f132ea6ab94c7829a6c" +
- "3b360c9f8da8e74027701",
- "electrumproof":
- {'pos': 9, 'merkle': [
- '6fe50f274fbb569e9edce2b650e10c99bd9bfa8671f1dcf4e156caf402fac85d',
- 'aded438b099e313738e07ea6f7c71eb30e8a902457434acd79cbb9fd2eb7ef57',
- '81f202bdf592d23be08289c38cfe70df1f94dd0fba934f6fdb307bae75afe93b',
- 'b6336c6a0cf08dc5b0140b408d8d63baf29f552e61ed8ad4360ba9efb72ede6c',
- '59f2a896aae16f416abe23f6741fcaf37a259a34e8af797341d6c708f943d337',
- '748edaf8c960b3c3a629784cb96aea32f1443b8b375a9503e2bc2121f32c3f98'],
- 'txid':
- 'd494741931290e0523ad747611fb6895756b8e886248716727e8a7a773dd6bde',
- "blockhash":
- "000000000000028113c80cc4be7058ab80a7767329d0253558d81d709f62ca40"}
- },
- {"coreproof":
- "000000206365d5e1d8b7fdf0b846cfa902115c1b8ced9dd49cb1780000000000000" +
- "000001032e829e1f9a5a09d0492f9cd3ec0762b7facea555989c3927d3d975fd407" +
- "8c7718495a45960018edd3b9e0160a00000dfe856a7d5d77c23ebf85c68b5eb303d" +
- "85e56491ed6d204372625d0b4383df5a44d6e46d2db09d936b9f5d0b53e0dbcb3ef" +
- "b7773d457369c228fd1ce6e11645e366a58b3fc1e8a7c916710ce29a87265a6729a" +
- "3b221b47ea9c8e6f48707b112b8d67e5cfb3db5f88b042dc49e4e5bc2e61c28e1e0" +
- "fbcba4c741bb5c75cac58ca04161a7377d70f3fd19a3e248ed918c91709b49afd37" +
- "60f89ed2fefbcc9c23447ccb40a2be7aba22b07189b0bf90c62db48a9fe37227e12" +
- "c7af8c1d4c22f9f223530dacdd5f3ad850ad4badf16cc24049a65334f59bf28c15c" +
- "ecda1a4cf3f2937bd70ee84f66569ce8ef951d50cca46d60337e6c697685b38ad21" +
- "7967bbe6801d03c44fcb808cd035be31888380a2df1be14b6ff100de83cab0dce25" +
- "0e2b40ca3b47e8309f848646bee63b6185c176d84f1546a482e7a65a87d1a2d0d5a" +
- "2b683e2cae0520df1e3525a71d71e1f551abd7d238c3bcb4ecaeea7d5988745fa42" +
- "1a8604a99857426957a2ccfa7cd8df145aa8293701989dd207505923fcb33984394" +
- "4ce3d21dc259bcda9c251ed90d4e55af2cf5b15432050084f513ac74c0bdd4b6046" +
- "fb70100",
- "electrumproof":
- {'pos': 330, 'merkle': [
- '23f2f9224c1d8cafc7127e2237fea948db620cf90b9b18072ba2abe72b0ab4cc',
- 'a08cc5ca755cbb41c7a4cbfbe0e1281ce6c25b4e9ec42d048bf8b53dfb5c7ed6',
- '37293fcfa4a1cdce158cf29bf53453a64940c26cf1ad4bad50d83a5fddac0d53',
- 'b812b10787f4e6c8a97eb421b2a329675a26879ae20c7116c9a7e8c13f8ba566',
- '1d80e6bb677921ad385b6897c6e63703d646ca0cd551f98ece6965f684ee70bd',
- 'a30cb4e250e2dcb0ca83de00f16f4be11bdfa280838831be35d08c80cb4fc403',
- 'e34516e1e61cfd28c26973453d77b7efb3bc0d3eb5d0f5b936d909dbd2466e4d',
- '3e682b5a0d2d1a7da8657a2e486a54f1846d175c18b663ee6b6448f809837eb4',
- 'a4f53d38b4d025263704d2d61e49565ed803b35e8bc685bf3ec2775d7d6a85fe',
- 'a821a45f7488597deaaeecb4bcc338d2d7ab51f5e1711da725351edf2005ae2c',
- '94439833cb3f92057520dd8919709382aa45f18dcda7cf2c7a95267485994a60',
- 'b6d4bdc074ac13f58400053254b1f52caf554e0dd91e259cdabc59c21dd2e34c'],
- 'txid':
- '4734c2c9bcef2fed890f76d3af499b70918c91ed48e2a319fdf3707d37a76141',
- "blockhash":
- "00000000000000000035c1e0b8f6c7886a5d41b685c4f0094a5b91759a5fe235"}
- }
-]
-
-#response electrum3.hachre.de {'result': {'pos': 2860, 'block_height': 503961, 'merkle': ['590e0d07c8d33b0453748d1034d3fd4e779e1e78a2c8ef20c2e66830a6d4230d', '0f1d4d6aaa71beaf8d30b7df3cd776ece4bcd1169d1550e8abfb2b3053388ac8', 'cbd44606e7d8ca49ccaa409a51b60854d6e31534c5c6315a257ef571f1398db3', '7d4d426bb8a3b5146b0c35845b7e12dc7bcd7f44c570ff712632d0d86b695cbd', '20e5e6a7eb7cf42e4d3a9ac803f160973b10da3da74d68afb8bfef04d9a46d85', '9032b3b57d81862168733b5a6b6370eaeafb4aaaea5023bf4cf3a998f8ca67e2', 'a16ed5aa6bab2c9b64e91f033aa1fdffa44270f0907aeb8eedd31840514f8f26', 'a53a1448437ac49c9f560f3e5c4a769c6295df2a04242b713d1f0747d90a8fe4', '6922f4bd74e95ae28fcd71c35dfb95e4551876ba78cb828cbc863870b34add53', 'bf152261c5f22dc73cb2fe5ee85984f0c8d71ab8db28bd0e39931f43d6766f1e', '2cbe3c851f5a58e2a407bf38bb829fde76e4fd22005b5c3124d3eff4de55c3a5', '0b7ceffc6a25d3b3c0619fd2d254881e8987f9182c3fb12bf5db14311cd7208d']}, 'method': 'blockchain.transaction.get_merkle', 'id': 32, 'jsonrpc': '2.0', 'params': ['590e0d07c8d33b0453748d1034d3fd4e779e1e78a2c8ef20c2e66830a6d4230d', 503961]}
-#proof = "00000020c656c90b521a2bbca14174f2939b882a28d23d86144b0e000000000000000000cf5185a8e369c3de5f15e039e777760994fd66184b619d839dace3aec9953fd6d861595ac1910018ee097a972d0b0000078d20d71c3114dbf52bb13f2c18f987891e8854d2d29f61c0b3d3256afcef7c0b1e6f76d6431f93390ebd28dbb81ad7c8f08459e85efeb23cc72df2c5612215bf53dd4ab3703886bc8c82cb78ba761855e495fb5dc371cd8fe25ae974bdf42269e267caf898a9f34cbf2350eaaa4afbeaea70636b5a3b73682186817db5b33290bd5c696bd8d0322671ff70c5447fcd7bdc127e5b84350c6b14b5a3b86b424d7db38d39f171f57e255a31c6c53415e3d65408b6519a40aacc49cad8e70646d4cb0d23d4a63068e6c220efc8a2781e9e774efdd334108d7453043bd3c8070d0e5903ad5b07"
-
-#has 7 txes, duplicated entry in the last depth, at tx level
-# 0000000000007d1bdd2cfd23ffb3c2bae3143772bd6577aecae9c6b29f88c2af
-#lasttx c40bbed8f34cb1c24660e2e0cb51e09a180f1ab97037265293fceab88247bccf
-#addr 15dcVuX7UT4fB74dikaAE4MXhCTkFZpV8F
-#response electrum3.hachre.de {'id': 33, 'params': ['c40bbed8f34cb1c24660e2e0cb51e09a180f1ab97037265293fceab88247bccf', 120013], 'result': {'block_height': 120013, 'pos': 6, 'merkle': ['c40bbed8f34cb1c24660e2e0cb51e09a180f1ab97037265293fceab88247bccf', 'ad69c91b8e9b7122dc2a2575cfa12a36de05595e0e8f59092d04b263b4c8f70f', '8ae24d1f1c3b0d65ec88f8c84cad7e02e98b26d7ad566bf3653158b72ebb3acd']}, 'jsonrpc': '2.0', 'method': 'blockchain.transaction.get_merkle'}
-#proof = "0100000056e02c6d3278c754e0699517834741f7c4ad3dcbfeb7803a3462000000000000af3bdd5dd465443fd003e9281455e60aae573dd4d46304d7ba17276ea33d506488cbb44dacb5001b9ebb193b0700000003cd3abb2eb7583165f36b56add7268be9027ead4cc8f888ec650d3b1c1f4de28a0ff7c8b463b2042d09598f0e5e5905de362aa1cf75252adc22719b8e1bc969adcfbc4782b8eafc9352263770b91a0f189ae051cbe0e26046c2b14cf3d8be0bc40135"
-
-#has 6 txes, duplicated entry in the last-but-one depth
-# 00000000000005163d8d16192985a3f2d0f6f44e668ad05b26f7edcd3385a37f
-# last tx eaefedc6dbb37223c771d8ccbbe4dac9e9d646ab90f17e387b63c866fad6e2c3
-# addr 1NwNmR7sd6NqxXBJMXrwt9yUms29pSDmm
-#response electrum3.hachre.de {'jsonrpc': '2.0', 'id': 33, 'method': 'blockchain.transaction.get_merkle', 'result': {'pos': 5, 'block_height': 150106, 'merkle': ['1f12a4c866548ab51766172f97a6741fbd62834ddfcadba249909ea8150eca88', 'f5a5aa78bd1f1ee5de900b7d1928864912425b67ece4a07e40af8eeb86f10d94', 'd52e599bc0ecc5e17bcb1e7539b61586c7457170923eab6d36243995ed452bf5']}, 'params': ['eaefedc6dbb37223c771d8ccbbe4dac9e9d646ab90f17e387b63c866fad6e2c3', 150106]}
-proof = "01000000299edfd28524eae4fb6012e4087afdb6e1b912db85e612374b03000000000000e16572394f8578a47bf36e15cd16faa5e3b9e18805cf4e271ae4ef95aa8cea7eb31fa14e4b6d0b1a42857d960600000003f52b45ed953924366dab3e92707145c78615b639751ecb7be1c5ecc09b592ed588ca0e15a89e9049a2dbcadf4d8362bd1f74a6972f176617b58a5466c8a4121fc3e2d6fa66c8637b387ef190ab46d6e9c9dae4bbccd871c72372b3dbc6edefea012d"
-'''
-ix = 1
-try:
- #proof = merkle_test_vectors[ix]['coreproof']
- merkleproof = convert_core_to_electrum_merkle_proof(proof)
- print(merkleproof)
-except ValueError:
- print("valueerror")
-'''
-#the function electrum uses to verify merkle branches is in verifer.py called hash_merkle_root()
-
-'''
-h1 = b"1f12a4c866548ab51766172f97a6741fbd62834ddfcadba249909ea8150eca88"
-h2 = b"eaefedc6dbb37223c771d8ccbbe4dac9e9d646ab90f17e387b63c866fad6e2c3"
-h1 = binascii.unhexlify(h1)[::-1]
-h2 = binascii.unhexlify(h2)[::-1]
-print(btc.dbl_sha256(h2 + h1))
-
-merkle_s = {'pos': 5, 'block_height': 150106, 'merkle':
-['1f12a4c866548ab51766172f97a6741fbd62834ddfcadba249909ea8150eca88',
-'f5a5aa78bd1f1ee5de900b7d1928864912425b67ece4a07e40af8eeb86f10d94',
-'d52e599bc0ecc5e17bcb1e7539b61586c7457170923eab6d36243995ed452bf5']}
-'''
-
-
-'''
-ix = 1
-electrumproof = merkle_test_vectors[ix]['electrumproof']
-print("txid = " + electrumproof['txid'])
-print("mkpf = " + hash_merkle_root(electrumproof["merkle"],
- electrumproof['txid'], electrumproof["pos"]))
-'''
-
-
-'''
- merkle_test_vectors[ix]['coreproof'])
-assert(merkleproof['pos'] ==
- merkle_test_vectors[ix]["electrumproof"]["pos"])
-assert(merkleproof['blockhash'] ==
- merkle_test_vectors[ix]["electrumproof"]["blockhash"])
-assert(len(merkleproof["merkle"]) ==
- len(merkle_test_vectors[ix]["electrumproof"]["merkle"]))
-for i in range(len(merkleproof["merkle"])):
- assert(merkleproof["merkle"][i] ==
- merkle_test_vectors[ix]["electrumproof"]["merkle"][i])
-'''
-
-'''
-def chunks(d, n):
- return [d[x:x + n] for x in range(0, len(d), n)]
-
-print(merkleproof)
-print("\" + \n\"".join(chunks(proof, 67)))
-'''
-
-
-#has 15
-# 000000000000b2847f688808836c3905fab245cf8081befb11d1422ad59be780
-#should get a block with 13 txes
-#get a block with 1tx, only the coinbase
-
-'''
-print(address_to_scripthash(addr))
-print(spkhash + " should be")
+def address_to_script(addr, rpc):
+ return rpc.call("validateaddress", [addr])["scriptPubKey"]
-print(get_status([(txhash, txheight)]))
-print(history_hash + " should be")
+def address_to_scripthash(addr, rpc):
+ return script_to_scripthash(address_to_script(addr, rpc))
-print(get_status([(r['tx_hash'], r['height']) for r in history['result']]))
-print(history_status + " should be")
-'''