Skip to content

Commit

Permalink
Fix merkle proof
Browse files Browse the repository at this point in the history
  • Loading branch information
primal100 committed Jan 11, 2018
1 parent 708c3af commit 7ec0691
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 40 deletions.
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,53 @@ auto-detected, so no need to know in advance if the address is segwit or not:
'float skirt road remind fire antique vendor select senior latin small glide'
> seed = mnemonic_to_seed(words)
> seed
b"\xb7Z\x9b\x9b\x9c\x1bq\x81\x1b\xdc\x98\x1c\xbc\xb8\xbb\x130\xea,\xda\x14\xeb\x9bF\xafu\x88\xc2\xf9\xfc\x7f\xd0\xb0?\x9d\xf3\xa7$0Tx\xd3\xb7\x82\x87U\xe7\xcc\xdd\x16\xddd\xbf'T\t_\xdc R!x\tJ"
b'\xb7Z\x9b\x9b\x9c\x1bq\x81\x1b\xdc\x98\x1c\xbc\xb8\xbb\x130\xea,\xda\x14\xeb\x9bF\xafu\x88\xc2\xf9\xfc\x7f\xd0\xb0?\x9d\xf3\xa7$0Tx\xd3\xb7\x82\x87U\xe7\xcc\xdd\x16\xddd\xbf'T\t_\xdc R!x\t'
> electrum_privkey(seed, 0)
'5a37812b3057e44636c6e07023e16a8669e12a4365dfabbcb376ed272081d522'
electrum_privkey(seed, 300, 0)
> electrum_privkey(seed, 300, 0)
'04cf414f200cd090239f2116d90608a74eae34ae21103ca9eef7bd9579e48bed'
> electrum_privkey(seed, 0, 1) #Change address
'9ca3631f813a6f81b70fbfc4384122bfe6fb159e6f7aea2811fe968c2a39d42a'

If an application only needs to receive coins and not spend, there is no need to have a copy of the words, seeds or
private keys on the server, as if the server is hacked, the coins will be stolen. Instead, store the master public key and
generate addresses based on that. The wallet words can be used to spend the coins on a separate cold storage machine.

> import os
> from cryptos import *
> words = entropy_to_words(os.urandom(16))
> words
'shield industry dose token network define slow under omit castle dinosaur afford'
> seed = mnemonic_to_seed(words)
> seed
b'\xe1\xa2R\xddV\xd1\xed\x84\xdd\x82d\xe7\xd6\xdcII\xa4\x7f([\xc4\xaem\x0c\x8a\xe8F\x1b6\xd6\xab\xda}\x02\xa4>\x03=\x83\xae&\x14\x908\xcdc\x10U\xf9\xe7.<r~Lu\xb4\xff\xe5\xd1\x8eXOU'
> master_public_key = electrum_mpk(seed)
> master_public_key
'9ea7e2f83bbf1c3ece4b120ddc3f142117c6c9d82d1b08ad0f54a91f3f0b938c587cb3edf7d54eba1eacc205b3890296fd58ec1b155021ac4d829a1288b24ce1'

# Safe to store the mpk on the web server and generate addresses on demand:
> from cryptos import *
> coin = Bitcoin()
> master_public_key = '9ea7e2f83bbf1c3ece4b120ddc3f142117c6c9d82d1b08ad0f54a91f3f0b938c587cb3edf7d54eba1eacc205b3890296fd58ec1b155021ac4d829a1288b24ce1'
> addr = coin.electrum_address(master_public_key, 0, 0)
> addr
'1GEHJopN3F5EgZozEbu6fyp4A6CQMAvyTZ'
> change_addr = coin.electrum_address(master_public_key, 0, 1)
> change_addr
'1AF6A7YD6fJnFUHX3mcmGeHs4MN9aFjp7X'

# Then to recover the coins on the cold storage machine:
> words = 'shield industry dose token network define slow under omit castle dinosaur afford'
> seed = mnemonic_to_seed(words)
> priv = electrum_privkey(seed, 0, 0)
> priv
'1120fb42302e05fbbdd55fefb7bc3ae075ce77ae9328672ee46b1f4af1d68189'
> change_priv = electrum_privkey(seed, 0, 1)
> change_priv
'29a6f2d3de33f79bb6e39753ce2a366ff7e6034f62c2809cfe501f3bb2580fe5'
> assert privtoaddr(priv) == '1GEHJopN3F5EgZozEbu6fyp4A6CQMAvyTZ'
> assert privtoaddr(change_priv) == '1AF6A7YD6fJnFUHX3mcmGeHs4MN9aFjp7X'


### The cryptotool command line interface:

Expand Down Expand Up @@ -359,7 +399,7 @@ The arguments are the private key of the sender, the receiver's address and the
* current_block_height : () -> Latest block height
* block_height : (txhash) -> Block height containing the txhash
* inspect : (tx_hex) -> Deserialize a transaction and decode and ins and outs

* merkle_prove : (txhash) -> Proves a transaction is valid and returns txhash, merkle siblings and block header.

### Listing of main non-coin specific commands:

Expand Down
6 changes: 3 additions & 3 deletions cryptos/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def deserialize_header(inp):


def mk_merkle_proof(header, hashes, index):
nodes = [h.decode('hex')[::-1] for h in hashes]
nodes = [safe_from_hex(h)[::-1] for h in hashes]
if len(nodes) % 2 and len(nodes) > 2:
nodes.append(nodes[-1])
layers = [nodes]
Expand All @@ -40,11 +40,11 @@ def mk_merkle_proof(header, hashes, index):
nodes = newnodes
layers.append(nodes)
# Sanity check, make sure merkle root is valid
assert nodes[0][::-1].encode('hex') == header['merkle_root']
assert bytes_to_hex_string(nodes[0][::-1]) == header['merkle_root']
merkle_siblings = \
[layers[i][(index >> i) ^ 1] for i in range(len(layers)-1)]
return {
"hash": hashes[index],
"siblings": [x[::-1].encode('hex') for x in merkle_siblings],
"siblings": [bytes_to_hex_string(x[::-1]) for x in merkle_siblings],
"header": header
}
30 changes: 28 additions & 2 deletions cryptos/coins/base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from ..transaction import *
from ..deterministic import electrum_pubkey
from ..blocks import mk_merkle_proof
from ..main import *
from ..explorers import blockchain
from ..py3specials import *
from ..py2specials import *


class BaseCoin(object):
"""
Base implementation of crypto coin class
Expand Down Expand Up @@ -90,6 +91,14 @@ def privtoaddr(self, privkey):
"""
return privtoaddr(privkey, magicbyte=self.magicbyte)

def electrum_address(self, masterkey, n, for_change=0):
pubkey = electrum_pubkey(masterkey, n, for_change=for_change)
return self.pubtoaddr(pubkey)

def electrum_address_segwit(self, masterkey, n, for_change=0):
pubkey = electrum_pubkey(masterkey, n, for_change=for_change)
return self.pubtop2w(pubkey)

def is_address(self, addr):
"""
Check if addr is a valid address for this chain
Expand Down Expand Up @@ -409,6 +418,9 @@ def block_height(self, txhash):
def current_block_height(self):
return self.explorer.current_block_height(coin_symbol=self.coin_symbol)

def block_info(self, height):
return self.explorer.block_info(height, coin_symbol=self.coin_symbol)

def inspect(self, tx):
if not isinstance(tx, dict):
tx = deserialize(tx)
Expand All @@ -431,4 +443,18 @@ def inspect(self, tx):
'fee': isum - osum,
'outs': outs,
'ins': ins
}
}

def merkle_prove(self, txhash):
"""
Prove that information an explorer returns about a transaction in the blockchain is valid. Only run on a
tx with at least 1 confirmation.
"""
blocknum = self.block_height(txhash)
blockinfo = self.block_info(blocknum)
hashes = blockinfo.pop('tx_hashes')
try:
i = hashes.index(txhash)
except ValueError:
raise Exception("Merkle proof failed because transaction %s is not part of the main chain" % txhash)
return mk_merkle_proof(blockinfo, hashes, i)
10 changes: 0 additions & 10 deletions cryptos/composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,3 @@ def sign_coinvault_tx(tx, priv):
scr = [None] + filter(lambda x: x, scr[1:-1])[:k] + [scr[-1]]
txobj['ins'][j]['script'] = serialize_script(scr)
return serialize(txobj)

# Will be added again after required explorer requests done (1/3 so far)
"""
def merkle_prove(txhash):
blocknum = str(get_block_height(txhash))
header = get_block_header_data(blocknum)
hashes = get_txs_in_block(blocknum)
i = hashes.index(txhash)
return mk_merkle_proof(header, hashes, i)
"""
4 changes: 2 additions & 2 deletions cryptos/deterministic.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def electrum_pubkey(masterkey, n, for_change=0):
# seed/stretched seed/pubkey -> address (convenience method)


def electrum_address(masterkey, n, for_change=0, version=0):
return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), version)
def electrum_address(masterkey, n, for_change=0, magicbyte=0):
return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), magicbyte)

# Given a master public key, a private key from that wallet and its index,
# cracks the secret exponent which can be used to generate all other private
Expand Down
21 changes: 21 additions & 0 deletions cryptos/explorers/base_insight.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
utxo_url = "%s/addrs/%s/utxo"
fetchtx_url = "%s/tx/%s"
current_block_height_url = "%s/status?q=getInfo"
block_hash_by_height_url = "%s/block-index/%s"
block_info_url = "%s/block/%s"

def unspent(base_url, *args):

Expand Down Expand Up @@ -81,6 +83,25 @@ def block_height(base_url, txhash):
tx = fetchtx(base_url, txhash)
return tx.get('blockheight', None) or tx.get('height', None)

def block_info(base_url, height):
url = block_hash_by_height_url % (base_url, height)
response = requests.get(url)
blockhash = response.json()['blockHash']
url = block_info_url % (base_url, blockhash)
response = requests.get(url)
data = response.json()
return {
'version': data['version'],
'hash': data['hash'],
'prevhash': data['previousblockhash'],
'timestamp': data['time'],
'merkle_root': data['merkleroot'],
'bits': data['bits'],
'nonce': data['nonce'],
'tx_hashes': data['tx']
}


def current_block_height(base_url):
url = current_block_height_url % base_url
response = requests.get(url)
Expand Down
18 changes: 18 additions & 0 deletions cryptos/explorers/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def get_url(coin_symbol):
fetchtx_url = "%s/rawtx/%s?format=json"
block_height_url = "%s/block-height/%s?format=json"
latest_block_url = "%s/latestblock"
block_info_url = "%s/rawblock/%s"

def unspent(*args, coin_symbol="BTC"):

Expand Down Expand Up @@ -97,6 +98,23 @@ def block_height(txhash, coin_symbol="BTC"):
tx = fetchtx(txhash,coin_symbol=coin_symbol)
return tx['block_height']

def block_info(height, coin_symbol="BTC"):
base_url = get_url(coin_symbol)
url = block_height_url % (base_url, height)
response = requests.get(url)
blocks = response.json()['blocks']
data = list(filter(lambda d: d['main_chain'], blocks))[0]
return {
'version': data['ver'],
'hash': data['hash'],
'prevhash': data['prev_block'],
'timestamp': data['time'],
'merkle_root': data['mrkl_root'],
'bits': data['bits'],
'nonce': data['nonce'],
'tx_hashes': [t['hash'] for t in data['tx']]
}

def current_block_height(coin_symbol="BTC"):
base_url = get_url(coin_symbol)
url = latest_block_url % base_url
Expand Down
6 changes: 5 additions & 1 deletion cryptos/explorers/blockdozer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ def block_height(tx, coin_symbol="bcc"):

def current_block_height(coin_symbol="bcc"):
base_url_for_coin = base_url % coin_symbol
return insight.current_block_height(base_url_for_coin)
return insight.current_block_height(base_url_for_coin)

def block_info(height, coin_symbol="bcc"):
base_url_for_coin = base_url % coin_symbol
return insight.block_info(base_url_for_coin, height)
6 changes: 5 additions & 1 deletion cryptos/explorers/dash_siampm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ def block_height(tx, coin_symbol="DASH"):

def current_block_height(coin_symbol="DASH"):
base_url = get_url(coin_symbol)
return insight.current_block_height(base_url)
return insight.current_block_height(base_url)

def block_info(height, coin_symbol="DASH"):
base_url = get_url(coin_symbol)
return insight.block_info(base_url, height)
17 changes: 17 additions & 0 deletions cryptos/explorers/sochain.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
tx_details_url = base_url + "tx/%s/%s"
tx_inputs_url = base_url + "get_tx_inputs/%s/%s"
network_url = base_url + "get_info/%s"
block_url = base_url + "block/%s/%s"

def unspent(addr, coin_symbol="LTC"):
url = utxo_url % (coin_symbol, addr)
Expand Down Expand Up @@ -66,6 +67,22 @@ def block_height(txhash, coin_symbol="LTC"):
tx = gettxdetails(txhash,coin_symbol=coin_symbol)
return tx['block_no']

def block_info(height, coin_symbol="LTC"):
url = block_url % (coin_symbol, height)
response = requests.get(url)
data = response.json()['data']
return {
'version': data['version'],
'hash': data['blockhash'],
'prevhash': data['previous_blockhash'],
'timestamp': data['time'],
'merkle_root': data['merkleroot'],
'bits': data['bits'],
'nonce': data['nonce'],
'tx_hashes': [t['txid'] for t in data['txs']]
}


def current_block_height(coin_symbol="LTC"):
url = network_url % coin_symbol
response = requests.get(url)
Expand Down
19 changes: 2 additions & 17 deletions cryptos/mnemonic.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import hashlib
import os.path
import binascii
import random
from .py2specials import *
from .py3specials import *
Expand Down Expand Up @@ -77,7 +74,8 @@ def words_verify(words,wordlist=wordlist_english):
ebytes=_eint_to_bytes(eint,entropy_bits)
return csint == entropy_cs(ebytes)

def mnemonic_to_seed(mnemonic_phrase,passphrase=b''):
def mnemonic_to_seed(mnemonic_phrase,passphrase=''):
passphrase = from_string_to_bytes(passphrase)
if isinstance(mnemonic_phrase, (list, tuple)):
mnemonic_phrase = ' '.join(mnemonic_phrase)
mnemonic_phrase = from_string_to_bytes(mnemonic_phrase)
Expand Down Expand Up @@ -118,16 +116,3 @@ def words_mine(prefix,entbits,satisfunction,wordlist=wordlist_english,randombits

return entropy_to_words(eint_to_bytes(pint+dint,entbits))

if __name__=="__main__":
import json
testvectors=json.load(open('vectors.json','r'))
passed=True
for v in testvectors['english']:
ebytes=binascii.unhexlify(v[0])
w=entropy_to_words(ebytes)
seed=mnemonic_to_seed(w,passphrase='TREZOR')
passed = passed and w==v[1]
passed = passed and binascii.hexlify(seed)==v[2]
print("Tests %s." % ("Passed" if passed else "Failed"))


2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from setuptools import setup, find_packages

setup(name='cryptos',
version='1.35',
version='1.36',
description='Python Crypto Coin Tools',
long_description=open('README.md').read(),
author='Paul Martin',
Expand Down
Loading

0 comments on commit 7ec0691

Please sign in to comment.