initial testnet support (petrkr)

This commit is contained in:
ThomasV 2017-01-07 16:58:23 +01:00
parent 9138cf3cb8
commit d042d6e970
8 changed files with 82 additions and 71 deletions

View file

@ -95,7 +95,7 @@ if is_bundle or is_local or is_android:
imp.load_module('electrum', *imp.find_module('lib')) imp.load_module('electrum', *imp.find_module('lib'))
imp.load_module('electrum_gui', *imp.find_module('gui')) imp.load_module('electrum_gui', *imp.find_module('gui'))
from electrum import bitcoin, network
from electrum import SimpleConfig, Network from electrum import SimpleConfig, Network
from electrum.wallet import Wallet from electrum.wallet import Wallet
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
@ -324,6 +324,10 @@ if __name__ == '__main__':
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
cmdname = config.get('cmd') cmdname = config.get('cmd')
if config.get('testnet'):
bitcoin.set_testnet()
network.set_testnet()
# run non-RPC commands separately # run non-RPC commands separately
if cmdname in ['create', 'restore']: if cmdname in ['create', 'restore']:
run_non_RPC(config) run_non_RPC(config)

View file

@ -36,6 +36,23 @@ from util import print_error, InvalidPassword
import ecdsa import ecdsa
import aes import aes
# Bitcoin network constants
TESTNET = False
ADDRTYPE_P2PKH = 0
ADDRTYPE_P2SH = 5
XPRV_HEADER = "0488ade4"
XPUB_HEADER = "0488b21e"
def set_testnet():
global ADDRTYPE_P2PKH, ADDRTYPE_P2SH
global XPRV_HEADER, XPUB_HEADER
global TESTNET
TESTNET = True
ADDRTYPE_P2PKH = 111
ADDRTYPE_P2SH = 196
XPRV_HEADER = "04358394"
XPUB_HEADER = "043587cf"
################################## transactions ################################## transactions
FEE_STEP = 10000 FEE_STEP = 10000
@ -226,11 +243,7 @@ def hash_160(public_key):
md.update(sha256(public_key)) md.update(sha256(public_key))
return md.digest() return md.digest()
def public_key_to_bc_address(public_key): def hash_160_to_bc_address(h160, addrtype):
h160 = hash_160(public_key)
return hash_160_to_bc_address(h160)
def hash_160_to_bc_address(h160, addrtype = 0):
vh160 = chr(addrtype) + h160 vh160 = chr(addrtype) + h160
h = Hash(vh160) h = Hash(vh160)
addr = vh160 + h[0:4] addr = vh160 + h[0:4]
@ -240,6 +253,15 @@ def bc_address_to_hash_160(addr):
bytes = base_decode(addr, 25, base=58) bytes = base_decode(addr, 25, base=58)
return ord(bytes[0]), bytes[1:21] return ord(bytes[0]), bytes[1:21]
def hash160_to_p2pkh(h160):
return hash_160_to_bc_address(h160, ADDRTYPE_P2PKH)
def hash160_to_p2sh(h160):
return hash_160_to_bc_address(h160, ADDRTYPE_P2SH)
def public_key_to_bc_address(public_key):
return hash160_to_p2pkh(hash_160(public_key))
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(__b58chars) == 58 assert len(__b58chars) == 58
@ -318,12 +340,14 @@ def PrivKeyToSecret(privkey):
return privkey[9:9+32] return privkey[9:9+32]
def SecretToASecret(secret, compressed=False, addrtype=0): def SecretToASecret(secret, compressed=False):
addrtype = ADDRTYPE_P2PKH
vchIn = chr((addrtype+128)&255) + secret vchIn = chr((addrtype+128)&255) + secret
if compressed: vchIn += '\01' if compressed: vchIn += '\01'
return EncodeBase58Check(vchIn) return EncodeBase58Check(vchIn)
def ASecretToSecret(key, addrtype=0): def ASecretToSecret(key):
addrtype = ADDRTYPE_P2PKH
vch = DecodeBase58Check(key) vch = DecodeBase58Check(key)
if vch and vch[0] == chr((addrtype+128)&255): if vch and vch[0] == chr((addrtype+128)&255):
return vch[1:] return vch[1:]
@ -380,19 +404,19 @@ def is_address(addr):
addrtype, h = bc_address_to_hash_160(addr) addrtype, h = bc_address_to_hash_160(addr)
except Exception: except Exception:
return False return False
if addrtype not in [0, 5]: if addrtype not in [ADDRTYPE_P2PKH, ADDRTYPE_P2SH]:
return False return False
return addr == hash_160_to_bc_address(h, addrtype) return addr == hash_160_to_bc_address(h, addrtype)
def is_p2pkh(addr): def is_p2pkh(addr):
if is_address(addr): if is_address(addr):
addrtype, h = bc_address_to_hash_160(addr) addrtype, h = bc_address_to_hash_160(addr)
return addrtype in [0] return addrtype == ADDRTYPE_P2PKH
def is_p2sh(addr): def is_p2sh(addr):
if is_address(addr): if is_address(addr):
addrtype, h = bc_address_to_hash_160(addr) addrtype, h = bc_address_to_hash_160(addr)
return addrtype in [5] return addrtype == ADDRTYPE_P2SH
def is_private_key(key): def is_private_key(key):
try: try:
@ -702,50 +726,21 @@ def _CKD_pub(cK, c, s):
return cK_n, c_n return cK_n, c_n
BITCOIN_HEADER_PRIV = "0488ade4"
BITCOIN_HEADER_PUB = "0488b21e"
TESTNET_HEADER_PRIV = "04358394"
TESTNET_HEADER_PUB = "043587cf"
BITCOIN_HEADERS = (BITCOIN_HEADER_PUB, BITCOIN_HEADER_PRIV)
TESTNET_HEADERS = (TESTNET_HEADER_PUB, TESTNET_HEADER_PRIV)
def _get_headers(testnet):
"""Returns the correct headers for either testnet or bitcoin, in the form
of a 2-tuple, like (public, private)."""
if testnet:
return TESTNET_HEADERS
else:
return BITCOIN_HEADERS
def deserialize_xkey(xkey): def deserialize_xkey(xkey):
xkey = DecodeBase58Check(xkey) xkey = DecodeBase58Check(xkey)
assert len(xkey) == 78 assert len(xkey) == 78
xkey_header = xkey[0:4].encode('hex')
# Determine if the key is a bitcoin key or a testnet key.
if xkey_header in TESTNET_HEADERS:
head = TESTNET_HEADER_PRIV
elif xkey_header in BITCOIN_HEADERS:
head = BITCOIN_HEADER_PRIV
else:
raise Exception("Unknown xkey header: '%s'" % xkey_header)
depth = ord(xkey[4]) depth = ord(xkey[4])
fingerprint = xkey[5:9] fingerprint = xkey[5:9]
child_number = xkey[9:13] child_number = xkey[9:13]
c = xkey[13:13+32] c = xkey[13:13+32]
if xkey[0:4].encode('hex') == head: if xkey[0:4].encode('hex') == XPRV_HEADER:
K_or_k = xkey[13+33:] K_or_k = xkey[13+33:]
else: else:
K_or_k = xkey[13+32:] K_or_k = xkey[13+32:]
return depth, fingerprint, child_number, c, K_or_k return depth, fingerprint, child_number, c, K_or_k
def get_xkey_name(xkey, testnet=False): def get_xkey_name(xkey):
depth, fingerprint, child_number, c, K = deserialize_xkey(xkey) depth, fingerprint, child_number, c, K = deserialize_xkey(xkey)
n = int(child_number.encode('hex'), 16) n = int(child_number.encode('hex'), 16)
if n & BIP32_PRIME: if n & BIP32_PRIME:
@ -760,38 +755,34 @@ def get_xkey_name(xkey, testnet=False):
raise BaseException("xpub depth error") raise BaseException("xpub depth error")
def xpub_from_xprv(xprv, testnet=False): def xpub_from_xprv(xprv):
depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) depth, fingerprint, child_number, c, k = deserialize_xkey(xprv)
K, cK = get_pubkeys_from_secret(k) K, cK = get_pubkeys_from_secret(k)
header_pub, _ = _get_headers(testnet) xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
return EncodeBase58Check(xpub) return EncodeBase58Check(xpub)
def bip32_root(seed, testnet=False): def bip32_root(seed):
header_pub, header_priv = _get_headers(testnet)
I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest() I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
master_k = I[0:32] master_k = I[0:32]
master_c = I[32:] master_c = I[32:]
K, cK = get_pubkeys_from_secret(master_k) K, cK = get_pubkeys_from_secret(master_k)
xprv = (header_priv + "00" + "00000000" + "00000000").decode("hex") + master_c + chr(0) + master_k xprv = (XPRV_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + chr(0) + master_k
xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK xpub = (XPUB_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + cK
return EncodeBase58Check(xprv), EncodeBase58Check(xpub) return EncodeBase58Check(xprv), EncodeBase58Check(xpub)
def xpub_from_pubkey(cK, testnet=False): def xpub_from_pubkey(cK):
header_pub, header_priv = _get_headers(testnet)
assert cK[0] in ['\x02','\x03'] assert cK[0] in ['\x02','\x03']
master_c = chr(0)*32 master_c = chr(0)*32
xpub = (header_pub + "00" + "00000000" + "00000000").decode("hex") + master_c + cK xpub = (XPUB_HEADER + "00" + "00000000" + "00000000").decode("hex") + master_c + cK
return EncodeBase58Check(xpub) return EncodeBase58Check(xpub)
def bip32_private_derivation(xprv, branch, sequence, testnet=False): def bip32_private_derivation(xprv, branch, sequence):
assert sequence.startswith(branch) assert sequence.startswith(branch)
if branch == sequence: if branch == sequence:
return xprv, xpub_from_xprv(xprv, testnet) return xprv, xpub_from_xprv(xprv)
header_pub, header_priv = _get_headers(testnet)
depth, fingerprint, child_number, c, k = deserialize_xkey(xprv) depth, fingerprint, child_number, c, k = deserialize_xkey(xprv)
sequence = sequence[len(branch):] sequence = sequence[len(branch):]
for n in sequence.split('/'): for n in sequence.split('/'):
@ -805,13 +796,12 @@ def bip32_private_derivation(xprv, branch, sequence, testnet=False):
fingerprint = hash_160(parent_cK)[0:4] fingerprint = hash_160(parent_cK)[0:4]
child_number = ("%08X"%i).decode('hex') child_number = ("%08X"%i).decode('hex')
K, cK = get_pubkeys_from_secret(k) K, cK = get_pubkeys_from_secret(k)
xprv = header_priv.decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k xprv = XPRV_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + chr(0) + k
xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
return EncodeBase58Check(xprv), EncodeBase58Check(xpub) return EncodeBase58Check(xprv), EncodeBase58Check(xpub)
def bip32_public_derivation(xpub, branch, sequence, testnet=False): def bip32_public_derivation(xpub, branch, sequence):
header_pub, _ = _get_headers(testnet)
depth, fingerprint, child_number, c, cK = deserialize_xkey(xpub) depth, fingerprint, child_number, c, cK = deserialize_xkey(xpub)
assert sequence.startswith(branch) assert sequence.startswith(branch)
sequence = sequence[len(branch):] sequence = sequence[len(branch):]
@ -821,10 +811,9 @@ def bip32_public_derivation(xpub, branch, sequence, testnet=False):
parent_cK = cK parent_cK = cK
cK, c = CKD_pub(cK, c, i) cK, c = CKD_pub(cK, c, i)
depth += 1 depth += 1
fingerprint = hash_160(parent_cK)[0:4] fingerprint = hash_160(parent_cK)[0:4]
child_number = ("%08X"%i).decode('hex') child_number = ("%08X"%i).decode('hex')
xpub = header_pub.decode('hex') + chr(depth) + fingerprint + child_number + c + cK xpub = XPUB_HEADER.decode('hex') + chr(depth) + fingerprint + child_number + c + cK
return EncodeBase58Check(xpub) return EncodeBase58Check(xpub)

View file

@ -27,6 +27,7 @@
import os import os
import util import util
import bitcoin
from bitcoin import * from bitcoin import *
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
@ -51,6 +52,7 @@ class Blockchain(util.PrintError):
def verify_header(self, header, prev_header, bits, target): def verify_header(self, header, prev_header, bits, target):
prev_hash = self.hash_header(prev_header) prev_hash = self.hash_header(prev_header)
assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')) assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))
if bitcoin.TESTNET: return
assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits')) assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits'))
_hash = self.hash_header(header) _hash = self.hash_header(header)
assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target) assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)
@ -109,6 +111,7 @@ class Blockchain(util.PrintError):
if os.path.exists(filename): if os.path.exists(filename):
return return
try: try:
if bitcoin.TESTNET: raise
import urllib, socket import urllib, socket
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
self.print_error("downloading ", self.headers_url) self.print_error("downloading ", self.headers_url)

View file

@ -38,7 +38,7 @@ from decimal import Decimal
import util import util
from util import print_msg, format_satoshis, print_stderr from util import print_msg, format_satoshis, print_stderr
import bitcoin import bitcoin
from bitcoin import is_address, hash_160_to_bc_address, hash_160, COIN, TYPE_ADDRESS from bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from transaction import Transaction from transaction import Transaction
import paymentrequest import paymentrequest
from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED from paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
@ -251,7 +251,7 @@ class Commands:
"""Create multisig address""" """Create multisig address"""
assert isinstance(pubkeys, list), (type(num), type(pubkeys)) assert isinstance(pubkeys, list), (type(num), type(pubkeys))
redeem_script = Transaction.multisig_script(pubkeys, num) redeem_script = Transaction.multisig_script(pubkeys, num)
address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) address = bitcoin.hash160_to_p2sh(hash_160(redeem_script.decode('hex')))
return {'address':address, 'redeemScript':redeem_script} return {'address':address, 'redeemScript':redeem_script}
@command('w') @command('w')
@ -746,6 +746,7 @@ def get_parser():
group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory") group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
# create main parser # create main parser
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
parents=[parent_parser], parents=[parent_parser],

View file

@ -64,6 +64,14 @@ DEFAULT_SERVERS = {
'btc.mustyoshi.com':{'t':'50001', 's':'50002'}, 'btc.mustyoshi.com':{'t':'50001', 's':'50002'},
} }
def set_testnet():
global DEFAULT_PORTS, DEFAULT_SERVERS
DEFAULT_PORTS = {'t':'51001', 's':'51002'}
DEFAULT_SERVERS = {
'14.3.140.101': DEFAULT_PORTS,
'testnet.not.fyi': DEFAULT_PORTS
}
NODES_RETRY_INTERVAL = 60 NODES_RETRY_INTERVAL = 60
SERVER_RETRY_INTERVAL = 10 SERVER_RETRY_INTERVAL = 10
@ -99,7 +107,7 @@ def parse_servers(result):
return servers return servers
def filter_protocol(hostmap = DEFAULT_SERVERS, protocol = 's'): def filter_protocol(hostmap, protocol = 's'):
'''Filters the hostmap for those implementing protocol. '''Filters the hostmap for those implementing protocol.
The result is a list in serialized form.''' The result is a list in serialized form.'''
eligible = [] eligible = []
@ -109,7 +117,9 @@ def filter_protocol(hostmap = DEFAULT_SERVERS, protocol = 's'):
eligible.append(serialize_server(host, port, protocol)) eligible.append(serialize_server(host, port, protocol))
return eligible return eligible
def pick_random_server(hostmap = DEFAULT_SERVERS, protocol = 's', exclude_set = set()): def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()):
if hostmap is None:
hostmap = DEFAULT_SERVERS
eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set) eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set)
return random.choice(eligible) if eligible else None return random.choice(eligible) if eligible else None

View file

@ -76,6 +76,9 @@ class SimpleConfig(PrintError):
if path is None: if path is None:
path = self.user_dir() path = self.user_dir()
if self.get('testnet'):
path = os.path.join(path, 'testnet')
# Make directory if it does not yet exist. # Make directory if it does not yet exist.
if not os.path.exists(path): if not os.path.exists(path):
if os.path.islink(path): if os.path.islink(path):

View file

@ -30,6 +30,7 @@
import bitcoin import bitcoin
from bitcoin import * from bitcoin import *
from bitcoin import hash160_to_p2sh, hash160_to_p2pkh
from util import print_error, profiler from util import print_error, profiler
import time import time
import sys import sys
@ -359,7 +360,7 @@ def parse_scriptSig(d, bytes):
d['x_pubkeys'] = x_pubkeys d['x_pubkeys'] = x_pubkeys
d['pubkeys'] = pubkeys d['pubkeys'] = pubkeys
d['redeemScript'] = redeemScript d['redeemScript'] = redeemScript
d['address'] = hash_160_to_bc_address(hash_160(redeemScript.decode('hex')), 5) d['address'] = hash160_to_p2sh(hash_160(redeemScript.decode('hex')))
@ -377,12 +378,12 @@ def get_address_from_output_script(bytes):
# DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG
match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ]
if match_decoded(decoded, match): if match_decoded(decoded, match):
return TYPE_ADDRESS, hash_160_to_bc_address(decoded[2][1]) return TYPE_ADDRESS, hash160_to_p2pkh(decoded[2][1])
# p2sh # p2sh
match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ]
if match_decoded(decoded, match): if match_decoded(decoded, match):
return TYPE_ADDRESS, hash_160_to_bc_address(decoded[1][1],5) return TYPE_ADDRESS, hash160_to_p2sh(decoded[1][1])
return TYPE_SCRIPT, bytes return TYPE_SCRIPT, bytes
@ -540,11 +541,11 @@ class Transaction:
return addr.encode('hex') return addr.encode('hex')
elif output_type == TYPE_ADDRESS: elif output_type == TYPE_ADDRESS:
addrtype, hash_160 = bc_address_to_hash_160(addr) addrtype, hash_160 = bc_address_to_hash_160(addr)
if addrtype == 0: if addrtype == bitcoin.ADDRTYPE_P2PKH:
script = '76a9' # op_dup, op_hash_160 script = '76a9' # op_dup, op_hash_160
script += push_script(hash_160.encode('hex')) script += push_script(hash_160.encode('hex'))
script += '88ac' # op_equalverify, op_checksig script += '88ac' # op_equalverify, op_checksig
elif addrtype == 5: elif addrtype == bitcoin.ADDRTYPE_P2SH:
script = 'a9' # op_hash_160 script = 'a9' # op_hash_160
script += push_script(hash_160.encode('hex')) script += push_script(hash_160.encode('hex'))
script += '87' # op_equal script += '87' # op_equal

View file

@ -1633,7 +1633,7 @@ class Multisig_Wallet(Deterministic_Wallet):
def pubkeys_to_address(self, pubkeys): def pubkeys_to_address(self, pubkeys):
redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m) redeem_script = Transaction.multisig_script(sorted(pubkeys), self.m)
address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5) address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), bitcoin.ADDRTYPE_P2SH)
return address return address
def new_pubkeys(self, c, i): def new_pubkeys(self, c, i):