Latest upstream changes
This commit is contained in:
parent
5fbb1d2dd2
commit
884208d93c
@ -1,19 +1,30 @@
|
||||
Metadata-Version: 1.2
|
||||
Metadata-Version: 2.1
|
||||
Name: electrumX
|
||||
Version: 1.8.7
|
||||
Version: 1.13.0
|
||||
Summary: ElectrumX Server
|
||||
Home-page: https://github.com/kyuupichan/electrumx
|
||||
Author: Neil Booth
|
||||
Author-email: kyuupichan@gmail.com
|
||||
License: MIT Licence
|
||||
Download-URL: https://github.com/kyuupichan/electrumX/archive/1.8.7.tar.gz
|
||||
Download-URL: https://github.com/kyuupichan/electrumX/archive/1.13.0.tar.gz
|
||||
Description: Server implementation for the Electrum protocol
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Framework :: AsyncIO
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: Unix
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Topic :: Database
|
||||
Classifier: Topic :: Internet
|
||||
Requires-Python: >=3.6
|
||||
Requires-Python: >=3.7
|
||||
Provides-Extra: rocksdb
|
||||
Provides-Extra: uvloop
|
||||
Provides-Extra: blake256
|
||||
Provides-Extra: crypto
|
||||
Provides-Extra: groestl
|
||||
Provides-Extra: tribus-hash
|
||||
Provides-Extra: xevan-hash
|
||||
Provides-Extra: x11-hash
|
||||
Provides-Extra: zny-yespower-0-5
|
||||
Provides-Extra: bell-yespower
|
||||
Provides-Extra: cpupower
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
README.rst
|
||||
electrumx_compact_history
|
||||
electrumx_rpc
|
||||
electrumx_server
|
||||
setup.py
|
||||
@ -19,6 +20,8 @@ electrumx/lib/script.py
|
||||
electrumx/lib/server_base.py
|
||||
electrumx/lib/text.py
|
||||
electrumx/lib/tx.py
|
||||
electrumx/lib/tx_axe.py
|
||||
electrumx/lib/tx_dash.py
|
||||
electrumx/lib/util.py
|
||||
electrumx/server/__init__.py
|
||||
electrumx/server/block_processor.py
|
||||
|
||||
@ -1,5 +1,38 @@
|
||||
aiorpcX<0.9,>=0.8.1
|
||||
aiorpcX[ws]<0.19,>=0.18.3
|
||||
attrs
|
||||
plyvel
|
||||
pylru
|
||||
aiohttp>=2
|
||||
aiohttp>=3.3
|
||||
|
||||
[bell-yespower]
|
||||
bell-yespower
|
||||
|
||||
[blake256]
|
||||
blake256>=0.1.1
|
||||
|
||||
[cpupower]
|
||||
cpupower
|
||||
|
||||
[crypto]
|
||||
pycryptodomex>=3.8.1
|
||||
|
||||
[groestl]
|
||||
groestlcoin-hash>=1.0.1
|
||||
|
||||
[rocksdb]
|
||||
python-rocksdb>=0.6.9
|
||||
|
||||
[tribus-hash]
|
||||
tribus-hash>=1.0.2
|
||||
|
||||
[uvloop]
|
||||
uvloop>=0.12.2
|
||||
|
||||
[x11-hash]
|
||||
x11-hash>=1.4
|
||||
|
||||
[xevan-hash]
|
||||
xevan-hash
|
||||
|
||||
[zny-yespower-0-5]
|
||||
zny-yespower-0-5
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
version = 'ElectrumX 1.13.0'
|
||||
version = 'ElectrumX 1.14.0'
|
||||
version_short = version.split()[-1]
|
||||
|
||||
from electrumx.server.controller import Controller
|
||||
|
||||
@ -86,6 +86,7 @@ class Coin(object):
|
||||
DECODE_CHECK = Base58.decode_check
|
||||
GENESIS_HASH = ('000000000019d6689c085ae165831e93'
|
||||
'4ff763ae46a2a6c172b3f1b60a8ce26f')
|
||||
GENESIS_ACTIVATION = 100_000_000
|
||||
# Peer discovery
|
||||
PEER_DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
||||
PEERS = []
|
||||
@ -146,13 +147,7 @@ class Coin(object):
|
||||
|
||||
@classmethod
|
||||
def hashX_from_script(cls, script):
|
||||
'''Returns a hashX from a script, or None if the script is provably
|
||||
unspendable so the output can be dropped.
|
||||
'''
|
||||
prefix = script[:2]
|
||||
# Match a prefix of OP_RETURN or (OP_FALSE, OP_RETURN)
|
||||
if prefix == b'\x00\x6a' or (prefix and prefix[0] == 0x6a):
|
||||
return None
|
||||
'''Returns a hashX from a script.'''
|
||||
return sha256(script).digest()[:HASHX_LEN]
|
||||
|
||||
@staticmethod
|
||||
@ -549,6 +544,7 @@ class BitcoinSV(BitcoinMixin, Coin):
|
||||
'sv.jochen-hoenicke.de s t',
|
||||
'sv.satoshi.io s t',
|
||||
]
|
||||
GENESIS_ACTIVATION = 620_538
|
||||
|
||||
|
||||
class BitcoinCash(BitcoinMixin, Coin):
|
||||
@ -777,6 +773,7 @@ class BitcoinSVTestnet(BitcoinTestnetMixin, Coin):
|
||||
PEERS = [
|
||||
'electrontest.cascharia.com t51001 s51002',
|
||||
]
|
||||
GENESIS_ACTIVATION = 1_344_302
|
||||
|
||||
|
||||
class BitcoinSVScalingTestnet(BitcoinSVTestnet):
|
||||
@ -787,6 +784,7 @@ class BitcoinSVScalingTestnet(BitcoinSVTestnet):
|
||||
TX_COUNT = 2015
|
||||
TX_COUNT_HEIGHT = 5711
|
||||
TX_PER_BLOCK = 5000
|
||||
GENESIS_ACTIVATION = 14_896
|
||||
|
||||
@classmethod
|
||||
def max_fetch_blocks(cls, height):
|
||||
@ -824,6 +822,7 @@ class BitcoinSVRegtest(BitcoinSVTestnet):
|
||||
PEERS = []
|
||||
TX_COUNT = 1
|
||||
TX_COUNT_HEIGHT = 1
|
||||
GENESIS_ACTIVATION = 10_000
|
||||
|
||||
|
||||
class BitcoinSegwitTestnet(BitcoinTestnetMixin, Coin):
|
||||
@ -925,16 +924,6 @@ class LitecoinTestnet(Litecoin):
|
||||
'electrum.ltc.xurious.com s t',
|
||||
]
|
||||
|
||||
|
||||
class LitecoinRegtest(LitecoinTestnet):
|
||||
NET = "regtest"
|
||||
GENESIS_HASH = ('530827f38f93b43ed12af0b3ad25a288'
|
||||
'dc02ed74d6d7857862df51fc56c416f9')
|
||||
PEERS = []
|
||||
TX_COUNT = 1
|
||||
TX_COUNT_HEIGHT = 1
|
||||
|
||||
|
||||
class Flo(Coin):
|
||||
NAME = "FLO"
|
||||
SHORTNAME = "FLO"
|
||||
@ -973,6 +962,15 @@ class FloTestnet(Flo):
|
||||
PEERS = []
|
||||
|
||||
|
||||
class LitecoinRegtest(LitecoinTestnet):
|
||||
NET = "regtest"
|
||||
GENESIS_HASH = ('530827f38f93b43ed12af0b3ad25a288'
|
||||
'dc02ed74d6d7857862df51fc56c416f9')
|
||||
PEERS = []
|
||||
TX_COUNT = 1
|
||||
TX_COUNT_HEIGHT = 1
|
||||
|
||||
|
||||
class BitcoinCashRegtest(BitcoinTestnetMixin, Coin):
|
||||
NAME = "BitcoinCashABC" # Some releases later remove the ABC suffix
|
||||
NET = "regtest"
|
||||
@ -3399,3 +3397,30 @@ class Myce(Coin):
|
||||
return scrypt.hash(header, header, 1024, 1, 1, 32)
|
||||
else:
|
||||
return double_sha256(header)
|
||||
|
||||
|
||||
class Navcoin(Coin):
|
||||
NAME = "Navcoin"
|
||||
SHORTNAME = "NAV"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("35")
|
||||
P2SH_VERBYTES = [bytes.fromhex("55")]
|
||||
WIF_BYTE = bytes.fromhex("96")
|
||||
GENESIS_HASH = ('00006a4e3e18c71c6d48ad6c261e2254'
|
||||
'fa764cf29607a4357c99b712dfbb8e6a')
|
||||
DESERIALIZER = lib_tx.DeserializerTxTimeSegWitNavCoin
|
||||
TX_COUNT = 137641
|
||||
TX_COUNT_HEIGHT = 3649662
|
||||
TX_PER_BLOCK = 2
|
||||
RPC_PORT = 44444
|
||||
REORG_LIMIT = 1000
|
||||
|
||||
@classmethod
|
||||
def header_hash(cls, header):
|
||||
if int.from_bytes(header[:4], "little") > 6:
|
||||
return double_sha256(header)
|
||||
else:
|
||||
import x13_hash
|
||||
return x13_hash.getPoWHash(header)
|
||||
|
||||
@ -76,6 +76,16 @@ assert OpCodes.OP_CHECKSIG == 0xac
|
||||
assert OpCodes.OP_CHECKMULTISIG == 0xae
|
||||
|
||||
|
||||
def is_unspendable_legacy(script):
|
||||
# OP_FALSE OP_RETURN or OP_RETURN
|
||||
return script[:2] == b'\x00\x6a' or (script and script[0] == 0x6a)
|
||||
|
||||
|
||||
def is_unspendable_genesis(script):
|
||||
# OP_FALSE OP_RETURN
|
||||
return script[:2] == b'\x00\x6a'
|
||||
|
||||
|
||||
def _match_ops(ops, pattern):
|
||||
if len(ops) != len(pattern):
|
||||
return False
|
||||
|
||||
@ -271,7 +271,6 @@ class DeserializerSegWit(Deserializer):
|
||||
tx, _tx_hash, vsize = self._read_tx_parts()
|
||||
return tx, vsize
|
||||
|
||||
|
||||
class TxFlo(namedtuple("Tx", "version inputs outputs locktime txcomment")):
|
||||
'''Class representing a transaction.'''
|
||||
|
||||
@ -346,8 +345,6 @@ class DeserializerFlo(DeserializerSegWit):
|
||||
locktime, tx_comment), double_sha256(orig_ser), vsize
|
||||
|
||||
|
||||
|
||||
|
||||
class DeserializerAuxPow(Deserializer):
|
||||
VERSION_AUXPOW = (1 << 8)
|
||||
|
||||
@ -537,6 +534,82 @@ class DeserializerTxTimeSegWit(DeserializerTxTime):
|
||||
return tx, vsize
|
||||
|
||||
|
||||
class DeserializerTxTimeSegWitNavCoin(DeserializerTxTime):
|
||||
def _read_witness(self, fields):
|
||||
read_witness_field = self._read_witness_field
|
||||
return [read_witness_field() for _ in range(fields)]
|
||||
|
||||
def _read_witness_field(self):
|
||||
read_varbytes = self._read_varbytes
|
||||
return [read_varbytes() for _ in range(self._read_varint())]
|
||||
|
||||
def read_tx_no_segwit(self):
|
||||
version = self._read_le_int32()
|
||||
time = self._read_le_uint32()
|
||||
inputs = self._read_inputs()
|
||||
outputs = self._read_outputs()
|
||||
locktime = self._read_le_uint32()
|
||||
strDZeel = ""
|
||||
if version >= 2:
|
||||
strDZeel = self._read_varbytes()
|
||||
return TxTime(
|
||||
version,
|
||||
time,
|
||||
inputs,
|
||||
outputs,
|
||||
locktime
|
||||
)
|
||||
|
||||
def _read_tx_parts(self):
|
||||
'''Return a (deserialized TX, tx_hash, vsize) tuple.'''
|
||||
start = self.cursor
|
||||
marker = self.binary[self.cursor + 8]
|
||||
if marker:
|
||||
tx = self.read_tx_no_segwit()
|
||||
tx_hash = self.TX_HASH_FN(self.binary[start:self.cursor])
|
||||
return tx, tx_hash, self.binary_length
|
||||
|
||||
version = self._read_le_int32()
|
||||
time = self._read_le_uint32()
|
||||
orig_ser = self.binary[start:self.cursor]
|
||||
|
||||
marker = self._read_byte()
|
||||
flag = self._read_byte()
|
||||
|
||||
start = self.cursor
|
||||
inputs = self._read_inputs()
|
||||
outputs = self._read_outputs()
|
||||
orig_ser += self.binary[start:self.cursor]
|
||||
|
||||
base_size = self.cursor - start
|
||||
witness = self._read_witness(len(inputs))
|
||||
|
||||
start = self.cursor
|
||||
locktime = self._read_le_uint32()
|
||||
strDZeel = ""
|
||||
|
||||
if version >= 2:
|
||||
strDZeel = self._read_varbytes()
|
||||
|
||||
vsize = (3 * base_size + self.binary_length) // 4
|
||||
orig_ser += self.binary[start:self.cursor]
|
||||
|
||||
return TxTimeSegWit(
|
||||
version, time, marker, flag, inputs, outputs, witness, locktime),\
|
||||
self.TX_HASH_FN(orig_ser), vsize
|
||||
|
||||
def read_tx(self):
|
||||
return self._read_tx_parts()[0]
|
||||
|
||||
def read_tx_and_hash(self):
|
||||
tx, tx_hash, vsize = self._read_tx_parts()
|
||||
return tx, tx_hash
|
||||
|
||||
def read_tx_and_vsize(self):
|
||||
tx, tx_hash, vsize = self._read_tx_parts()
|
||||
return tx, vsize
|
||||
|
||||
|
||||
class TxTrezarcoin(
|
||||
namedtuple("Tx", "version time inputs outputs locktime txcomment")):
|
||||
'''Class representing transaction that has a time and txcomment field.'''
|
||||
|
||||
@ -17,6 +17,7 @@ from aiorpcx import TaskGroup, run_in_thread
|
||||
import electrumx
|
||||
from electrumx.server.daemon import DaemonError
|
||||
from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN
|
||||
from electrumx.lib.script import is_unspendable_legacy, is_unspendable_genesis
|
||||
from electrumx.lib.util import (
|
||||
chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint32
|
||||
)
|
||||
@ -385,10 +386,13 @@ class BlockProcessor(object):
|
||||
'''
|
||||
min_height = self.db.min_undo_height(self.daemon.cached_height())
|
||||
height = self.height
|
||||
genesis_activation = self.coin.GENESIS_ACTIVATION
|
||||
|
||||
for block in blocks:
|
||||
height += 1
|
||||
undo_info = self.advance_txs(block.transactions)
|
||||
is_unspendable = (is_unspendable_genesis if height >= genesis_activation
|
||||
else is_unspendable_legacy)
|
||||
undo_info = self.advance_txs(block.transactions, is_unspendable)
|
||||
if height >= min_height:
|
||||
self.undo_infos.append((undo_info, height))
|
||||
self.db.write_raw_block(block.raw, height)
|
||||
@ -398,7 +402,7 @@ class BlockProcessor(object):
|
||||
self.headers.extend(headers)
|
||||
self.tip = self.coin.header_hash(headers[-1])
|
||||
|
||||
def advance_txs(self, txs):
|
||||
def advance_txs(self, txs, is_unspendable):
|
||||
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
||||
|
||||
# Use local vars for speed in the loops
|
||||
@ -429,12 +433,15 @@ class BlockProcessor(object):
|
||||
|
||||
# Add the new UTXOs
|
||||
for idx, txout in enumerate(tx.outputs):
|
||||
# Get the hashX. Ignore unspendable outputs
|
||||
# Ignore unspendable outputs
|
||||
if is_unspendable(txout.pk_script):
|
||||
continue
|
||||
|
||||
# Get the hashX
|
||||
hashX = script_hashX(txout.pk_script)
|
||||
if hashX:
|
||||
append_hashX(hashX)
|
||||
put_utxo(tx_hash + to_le_uint32(idx),
|
||||
hashX + tx_numb + to_le_uint64(txout.value))
|
||||
append_hashX(hashX)
|
||||
put_utxo(tx_hash + to_le_uint32(idx),
|
||||
hashX + tx_numb + to_le_uint64(txout.value))
|
||||
|
||||
append_hashXs(hashXs)
|
||||
update_touched(hashXs)
|
||||
@ -455,6 +462,7 @@ class BlockProcessor(object):
|
||||
'''
|
||||
self.db.assert_flushed(self.flush_data())
|
||||
assert self.height >= len(raw_blocks)
|
||||
genesis_activation = self.coin.GENESIS_ACTIVATION
|
||||
|
||||
coin = self.coin
|
||||
for raw_block in raw_blocks:
|
||||
@ -467,13 +475,15 @@ class BlockProcessor(object):
|
||||
hash_to_hex_str(self.tip),
|
||||
self.height))
|
||||
self.tip = coin.header_prevhash(block.header)
|
||||
self.backup_txs(block.transactions)
|
||||
is_unspendable = (is_unspendable_genesis if self.height >= genesis_activation
|
||||
else is_unspendable_legacy)
|
||||
self.backup_txs(block.transactions, is_unspendable)
|
||||
self.height -= 1
|
||||
self.db.tx_counts.pop()
|
||||
|
||||
self.logger.info('backed up to height {:,d}'.format(self.height))
|
||||
|
||||
def backup_txs(self, txs):
|
||||
def backup_txs(self, txs, is_unspendable):
|
||||
# Prevout values, in order down the block (coinbase first if present)
|
||||
# undo_info is in reverse block order
|
||||
undo_info = self.db.read_undo_info(self.height)
|
||||
@ -493,10 +503,13 @@ class BlockProcessor(object):
|
||||
for idx, txout in enumerate(tx.outputs):
|
||||
# Spend the TX outputs. Be careful with unspendable
|
||||
# outputs - we didn't save those in the first place.
|
||||
if is_unspendable(txout.pk_script):
|
||||
continue
|
||||
|
||||
# Get the hashX
|
||||
hashX = script_hashX(txout.pk_script)
|
||||
if hashX:
|
||||
cache_value = spend_utxo(tx_hash, idx)
|
||||
touched.add(cache_value[:-12])
|
||||
cache_value = spend_utxo(tx_hash, idx)
|
||||
touched.add(cache_value[:-12])
|
||||
|
||||
# Restore the inputs
|
||||
for txin in reversed(tx.inputs):
|
||||
@ -590,7 +603,7 @@ class BlockProcessor(object):
|
||||
|
||||
if len(candidates) > 1:
|
||||
tx_num, = unpack_le_uint32(tx_num_packed)
|
||||
hash, height = self.db.fs_tx_hash(tx_num)
|
||||
hash, _height = self.db.fs_tx_hash(tx_num)
|
||||
if hash != tx_hash:
|
||||
assert hash is not None # Should always be found
|
||||
continue
|
||||
@ -691,8 +704,8 @@ class DecredBlockProcessor(BlockProcessor):
|
||||
|
||||
class NameIndexBlockProcessor(BlockProcessor):
|
||||
|
||||
def advance_txs(self, txs):
|
||||
result = super().advance_txs(txs)
|
||||
def advance_txs(self, txs, is_unspendable):
|
||||
result = super().advance_txs(txs, is_unspendable)
|
||||
|
||||
tx_num = self.tx_count - len(txs)
|
||||
script_name_hashX = self.coin.name_hashX_from_script
|
||||
@ -722,7 +735,7 @@ class NameIndexBlockProcessor(BlockProcessor):
|
||||
|
||||
class LTORBlockProcessor(BlockProcessor):
|
||||
|
||||
def advance_txs(self, txs):
|
||||
def advance_txs(self, txs, is_unspendable):
|
||||
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
||||
|
||||
# Use local vars for speed in the loops
|
||||
@ -744,12 +757,15 @@ class LTORBlockProcessor(BlockProcessor):
|
||||
tx_numb = to_le_uint32(tx_num)
|
||||
|
||||
for idx, txout in enumerate(tx.outputs):
|
||||
# Get the hashX. Ignore unspendable outputs.
|
||||
# Ignore unspendable outputs
|
||||
if is_unspendable(txout.pk_script):
|
||||
continue
|
||||
|
||||
# Get the hashX
|
||||
hashX = script_hashX(txout.pk_script)
|
||||
if hashX:
|
||||
add_hashXs(hashX)
|
||||
put_utxo(tx_hash + to_le_uint32(idx),
|
||||
hashX + tx_numb + to_le_uint64(txout.value))
|
||||
add_hashXs(hashX)
|
||||
put_utxo(tx_hash + to_le_uint32(idx),
|
||||
hashX + tx_numb + to_le_uint64(txout.value))
|
||||
tx_num += 1
|
||||
|
||||
# Spend the inputs
|
||||
@ -774,7 +790,7 @@ class LTORBlockProcessor(BlockProcessor):
|
||||
|
||||
return undo_info
|
||||
|
||||
def backup_txs(self, txs):
|
||||
def backup_txs(self, txs, is_unspendable):
|
||||
undo_info = self.db.read_undo_info(self.height)
|
||||
if undo_info is None:
|
||||
raise ChainError('no undo information found for height {:,d}'
|
||||
@ -804,11 +820,14 @@ class LTORBlockProcessor(BlockProcessor):
|
||||
# Remove tx outputs made in this block, by spending them.
|
||||
for tx, tx_hash in txs:
|
||||
for idx, txout in enumerate(tx.outputs):
|
||||
hashX = script_hashX(txout.pk_script)
|
||||
if hashX:
|
||||
# Be careful with unspendable outputs- we didn't save those
|
||||
# in the first place.
|
||||
cache_value = spend_utxo(tx_hash, idx)
|
||||
add_touched(cache_value[:-12])
|
||||
# Spend the TX outputs. Be careful with unspendable
|
||||
# outputs - we didn't save those in the first place.
|
||||
if is_unspendable(txout.pk_script):
|
||||
continue
|
||||
|
||||
# Get the hashX
|
||||
hashX = script_hashX(txout.script)
|
||||
cache_value = spend_utxo(tx_hash, idx)
|
||||
add_touched(cache_value[:-12])
|
||||
|
||||
self.tx_count -= len(txs)
|
||||
|
||||
@ -1,309 +0,0 @@
|
||||
# Copyright (c) 2017, Neil Booth
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# See the file "LICENCE" for information about the copyright
|
||||
# and warranty status of this software.
|
||||
|
||||
'''Logic for BIP32 Hierarchical Key Derviation.'''
|
||||
|
||||
import struct
|
||||
|
||||
import ecdsa
|
||||
import ecdsa.ellipticcurve as EC
|
||||
import ecdsa.numbertheory as NT
|
||||
|
||||
from electrumx.lib.coins import Coin
|
||||
from electrumx.lib.hash import Base58, hmac_sha512, hash160
|
||||
from electrumx.lib.util import cachedproperty, bytes_to_int, int_to_bytes, \
|
||||
pack_be_uint32, unpack_be_uint32_from
|
||||
|
||||
|
||||
class DerivationError(Exception):
|
||||
'''Raised when an invalid derivation occurs.'''
|
||||
|
||||
|
||||
class _KeyBase(object):
|
||||
'''A BIP32 Key, public or private.'''
|
||||
|
||||
CURVE = ecdsa.SECP256k1
|
||||
|
||||
def __init__(self, chain_code, n, depth, parent):
|
||||
if not isinstance(chain_code, (bytes, bytearray)):
|
||||
raise TypeError('chain code must be raw bytes')
|
||||
if len(chain_code) != 32:
|
||||
raise ValueError('invalid chain code')
|
||||
if not 0 <= n < 1 << 32:
|
||||
raise ValueError('invalid child number')
|
||||
if not 0 <= depth < 256:
|
||||
raise ValueError('invalid depth')
|
||||
if parent is not None:
|
||||
if not isinstance(parent, type(self)):
|
||||
raise TypeError('parent key has bad type')
|
||||
self.chain_code = chain_code
|
||||
self.n = n
|
||||
self.depth = depth
|
||||
self.parent = parent
|
||||
|
||||
def _hmac_sha512(self, msg):
|
||||
'''Use SHA-512 to provide an HMAC, returned as a pair of 32-byte
|
||||
objects.
|
||||
'''
|
||||
hmac = hmac_sha512(self.chain_code, msg)
|
||||
return hmac[:32], hmac[32:]
|
||||
|
||||
def _extended_key(self, ver_bytes, raw_serkey):
|
||||
'''Return the 78-byte extended key given prefix version bytes and
|
||||
serialized key bytes.
|
||||
'''
|
||||
if not isinstance(ver_bytes, (bytes, bytearray)):
|
||||
raise TypeError('ver_bytes must be raw bytes')
|
||||
if len(ver_bytes) != 4:
|
||||
raise ValueError('ver_bytes must have length 4')
|
||||
if not isinstance(raw_serkey, (bytes, bytearray)):
|
||||
raise TypeError('raw_serkey must be raw bytes')
|
||||
if len(raw_serkey) != 33:
|
||||
raise ValueError('raw_serkey must have length 33')
|
||||
|
||||
return (ver_bytes + bytes([self.depth])
|
||||
+ self.parent_fingerprint() + pack_be_uint32(self.n)
|
||||
+ self.chain_code + raw_serkey)
|
||||
|
||||
def fingerprint(self):
|
||||
'''Return the key's fingerprint as 4 bytes.'''
|
||||
return self.identifier()[:4]
|
||||
|
||||
def parent_fingerprint(self):
|
||||
'''Return the parent key's fingerprint as 4 bytes.'''
|
||||
return self.parent.fingerprint() if self.parent else bytes(4)
|
||||
|
||||
def extended_key_string(self, coin):
|
||||
'''Return an extended key as a base58 string.'''
|
||||
return Base58.encode_check(self.extended_key(coin))
|
||||
|
||||
|
||||
class PubKey(_KeyBase):
|
||||
'''A BIP32 public key.'''
|
||||
|
||||
def __init__(self, pubkey, chain_code, n, depth, parent=None):
|
||||
super().__init__(chain_code, n, depth, parent)
|
||||
if isinstance(pubkey, ecdsa.VerifyingKey):
|
||||
self.verifying_key = pubkey
|
||||
else:
|
||||
self.verifying_key = self._verifying_key_from_pubkey(pubkey)
|
||||
self.addresses = {}
|
||||
|
||||
@classmethod
|
||||
def _verifying_key_from_pubkey(cls, pubkey):
|
||||
'''Converts a 33-byte compressed pubkey into an ecdsa.VerifyingKey
|
||||
object'''
|
||||
if not isinstance(pubkey, (bytes, bytearray)):
|
||||
raise TypeError('pubkey must be raw bytes')
|
||||
if len(pubkey) != 33:
|
||||
raise ValueError('pubkey must be 33 bytes')
|
||||
if pubkey[0] not in (2, 3):
|
||||
raise ValueError('invalid pubkey prefix byte')
|
||||
curve = cls.CURVE.curve
|
||||
|
||||
is_odd = pubkey[0] == 3
|
||||
x = bytes_to_int(pubkey[1:])
|
||||
|
||||
# p is the finite field order
|
||||
a, b, p = curve.a(), curve.b(), curve.p()
|
||||
y2 = pow(x, 3, p) + b
|
||||
assert a == 0 # Otherwise y2 += a * pow(x, 2, p)
|
||||
y = NT.square_root_mod_prime(y2 % p, p)
|
||||
if bool(y & 1) != is_odd:
|
||||
y = p - y
|
||||
point = EC.Point(curve, x, y)
|
||||
|
||||
return ecdsa.VerifyingKey.from_public_point(point, curve=cls.CURVE)
|
||||
|
||||
@cachedproperty
|
||||
def pubkey_bytes(self):
|
||||
'''Return the compressed public key as 33 bytes.'''
|
||||
point = self.verifying_key.pubkey.point
|
||||
prefix = bytes([2 + (point.y() & 1)])
|
||||
padded_bytes = _exponent_to_bytes(point.x())
|
||||
return prefix + padded_bytes
|
||||
|
||||
def address(self, coin):
|
||||
"The public key as a P2PKH address"
|
||||
address = self.addresses.get(coin)
|
||||
if not address:
|
||||
address = coin.P2PKH_address_from_pubkey(self.pubkey_bytes)
|
||||
self.addresses[coin] = address
|
||||
return address
|
||||
|
||||
def ec_point(self):
|
||||
return self.verifying_key.pubkey.point
|
||||
|
||||
def child(self, n):
|
||||
'''Return the derived child extended pubkey at index N.'''
|
||||
if not 0 <= n < (1 << 31):
|
||||
raise ValueError('invalid BIP32 public key child number')
|
||||
|
||||
msg = self.pubkey_bytes + pack_be_uint32(n)
|
||||
L, R = self._hmac_sha512(msg)
|
||||
|
||||
curve = self.CURVE
|
||||
L = bytes_to_int(L)
|
||||
if L >= curve.order:
|
||||
raise DerivationError
|
||||
|
||||
point = curve.generator * L + self.ec_point()
|
||||
if point == EC.INFINITY:
|
||||
raise DerivationError
|
||||
|
||||
verkey = ecdsa.VerifyingKey.from_public_point(point, curve=curve)
|
||||
|
||||
return PubKey(verkey, R, n, self.depth + 1, self)
|
||||
|
||||
def identifier(self):
|
||||
'''Return the key's identifier as 20 bytes.'''
|
||||
return hash160(self.pubkey_bytes)
|
||||
|
||||
def extended_key(self, coin):
|
||||
'''Return a raw extended public key.'''
|
||||
return self._extended_key(coin.XPUB_VERBYTES, self.pubkey_bytes)
|
||||
|
||||
|
||||
class PrivKey(_KeyBase):
|
||||
'''A BIP32 private key.'''
|
||||
|
||||
HARDENED = 1 << 31
|
||||
|
||||
def __init__(self, privkey, chain_code, n, depth, parent=None):
|
||||
super().__init__(chain_code, n, depth, parent)
|
||||
if isinstance(privkey, ecdsa.SigningKey):
|
||||
self.signing_key = privkey
|
||||
else:
|
||||
self.signing_key = self._signing_key_from_privkey(privkey)
|
||||
|
||||
@classmethod
|
||||
def _signing_key_from_privkey(cls, privkey):
|
||||
'''Converts a 32-byte privkey into an ecdsa.SigningKey object.'''
|
||||
exponent = cls._privkey_secret_exponent(privkey)
|
||||
return ecdsa.SigningKey.from_secret_exponent(exponent, curve=cls.CURVE)
|
||||
|
||||
@classmethod
|
||||
def _privkey_secret_exponent(cls, privkey):
|
||||
'''Return the private key as a secret exponent if it is a valid private
|
||||
key.'''
|
||||
if not isinstance(privkey, (bytes, bytearray)):
|
||||
raise TypeError('privkey must be raw bytes')
|
||||
if len(privkey) != 32:
|
||||
raise ValueError('privkey must be 32 bytes')
|
||||
exponent = bytes_to_int(privkey)
|
||||
if not 1 <= exponent < cls.CURVE.order:
|
||||
raise ValueError('privkey represents an invalid exponent')
|
||||
|
||||
return exponent
|
||||
|
||||
@classmethod
|
||||
def from_seed(cls, seed):
|
||||
# This hard-coded message string seems to be coin-independent...
|
||||
hmac = hmac_sha512(b'Bitcoin seed', seed)
|
||||
privkey, chain_code = hmac[:32], hmac[32:]
|
||||
return cls(privkey, chain_code, 0, 0)
|
||||
|
||||
@cachedproperty
|
||||
def privkey_bytes(self):
|
||||
'''Return the serialized private key (no leading zero byte).'''
|
||||
return _exponent_to_bytes(self.secret_exponent())
|
||||
|
||||
@cachedproperty
|
||||
def public_key(self):
|
||||
'''Return the corresponding extended public key.'''
|
||||
verifying_key = self.signing_key.get_verifying_key()
|
||||
parent_pubkey = self.parent.public_key if self.parent else None
|
||||
return PubKey(verifying_key, self.chain_code, self.n, self.depth,
|
||||
parent_pubkey)
|
||||
|
||||
def ec_point(self):
|
||||
return self.public_key.ec_point()
|
||||
|
||||
def secret_exponent(self):
|
||||
'''Return the private key as a secret exponent.'''
|
||||
return self.signing_key.privkey.secret_multiplier
|
||||
|
||||
def WIF(self, coin):
|
||||
'''Return the private key encoded in Wallet Import Format.'''
|
||||
return coin.privkey_WIF(self.privkey_bytes, compressed=True)
|
||||
|
||||
def address(self, coin):
|
||||
"The public key as a P2PKH address"
|
||||
return self.public_key.address(coin)
|
||||
|
||||
def child(self, n):
|
||||
'''Return the derived child extended privkey at index N.'''
|
||||
if not 0 <= n < (1 << 32):
|
||||
raise ValueError('invalid BIP32 private key child number')
|
||||
|
||||
if n >= self.HARDENED:
|
||||
serkey = b'\0' + self.privkey_bytes
|
||||
else:
|
||||
serkey = self.public_key.pubkey_bytes
|
||||
|
||||
msg = serkey + pack_be_uint32(n)
|
||||
L, R = self._hmac_sha512(msg)
|
||||
|
||||
curve = self.CURVE
|
||||
L = bytes_to_int(L)
|
||||
exponent = (L + bytes_to_int(self.privkey_bytes)) % curve.order
|
||||
if exponent == 0 or L >= curve.order:
|
||||
raise DerivationError
|
||||
|
||||
privkey = _exponent_to_bytes(exponent)
|
||||
|
||||
return PrivKey(privkey, R, n, self.depth + 1, self)
|
||||
|
||||
def identifier(self):
|
||||
'''Return the key's identifier as 20 bytes.'''
|
||||
return self.public_key.identifier()
|
||||
|
||||
def extended_key(self, coin):
|
||||
'''Return a raw extended private key.'''
|
||||
return self._extended_key(coin.XPRV_VERBYTES,
|
||||
b'\0' + self.privkey_bytes)
|
||||
|
||||
|
||||
def _exponent_to_bytes(exponent):
|
||||
'''Convert an exponent to 32 big-endian bytes'''
|
||||
return (bytes(32) + int_to_bytes(exponent))[-32:]
|
||||
|
||||
|
||||
def _from_extended_key(ekey):
|
||||
'''Return a PubKey or PrivKey from an extended key raw bytes.'''
|
||||
if not isinstance(ekey, (bytes, bytearray)):
|
||||
raise TypeError('extended key must be raw bytes')
|
||||
if len(ekey) != 78:
|
||||
raise ValueError('extended key must have length 78')
|
||||
|
||||
is_public, coin = Coin.lookup_xverbytes(ekey[:4])
|
||||
depth = ekey[4]
|
||||
fingerprint = ekey[5:9] # Not used
|
||||
n, = unpack_be_uint32_from(ekey[9:13])
|
||||
chain_code = ekey[13:45]
|
||||
|
||||
if is_public:
|
||||
pubkey = ekey[45:]
|
||||
key = PubKey(pubkey, chain_code, n, depth)
|
||||
else:
|
||||
if ekey[45] is not 0:
|
||||
raise ValueError('invalid extended private key prefix byte')
|
||||
privkey = ekey[46:]
|
||||
key = PrivKey(privkey, chain_code, n, depth)
|
||||
|
||||
return key, coin
|
||||
|
||||
|
||||
def from_extended_key_string(ekey_str):
|
||||
'''Given an extended key string, such as
|
||||
|
||||
xpub6BsnM1W2Y7qLMiuhi7f7dbAwQZ5Cz5gYJCRzTNainXzQXYjFwtuQXHd
|
||||
3qfi3t3KJtHxshXezfjft93w4UE7BGMtKwhqEHae3ZA7d823DVrL
|
||||
|
||||
return a (key, coin) pair. key is either a PubKey or PrivKey.
|
||||
'''
|
||||
return _from_extended_key(Base58.decode_check(ekey_str))
|
||||
@ -1,31 +0,0 @@
|
||||
# Copyright (c) 2017, Neil Booth
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# See the file "LICENCE" for information about the copyright
|
||||
# and warranty status of this software.
|
||||
|
||||
'''Class for handling environment configuration and defaults.'''
|
||||
|
||||
|
||||
from electrumx.lib.env_base import EnvBase
|
||||
|
||||
|
||||
class Env(EnvBase):
|
||||
'''Wraps environment configuration.'''
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.db_dir = self.required('DB_DIRECTORY')
|
||||
self.db_engine = self.default('DB_ENGINE', 'leveldb')
|
||||
|
||||
self.ssl_port = self.integer('SSL_PORT', 20214)
|
||||
self.ssl_certfile = self.required('SSL_CERTFILE')
|
||||
self.ssl_keyfile = self.required('SSL_KEYFILE')
|
||||
|
||||
self.rpc_port = self.integer('RPC_PORT', 8001)
|
||||
|
||||
self.tor_proxy_host = self.default('TOR_PROXY_HOST', 'localhost')
|
||||
self.tor_proxy_port = self.integer('TOR_PROXY_PORT', None)
|
||||
|
||||
self.session_timeout = self.integer('SESSION_TIMEOUT', 600)
|
||||
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3.7
|
||||
#!/usr/bin/python3.7
|
||||
#
|
||||
# Copyright (c) 2016-2018, Neil Booth
|
||||
#
|
||||
@ -10,7 +10,7 @@
|
||||
'''Script to kick off the server.'''
|
||||
|
||||
import asyncio
|
||||
import loggingd
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from electrumx import Controller, Env
|
||||
|
||||
Loading…
Reference in New Issue
Block a user